type
status
date
slug
summary
tags
category
icon
password
web342 很刺激 (
感觉对原型链污染理解又加深了呢~
web334 JS 大小写特性
搜集了半天信息,发现题目描述处有源码可以直接下载。。
user.js
login.js
即满足
我们输入的
name
不为 CTFSHOW
然后 name
转换后的大写为 CTFSHOW
,密码为 123456
即可。网上看了一下,其实想要的考点是
toUpperCase()
函数,字符 ı 会转变为 I ,字符 ſ 会变为 S 。toLowerCase()
函数中,字符 İ 会转变为 i ,字符 K 会转变为 k 。可以参考
这样的应该是,比如字符
s
或 S
不能出现在输入字符中就好了。web335 RCE
f12 源码提示
即简单命令执行
获取 flag
web336 RCE 黑名单绕过
f12 源码提示
即简单命令执行
获取 flag
emm 好像就 flag 文件换了个名字?
顺便收集多几个命令执行
收集过程中发现,原来第 2、3 个 被禁了,2333 原来考察黑名单
先通过全局变量读取当前目录位置
当前目录
读文件
发现过滤了exec和load
绕过方法
因为
require('child_process')
方法返回一个对象,可以通过类型 Python数组的方式去访问里面的成员。或者用其他模块读文件
当然还有其他姿势,比如变量拼接在执行
2023.11.6 更,发其他师傅那get到一种新姿势
web337 md5绕过
和 PHP 一样,数组绕过即可
发现这样更易于理解
payload:
a[x]=1&b[x]=2
运行一下代码
web338 原型链污染
题目描述处蓝奏云下载源码
先看看这篇文章了解一波什么是 JS 的原型链污染
关键代码
utils/common.js
routes/login.js
也就是说,我们不用关心
secert
是否有 ctfshow
这个属性,因为当它找不到这个属性时,它会从它自己的原型里找。这里的
secert
是一个数组,然后 utils.copy(user,req.body);
操作是 user
也是数组,也就是我们通过 req.body
即 POST 请求体传入参数,通过 user
污染数组的原型,那么 secert
数组找不到 ctfshow
属性时,会一直往原型找,直到在数组原型中发现 ctfshow
属性值为 36dboy
。那么 if
语句即判断成功,就会输出 flag 了。payload
一般遇到这种整套代码,特别是带有 package.json 的,可以尝试 snyk ,
发现个
ejs
的 RCE先污染参数
然后随便请求一下,触发
render
方法成功反弹 shell
不过 snyk 给出的 poc 用
filename
参数利用好像有点问题, 不太清楚为啥。。即顺便记录一下 node 项目本地启动方法,在项目文件运行下面命令即可。
默认端口为
3000
web339 原型链污染 +
node/v10.19.0
题目描述处蓝奏云下载源码
和 web338 有点相似,不过不同点是
flag
是变量,具体值不知然后还多了个
api.js
,主要关注这行是不是和 web338 里的参考链接 PHITHON 师傅出的 Code-Breaking 2018 Thejs 如出一辙?即可以 RCE,因为这里可污染点存在的匿名函数调用
仿照题目中的代码,先写个小demo
为什么
query
的值是 2233
呢?也就是为啥会被调用了呢?首先看看
query
值是如何被改变的,其实就是通过 web338 的原型链污染,即 JS 中所有的对象的原型都可以继承到 Object
,然后终点是 null
对象如 web338 中所说的,当在当前上下文找不到相应对象时,会遍历
Object
对象是否存在相应的属性。到这里就很清楚的知道了,为什么
query
的值是 "return 2233"
,因为在调用 copy
时,原型链被污染了。至于
{ query: Function(query)(query)}
为何为 { query: 2233 }
JS 的函数实际上都是一个
Function
对象,它的参数为写个小demo
即
Function
对象传入构造函数里的前面参数是函数的形参,当然可以省略,最后的形参写函数体。其实作用和
eval
有点类似,详细可以看至此,利用思路明确了,只要污染了
query
对象,就可以执行任意我们想执行的代码,比如反弹个 shell 再获取 flag ~然后污染点和 web338 一致,在
login.js
里的 utils.copy(user,req.body);
,代码执行的触发点在 api.js
的 res.render('api', { query: Function(query)(query)});
处。payload ,这里用 nodejs 原生 socket,防止因系统运行环境问题 shell 弹不回来,这里服务器的监听端口为
2233
,然后如何是 windows 系统就把 /bin/sh
换成 cmd.exe
应该就可以了。先本地试试,这里服务器监听端口为
2233
ok,没啥大问题。
先 POST 一下
login
接口,污染 query
对象然后直接 POST 一下
api
接口即可。emm,发现不行,之后访问
/login
和 /api
接口都是 404 找不到文件
,shell 也反弹不回来。试试别人的
flag
仔细对比了一下,发现了,感觉是命名空间问题,即
require
可能不被识别,尝试把 require 改为 global.process.mainModule.constructor._load
,同样服务器监听端口为 2233
也是先 POST /login 接口污染
query
对象访问下 /api 接口
反弹 shell 并获取 flag
然后顺便翻了个链接,好像确实如此~
因为 node 是基于 chrome v8 内核的,运行时,压根就不会有
require
这种关键字,模块加载不进来,自然 shell 就反弹不了了。但在 node交互环境,或者写 js 文件时,通过 node 运行会自动把 require
进行编译。还有一个其他解,见 web338 中的
ejs
RCE,一样的EXP、步骤和利用方式web340 原型连污染 ++
nodejs/v10.19.0
题目描述处蓝奏云下载源码
提取关键部分
发现
userinfo
的原型不是 Object
对象, userinfo.__proto__.__proto__
才是 Object
对象。和 web339 一样,只不过要套两层才能污染
Object
对象,同样用原生 socket,服务端监听端口为 2233
(同样 POST 一下 /login 接口污染
query
对象POST 一下 /api 接口触发
获取 flag
web341 原型链污染 +++
题目描述处蓝奏云下载源码
这题和其他不同的是没有
/api
接口触发污染点了,所以使用 web338 中的 ejs
RCE。然后像 web340 一样污染要套两层。下面 EXP 也服务器监听端口是
2233
同样 POST 一下 /login 接口污染数据
然后请求一个会调用
render
方法的接口即可获得 flag
web342 原型链污染 ++++
题目描述处蓝奏云下载源码
然后还有个提示
审计了1个小时发现的,此链目前网上未公开,难度稍大
本题就不是用
ejs
进行模板渲染了,而是使用了 jade
一开始想看其他师傅的文章
https://xz.aliyun.com/t/7025
emm,但是发现说的每个字都认识,连起来就觉得有点莫名其妙了。。于是想着先自己手动分析一下,熟悉一波,方便自己和师傅在同一个频道上,防止师傅说的每句话都感觉莫名其妙…
详细的断点分析参考
这里用 VS Code 来 debug 分析
就会自动创建这样一个文件
然后下个断点,运行一下,访问网页就可以开始愉快的 debug 了
这里直接用 ctfshow web342 的代码进行
依次进入
res.render=>app.render=>tryRender=>view.render=>this.engine
入口是
rederFile
方法,注意 renderFile
函数返回值可执行进入
handleTemplateCache
进入
compile
函数205行 有个
parse
解析,可以看到结果返回到 parsed
,又传递给了 fn
,先不管 parse
方法,继续向下看代码218行 这里就比较有趣了,有个看似可控的代码执行(new Function,这个在 web339 有提到)
进入
parse
看看返回值中是否有可控部分parse
方法内部可以看到先内部 parse
再内部 compile
,内部 parse
结果最终会被拼接到外层 parse
函数返回值部分即 114行的
js对象
被拼接到 148行 或 149行的 js对象
里,最终拼接到 body键
返回先跟进
114行
的 compile
方法,发现本方法返回的是 buf
跟进 66行的
this.visit(this.node);
发现可控(因为
node.filename
被 utils.stringify()
了)的 node.line
可以被 push
到 buf
中,条件是 this.debug=true
然后在 212行的
this.visitNode(node);
去遍历 ast 树,遍历完后回到 compile
方法可以看到
node.line
被记录到 fn 中,然后通过 Function
调用执行这部分代码。也就是说如果我们可以污染
node.line
就可以运行我们代码。也就是说
jade
本身是没有漏洞的,因为模板的渲染逻辑是如何,但问题是,如果存在可控的原型链污染,就可以帮助我们污染 node.line
。于是尝试 POST
/login
接口断点发现,,
node.line
值还是 0
、1
、2
之类的?突然醒悟,,还有一个问题,我们在调试过程中发现,
node.line
是存在值的,比如上图中的 0
、1
、2
,如果存在值的话,它会直接获取这个值,而不是获取我们污染过的 Object
对象里的值!其实原型链污染的利用核心就是:访问对象的属性/值为 undefined 才行,不然我们污染
Object
对象就没有意义了。现在看这篇文章就能看懂了,而且发现总结的非常好。。
好的,在同一频道上了。
先梳理上面分析的函数调用栈,我们断点入口在
routes/index.js
res.render('index',{title:'ctfshow'});
,调用栈如下- routes/index.js :: res.render
- jade/lib/index.js :: exports.__express
- jade/lib/index.js :: exports.renderFile
- jade/lib/index.js :: handleTemplateCache
- jade/lib/index.js :: exports.compile
- jade/lib/index.js :: parse -> compiler.compile();
- jade/lib/compiler.js :: Compiler.compile -> this.visit(this.node)jade/lib/compiler.js :: this.visitjade/lib/compiler.js :: this.buf.push
- jade/lib/index.js :: parse -> options.selfjade/lib/index.js :: fn = new Function(‘locals, jade’, fn)jade/lib/index.js :: fn(locals, Object.create(runtime))
至 4. a. iii. 这部分调用栈用下图,4. b. c. d. 的在上面 debug 也有提及,因此调用栈这部分问题不大
问题有 3
visit
方法中this.debug=true
,不然this.buf.push
调用不了
- 在上面提到的:
node.line
在某处为undefined
才行,不然我们污染的Object
对象就没意义
- 保证能够执行到渲染阶段,因为覆盖某些属性会导致莫名其妙的异常
第 1 个问题容易解决,因为在 Jade 入口
exports.__express
,我们上面 deubg 分析时也看到options.compileDebug
无初始值,可以覆盖开启 Debug 模式(经分析,this.debug
获取的就是这里的 debug 值),当然也有另外一种情况,部署时,没有正确配置 req.app.get('env')
导致 debug 模式开启,那么这个变量也可以不用覆盖,但为了确保通用性,这里还是覆盖一下,防止正确配置,2333。这里因
utils.copy(user.userinfo,req.body);
与 web341一样,userinfo.__proto__.__proto__
才是 Object
对象的原型,所以要套两层。先打一下
第 3 个问题提前出现了,要先解决这个问题,保证代码能够执行到渲染阶段
和先知文章里不同,这里报错有点不一样,先跟进
Compiler.visitNode
225行看一下拼接一下,发现没有这个方法
在遍历AST树时,通常是通过
"visit" + 节点类型
来遍历所有节点的,观察错误调用栈也知,比如 visitBlock
,就是访问 Block
节点。那 Block
可用,我们就污染一下 type
就好了,当它当前上下文找不到就去找 Object
了。当然选取时,最好是选的节点附带的上下文信息进入后,啥事也不会做的,不然也有走向奇奇怪怪的逻辑,先来看看 Block 节点。
image.png
这里加上这部分逻辑,来看看我们故意伪造 Block 节点会发生什么
这里
block.nodes
为 undefined
,然后 undefined.length
image.png
明显,这样访问会报错,导致进入其他错误,然后
jade
没有对这块异常进行处理。也就是说,如果
vist
的节点不是这种 block.子属性.孙子属性
问题应该不大。顶多一个 undefined
然后直接结束啥的,这里试试 visitCode
,正常走到后面,没有报错,期间只触发了
this.buf.push(code.val)
,问题应该不大。尝试污染
type
为 Code
好耶,像是解决了,因为这个报错和先知文章报错一致。
尝试解决这个报错,因为看错误调这里好像还没到渲染成功的地方。
分析这个错误栈,可以看到前 4 点还是属于
jade
范畴。先跟进 jade
模块最后报错的地方,即 jade/libindex.js
149行的 parse
方法,发现,程序已经走完刚刚AST遍历部分了,已经差不多要返回了。
感觉只要避免进入149行的
addWith
方法,就可以渲染成功了!然后进入149行的
addWith
方法是满足147行的 options.self
值为 False,尝试下看看这个 options.self
默认是不是 undefined
,如果是就可以污染了。nice,就是
undefined
,尝试污染 self
为 true
,1
也可以!!!终于走到渲染这一步了!
看到
undefined
好说,污染就完事了 (到这 index 的页面渲染就没啥问题了,到 第 2 个问题,
node.line
在某处为 undefined
才行,不然我们污染的 Object
对象就没意义这里如果一步一步动态调试会比较麻烦,直接注入测试即可
发现都
node
为 Block
的时候 line
是不存在的。理论上,只要覆盖了
node.line
即可达到代码执行的目的。然后拼接上面避免报错的参数,得到
反弹 Shell
再 POST一次,发送的数据包不用变
不就少了个
message
,那我污染你呗,,然后 message
后又说少了 error
继续污染,然后 error
是一个对象,就需要弄个 json
格式数据啦。emmm,到这,其实一开始就犯了个很蠢的错。。。其实一开始就应该污染
line
先的,然后在试 compileDebug
、type
、self
,蠢哭了,,因为,如果不污染 line 先的话,不能保证在 line 正确的情况下试其他的,就会导致,,其他对了,加上 line
就错了。。其实这里,压根就不用污染
title
,如果是 先污染 line
,然后到 compileDebug
、type
、self
会发现,到 self
这就可以成功反弹 shell 了,人都傻了。。。最终 EXP
不用变,在POST一次即可
原本想用原生Socket的,不知道为啥老是报错,奇怪,,难道闭合不了?
web343 原型链污染 +++++
说有过滤,但 web342一样的 EXP 也可以打,反弹shell后看源码才发现确实多了点东西
login.js
emmm 有点不懂这个
JSON.stringify(req.body).match(/Text/ig)
过滤有啥意义就是了。。和web342一样的EXP
web344 HPP
代码
即 url 中不能包含大小写
8c
、2c
和 逗号
先构造一个正常请求
发现题目会过滤掉逗号,尝试 URL 编码,
urlencode(",") = %2c
发现 2c
也被过滤HTTP协议中允许同名参数出现多次,不同服务端对同名参数处理都是不一样的,下面链接列举了一些
nodejs 会把同名参数以数组的形式存储,并且
JSON.parse
可以正常解析。因此构造
这里把 c进行url编码,是因为 双引号 的url编码是
%22
,和 c
连接起来就是 %22c
,会匹配到正则表达式。一些参考的题解