1、搭建环境
1.1、配置
- 在sql-connections下的db-creds.inc中将小皮面板中的密码填入


2、必备Sql语句
2.1、基本语句
#创建数据库
create database test1;
#展示所有数据库
show databases;
#使用test1数据库
use test1;
#查看当前数据库中的表
show tables;
#获取当前使用的数据库
select database();
#删除数据表
drop table table1;
#查询当前的用户
select user();
#获取当前数据库版本
select version();
#查看schemata这个表的结构
DESC schemata;
2.2、常用关键字
- order by
-
给查询结果排序,指定一个或多个字段,指定升序 ASC / 降序 DESC
-
常用于通过报错临界点测试出该表有多少列
#按SELECT出来的id列进行排序,ASC=升序(默认),DESC=降序
select * from member order by id;
#按SELECT出来的第4列进行排序
select * from member order by 4;
- union
-
常用于联合查询,左右两个表列数一致
select id,username from users union select id,content from message;
2.3、常用函数
-
截断
select substr('root',1,1); #执行结果------r
select substring('root',2,2);#执行结果------oo
#以上两个函数作用类似
select left('root',2);#执行结果------ro
select right('root',2);#执行结果------ot
select mid('root',1,1); #执行结果------r -
字符转换
ASCII函数用来返回给定字符串的第一个字符的ASCII码
select ascii('root'); #执行结果------114
#将 ASCII码转换成字符
select char(65);#执行结果------A -
获取数据长度
select length(database());
-
时间
now()#返回当前的日期和时间
sysdate()#返回当前的日期和时间
3、预编译与sql注入
1. 为什么预编译可以防止SQL注入?
-
原因:预编译将SQL语句与参数分开处理,语句先编译成固定结构,参数再传入,不再视为SQL代码的一部分。
-
效果:消除了语句的歧义,防止用户输入被解释为SQL指令,从而阻断注入。
2. 预编译能完美防御SQL注入吗?
-
不能完全防御 ,因为预编译只能保护可参数化的位置(如WHERE条件中的值)。
-
不可参数化的位置(如表名、列名、ORDER BY、GROUP BY、LIMIT、JOIN等)仍可能被注入。
3. 哪些位置不可参数化?
-
表名、列名
-
ORDER BY、GROUP BY
-
LIMIT
-
JOIN
-
其他SQL关键字或结构
4. 为什么这些位置不可参数化?
-
预编译通常自动为字符串参数添加引号,而这些位置(如字段名)不能带引号,否则会引发语法错误。
-
例如:
ORDER BY 'username'是无效的,正确应为ORDER BY username。
5. 预编译的两种类型:
-
虚假预编译(模拟预编译):
-
客户端模拟参数绑定,实际是字符串转义和拼接。
-
仍可能被宽字节注入等绕过。
-
-
真实预编译:
-
数据库层面编译语句,参数以二进制形式传递。
-
安全性更高,但仍不能保护不可参数化位置。
-
6. 宽字节注入与预编译:
-
在虚假预编译下,如果数据库与客户端编码不一致(如GBK),可能通过宽字节绕过转义。
-
真实预编译下,参数以十六进制传递,不易被绕过。
7. ORDER BY 注入示例:
-
由于ORDER BY不能参数化,攻击者可注入条件如:
ORDER BY rand(ascii(substr(database(),1,1))>96)
8. 预编译的局限性总结:
-
设计初衷是提升性能(减少语法树重复构建),而非专门防注入。
-
仅适用于值参数,不适用于SQL结构部分。
-
在不可参数化位置仍需依赖其他防护措施(如白名单、严格过滤)。
9. 防御建议:
-
使用真实预编译(如设置
PDO::ATTR_EMULATE_PREPARES = false)。 -
对不可参数化的位置进行白名单验证(如固定选项、映射表)。
-
避免直接拼接用户输入到SQL结构部分。
-
结合WAF、输入验证、编码统一等多层防护。
10. 面试常见问题:
- SQL注入类型(盲注、宽字节、二次注入等)
1. 盲注(Blind SQL Injection)
-
布尔盲注:通过页面返回的True/False状态推断数据
- 如:
id=1' and ascii(substr(database(),1,1))>97--+
- 如:
-
时间盲注:通过页面响应时间推断数据
- 如:
id=1' and if(ascii(substr(database(),1,1))>97,sleep(5),0)--+
- 如:
2. 宽字节注入
-
条件 :数据库使用GBK等宽字符集,且PHP使用
addslashes()等转义 -
原理 :
%df%27→%df%5c%27→運'(吃掉转义符\) -
示例 :
id=%df%27 union select 1,2,3--+
3. 二次注入
-
过程:
-
输入被转义存储到数据库(如
admin'--) -
从数据库取出时转义符被移除
-
在另一处使用该数据时触发注入
-
-
特点:绕过前端过滤,危害更大
4. 其他类型
-
联合查询注入 :
union select -
报错注入 :利用
extractvalue()、updatexml()等 -
堆叠注入:执行多条SQL语句(需支持如PDO)
-
Cookie注入 、UA注入 、Referer注入
- 手工注入经验
基本步骤:
-
判断注入点 :
'、"、)、and 1=1、and 1=2 -
判断字段数 :
order by n -
判断回显位 :
union select 1,2,3,...,n -
获取数据库信息:
database()、user()、version()
-
查表名:
union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()
-
查列名:
union select 1,group_concat(column_name) from information_schema.columns where table_name='users' -
脱数据:
union select username,password from users limit 0,1
盲注手工技巧:
-
二分法加快判断:
ascii(substr(password,1,1))>100 -
使用
length()判断长度 -
注意
substr()和mid()的用法