原创 | "白话"SQL注入漏洞(上)
对于SQL注入漏洞,笔者只想说,懂得都懂,不懂的仍然不懂,而随着认识的深入,越发觉得自己似乎也不是很懂,因此,本文并不打算向读者介绍更多更新的绕过手段,旨在为入门的网安伙伴提供一份更节约时间的参考资料,整个文章可能更像是一个知识框图,除了一些关键点,更多的还希望读者自己去学习。所以,还望各位师傅与前辈勿喷勿怪。
本篇为上篇,另有下篇。
0x01 本地搭建一个www服务器
1.搭建环境的零件
简单说一下用到的一些配件:
id | name | number |
---|---|---|
1 | VMware Workstation Pro | 1枚 |
2 | Linux系统镜像 | 1张 |
3 | Apache容器 | 1枚 |
4 | PHP解释器 | 1枚 |
5 | Mysql数据库 | 1个 |
6 | 搭好的dns服务器 | 1栋 |
7 | sqli-labs靶场 | 1个 |
8 | 客户端(win10物理主机+浏览器) | 1位 |
VMware Workstation Pro这个软件实现了在自己的win10操作系统上体验不同操作系统的功能,仅仅需要放不同镜像文件就行;
使用Linux镜像是为了在在Linux操作系统环境而不是win10环境下搭建www服务器,现实中Linux系统更多;
Apache容器则主要负责管理www服务,协调运作PHP解释器和MySQL数据库,换言之,可以简单的理解为正是有了它,一台主机才能变为一台www服务器;
PHP解释器就不用说了,它为运行PHP代码提供环境支持;
MySQL则负责将数据组织起来,简单点讲就是将杂七杂八的事情组织成一张张有条理的表,这样我们仅需读一下表格就能快速的找到我们想要的数据了;
sqil-labs则更不用说了,可谓是家喻户晓,一个聚合丰富的SQL注入漏洞的靶场(或简易的含SQL漏洞的网站)。
2.各零件的关系
如图:
有图有真相,整个思路还是要理一下的:
VmWare里布置两台Linux虚拟机,一台当作www服务器,一台当作dns服务器(也可以不布置,这里为了更仿真,布置了一下),而对于www服务器,其核心可以简单归结于Apache、php、Mysql的安装,也就是说,这仨软件得在www服务器上下载并安装(安装之后,别忘了把服务提起来),之后把下载到的sqli-labs靶场传到www服务器并安装即可搭建成功一个简单的本地www服务器。
具体答搭建,网上的教程很多,不再凑字了。
=>如果对www服务想知道更多,这里推荐阅读《鸟哥的私房菜-服务器架设篇》!
时间不是很充足的话,重点阅读一下www服务器部分就好。
在这里,我们将sqli-labs布置成一个网站,有自己的域名,可以通过本地搭建的DNS服务器解析到,并通过物理主机win10的浏览器访问到。
3.搭建好的效果
搭建好的一个效果,如下图所示一般:
直接访问域名,显示欢迎页面:
http://www.iisseeee.com/
sqli-labs.iisseeee.com
192.168.7.150
http://192.168.7.150/sqli-labs/
4.访问资源
一次访问的流程,如,图示:
这里我们可以看一下www服务器上的东西:
所以说,到底什么是服务器上的资源
凡是www服务器提供的可以访问的文件都可称为资源,例如,静态资源:图片,文本,视频,音频(.html文件也可归为静态资源,因为本质上来讲,.html文件就是附加了HTML标签格式普通文本文件,而所谓的HTML标签格式的作用可以简单理解为将普通文本美化一下后再展示给客户),动态资源:php文件(在PHP实现的web网站中,访问PHP文件时,PHP文件会根据自身定义的作用与功能去处理用户的访问请求,简单点来讲,相当于网站客服,自动化处理每个用户的请求)
www服务到底是什么
先说说什么是服务器。字面意思就是,能为其他人提供服务的计算机都可以被称为服务器。说白了,其本质还是一台计算机,只不过安装了一些特殊的软件实现了为其他计算机远程提供各种服务的功能,如www服务,dns服务等。
享受www服务的过程类似于我们从本地磁盘上找文件的过程,只不过,是找别人计算机上的文件。我们平常浏览网页,实际上就是享受了远程计算机提供的www服务,我们可以作这样的一个想像:我们可以很轻松的去浏览自己本地磁盘上的文件,而计算机网络就像是一个桥梁,它实现了我们可以浏览其他人计算机上磁盘上的文件的功能。同时,这给我们的一个启发就是,我们手上用的计算机也可以变为一台www服务器。
www服务器上的文件资源和我们本地文件资源其本质是一样的,但不同的是,www服务器上的文件不能随意访问(安全问题),www服务器上部署有脚本程序可以实现自动化处理我们的访问请求(常见的后台实现语言,如Java,PHP等)与自动化答复,像是半个机器人。有时候零散的数据太多,不方便网站后台的脚本程序去利用,这时候还要引入数据库,而数据库实际上就是将各种数据进行有条理组织的软件。
为了用户获得的数据看的更方便或具有吸引力,一般前端都要通过HTML语言进行排版与美化,不能让浏览器获得的数据“黏到一起”(浏览器会自动解析HTML语言,只会将最后美化后的数据展示给用户,),除此之外,要部署好www服务,还要安装专门的web容器,有了它,才能更加方便的实现将后端语言、数据库、文件资源等协调在一起,如,Apache。
当然不同的操作系统也存在不同的差异(windows或Linux版),但大体上相同。一种常见的部署www服务的网站架构为LAMP(Linux品台+Apache容器+MySQL数据库+PHP后端处理语言)。
总而言之
浏览网页的过程就是体验www服务的过程,也是体验访问别人电脑文件的过程,但至于请求的处理与请求的答复如何,则由提供www服务的计算机的后天实现程序说了算(可能是Java,PHP,python)。
概念的东西总是很枯燥,难受的小伙伴,跳过这里,先接着往下看吧!
最后,谈一下http协议
在计算机传输信息的过程中,有着各种各样的数据,而不同种类的数据传输需要不同的网络协议协助,协议可以简单理解为:为不同的在网络上的传输的数据规定不同的格式或排版(形象点讲就是不同的协议对应不同信封的报文,享受不同的服务就需要使用不同的信封去传递消息),而www服务的数据传输需要用到的则是HTTP(s)协议。
=>这里推荐阅读《图解HTTP》一书,简单且有趣!
0x02 数据的传递与交互
继续拿sqli-labs靶场举例,这个靶场搭建好后,会在Mysql数据库里新产生一个名为security的数据库,该数据库下有几张重要的表,表里有一些重要的数据。
1.初始化靶场
点击重置数据库这个过程,实际就是通过http协议传消息给www服务器的sqli-labs靶场,通知它可以初始化了,之后,sqli-labs靶场下的用PHP写的程序就会自动的去调用写好的SQL语句去发送给数据库MySQL,之后MySQL建库,建表,建数据,并把执行成功或不成功的消息通知给PHP实现的程序,之后PHP程序再发给客户端,即,传回浏览器。
去MySQL数据库软件中瞅瞅,可以看到,有许多个数据库,并产生了一个名为security的数据库:
数据库security下有几张表格:
随意看一张表里的东西,没啥新奇的,就是一些整整齐齐的数据:
2.关卡1,输入个?id=1
原始界面:
输个1后返回的界面:
可以看到,页面返回了Dump,Dump这样的信息,这个信息与数据库security下的user表里的数据是相对应的:
接着,多来几个,看看效果:
3.抓个http的包
ok,接下来,来个具体的流程,在哪之前,得抓个包。不用问,问就是 Burp suite,拦一下http的数据包,再发出去,(亦被称作代理)。
=>对Burp suit 不是很熟的话,可以阅读一下《Burp Suite 实战指南》
https://t0data.gitbooks.io/burpsuite/content
当然,个人觉得,工具这种东西,找一下视频教程,照葫芦画瓢,来一遍,可能更快更有效。
还是在,关卡1,传个?id=1
:
抓到的包:
被www服务器下的
/var/www/html/sqli-labs/less-1/index.php
接收:
4.后台是怎么写的
index.php是这样的,PHP代码和HTML混合:
(这也是为什么要分层设计的原因,看着也很别扭)
<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">
//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);
if($row)
{
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}
}
else { echo "Please input the ID as parameter with numeric value"."<br>";
}
</font> </div></br></br></br><center>
<img src="../images/Less-1.jpg" /></center>
</body>
</html>
精简一下,对index.php做简要分析:
sql-connect.php
db-creads.inc,mysql的账户名,账户密码,url,具体的数据库。
=>对PHP语言不是很熟的话,这里推荐黑马程序员的《PHP基础案例教程》
如果时间不是很充裕,重点关注PHP语言是如何连接数据库并接受或发送数据这一部分的东西。
简而言之,我们通过get方式向sqli-lab发起了一个http请求,然后sqli-labs的对应PHP程序去处理了我们的请求(这里是index.php),然后该PHP程序接受了我们的get方式传过来的值,建立与MySQL的连接,将接受的值拼接到早都建立好的SQL语句上,发给mysql数据库管理程序,MySQL接受后,查了一下security的user表下id=1的一行数据并将结果以数组的形式存起来发回给对应的PHP程序,PHP程序接受后处理一下结果就发回给客户,即发回浏览器。
****数据库软件MySQL就是用来管理数据的,但只认自己的SQL语言,只有对应的正确的SQL语句才可以被执行,刚刚也看到了,数据被组织成了表,因此,学习SQL语言重要的就是学习表的的增删查改。
5.结合代码对比一下响应结果
请求报文:
响应报文:
返回的页面:
F12查看一下页面源码:
index.php中的HTML代码
<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">
//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);
if($row)
{
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}
}
else { echo "Please input the ID as parameter with numeric value"."<br>";
}
</font> </div></br></br></br><center>
<img src="../images/Less-1.jpg" /></center>
</body>
</html>
可以发现,只有HTML部分被发了回来,这就验证了我们前面提到的,HTML标签语言说白了就是排版用的,而动态数据则是由后台的脚本语言产生的。
=>这里推荐两个学习网站,补充点芝士。
https://www.w3school.com.cn/
https://www.runoob.com/
6.post请求
刚刚有提到,http协议中最常用的就是GET和POST请求方式。
理解了GET,POST就so easy 了,区别就在于一个在报文头部,一个报文消息体部分,我们对刚刚的代码进行轻微的修改,来看下这个过程。
修改后的Less-1/index.php:
<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">
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
error_reporting(0);
// take the variables
if(isset($_POST['id']))
{
$id=$_POST['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";
//echo 'payload:'.$sql.'<br/>';
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}
}
else { echo "Please input the ID as parameter with numeric value"."<br>".
'<form method="post" action="/Less-1/" >
< input type="text" name="id">
< input type="submit" name="submit" value="提交"/>
</form>
';
}
</font> </div></br></br></br><center>
<img src="../images/Less-1.jpg" /></center>
</body>
</html>
实际上,仅修改了2个地方:
来看下效果:
提交个1,试试:
F12查看一下网页源码:
提交了个id=1,数据跑下面了:
接着到 Less-1/index.php后还是刚刚的流程,仅仅是变成了post方式接收参数:
接着还是熟悉的响应包,熟悉的味道:
0x03 sql注入初步探索
1.SQL注入定义
对于SQL注入漏洞,一句话便可以概括出它的成因,即:用户传递到3w服务器的参数动态拼接到后台的SQL语句上时后台程序并未作安全处理。
概念这种东西总是抽象的,接下来,我们来仔细聊聊这个过程。
2.?
分隔
从SQL注入的角度来看,一段url(网址)可简单的分为资源路径部分和请求参数部分,以?
分隔。
客户端提交请求的方式常用的无非就是GET和POST两种,
在http报文中的区别可以简单的理解为一个在报文顶部,一个在尾部:
接着,后台的脚本程序通过$_GET或$_POST
超全局变量以数组的方式存错用户的传参,因为是数组,所以可以通过索引去引用,并赋值给新变量:
之后,将获得的参数拼接到SQL语句上,并发给数据库执行,数据库执行后再把结果发回来:
因为sqli-labs默认连接的是数据库security,所以,我们可以到这里去瞧瞧:
因此,$row接收到的MySQL传回的值就是这样的一个数组:
$row['id']=1;$row['username']='Dump';$row['password']='Dump'
最后将结果输出后拼接到HTML页面中返回给浏览器,浏览器渲染一下HTML代码,排个版,将界面展示给用户:
整个过程归纳完就是以下的一个简图:
3.闭合与注释是关键
根据上面的流程,我们可以知道,构造出SQL语句并让数据库执行是整个流程的关键,既然我们可以控制SQL语句拼接前的一部分$id
,那么可不可借此构造出任意的SQL语句并让数据库去执行呢?--答案是肯定的,关键技巧就在于引号的闭合和注释符。
正常的来个?id=5
:
来个简单的SQL注入的payload:
?id=' union select * from users where id=5%23
虽然结果相同,但却有本质的区别:
第一个传参最后拼接后的SQL语句为:
SELECT * FROM users WHERE id='1' LIMIT 0,1
第二个传参最后拼接后的SQL语句为:
SELECT * FROM users WHERE id='' union select * from users where id=5%23' LIMIT 0,1
加个引号,负责闭合后台的SQL语句中的引号,
加个union关键字是为了构造我们自定义的SQL语句,%23是url编码后的#,在MySQL中代表注释符,因为后面的引号被注释了,以此,前面要补个,形成闭合。
这个sql语句的意思是在user表里查id=‘’的数据和在user表里查id=‘5’的数据,之后将结果合并后返回。也就是说,因为有了union关键字,我们可以让数据库一次执行多个SQL语句,这为SQL注入攻击提供了方便。
相当于这个过程
类比的猜测一下,这是不是意味着,我们可以构造任意的SQL语句对数据库进行任意的操作。
至此,我们简单的归纳一下对sql注入攻击的认识
数据库里以表格的形式存储了许多重要的数据,且操作数据库需要用到SQL语言,sqli-labs的后台程序里有已经一半实现好的SQL语句,只要把从用户哪里传来的数据接收后拼接到SQL语句上就能构造出完整的SQL语句让数据库执行,如果用户可以自由的控制构造SQL语句的过程,就会造成SQL注入攻击,比如说,通过引号闭合,通过union关键字附加自己的SQL语句,通过注#注释掉多余的SQL语句部分等等。
另外,可以看出,SQL语言在整个SQL注入中至关重要,因此,这部分的东西还得多少有点存货,起码要知道对数据库的增删查改。