SQL注入是Web安全层面最高危的漏洞之一,长期霸榜OWASPTop10首位,但是究竟什么事SQL注入?SQL注入又是怎么产生的?接下来本篇文章将详细介绍SQL注入产生的原理。本篇文章并没有描述具体的注入方法,而是侧重于对原理的描述,并分别分析了GET和POST注入。
在弄清SQL注入的基本原理之前,我们首先要来搭建实验的环境,我们需要一台L(Linux)NMP服务器或者W(Windows)NMP服务器,或者直接使用phpStudy完成搭建。 完成服务器的搭建后,我们要准备好测试用的数据库,执行下面的SQL命令完成数据库的建立
create database SQLInjection; use SQLInjection create table users(id int,username varchar(255),password varchar(255));
创建完成后,可以插入几条用于测试的数据
insert into users value(1,'Tom','123456'); insert into users value(2,'Alice','654321'); insert into users value(3,'Jerry','qwerty');
HTTP协议是Web应用层协议,全称是超文本传输协议。这个协议是Web应用的核心,HTTP由两个程序实现,一个是客户端程序,通常是我们使用的浏览器,一个是服务器程序,运行在我们所访问俄的服务器上。这两个程序通过HTTP协议进行交流,HTTP定义了这些报文的语法、语义和时序。这里我们并不需要对HTTP协议做过深的了解。但是我们依旧需要明确以下几个知识点:
1.客户端程序与服务器程序之间是通过报文进行交流的,通常是客户端请求数据,服务器响应客户端请求的数据; 2.报文是包含了不同的字段,不同的字段代表的不同的信息 3.报文中携带了客户端程序与服务器程序之间通信的数据 4.数据在报文中的位置不同,服务器接收该数据的方式也不同
客户端与服务器端的报文,根据发送方的不同被分为请求报文和响应报文,从客户端发往服务器端的被称为请求报文,从服务器端发往客户端的被称为响应报文。请求报文和响应报文都有不同的固定格式,请求报文的一般报文格式如下
响应报文的一般格式如下
因为本文主要主要讲述的是SQL注入,SQL注入我们是无法操作服务器的,只能通过影响从客户端发送的请求报文进而从服务器获取数据,所以我们只需要了解请求报文,在上面的请求报文结构图中,我们可以看到请求报文主要包含请求行、首部行、空行、实体体4个模块。各个模块的功能说明如下
在前面的内容中不断的提到数据的传递,数据传递的方法会在请求行的方法中说明,主要有4种数据传递的方法,这里只介绍两种与SQL注入有关的
当请求报文的方法字段被设置为GET的时候,表明该请求报文使用GET方式进行传参。通常GET方法是用来获取服务器上的指定文件,比如
GET /index.php HTTP/1.1
上面的请求行用来获取网站根目录下的index.php文件,当站点接收到该请求后会将用户请求的文件内容进行解析,然后通过响应报文响应用户的请求。但是也会遇到某些文件需要接收参数的情况,客户端程序会使用下面的方式对文件完成传参
GET /index.php?id=1 HTTP/1.1
这样服务器上的文件便能够接受id=1的传参。然后根据用户的传参作出响应的处理。
当请求报文的方法字段被设置为POST的时候,表明该请求报文使用POST方式进行传参。通常POST方式用来提交用户输入的表单信息,这些协议存放在请求报文的实体体中。比如
POST /user/login.html HTTP/1.1 //...... user=admin&pass=admin
上面是一个用户登录报文的示例,通过POST方式提交用户输入的用户名和密码。
上面所说到的报文或者传参方式都是客户端程序生成的,作为用户只需要在页面上点击鼠标或者输入内容即可。那么客户端程序是怎么确定要使用什么样的方式传参呢?简单来说我们在表格中输入的数据会通过POST进行传参,而我们的点击页面的操作一般会通过GET传参。 POST传参的方式比较好理解,但是我们还要重点说明一下GET方式,比如当我们浏览某个站点的时候,点击了站点中的一篇文件,页面就给我们显示了我们点击的文章。现在我从数据包的角度分析站点为什么可以准确的显示我们所点击的文章。首先我们点击文章,服务端会发送类似于下面的数据包
GET /index.php?p=1234 HTTP/1.1 Host: www.test.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html, */*; q=0.01 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate X-Requested-With: XMLHttpRequest Connection: close Referer: http://www.test.com
可以看到由于是GET方法,所以报文的实体体为空。Host字段表示站点的域名。通过域名可以找到该站点的服务器,然后将报文传递到该站点,站点接收报文后发现用户请求的是/index.php文件,并且有传参p=1234,站点便会在根目录下寻找index.php文件,执行index.php文件并完成传参,执行的结果就是我们请求的文章,然后将文章发送给我们。我们会发现当页面显示文章后,浏览器的的URL栏变成了
可以看到URL栏会显示我们通过GET方式传递的参数p=1234。其实我们通过GET方式对文件传递的参数都会显示在URL栏中,我们可以直接修改URL栏中的内容为
http://www.test.com/?p=1234
或者
http://www.test.com/index.php?p=1234
都会发送同样的数据包,显示同样的内容。也就是说我们可以通过修改URL栏中的内容控制传参的内容。 补充 :因为站点会默认请求index.php或者index.html文件所以在请求index.php文件的时候可以不写index.php文件,站点就会直接请求index.php文件。
说到这里我们才正式进入SQL注入的原理分析,但是看完上面的内容我们已经完成一大半了,接下来就是将上面的知识应用到SQL注入中。这里仅仅介绍两种典型的注入GET和POST。SQL注入的方式有很多但是基本原理都差不多的。 站点存在SQL注入必须满足下面的三个条件
1.用户能够控制输入 2.待执行的代码拼接了用户输入的数据 3.执行了待执行的代码
首先我们来模拟一下上传的传参方式,首先在我们的LNMP或者WNMP服务器(下称“服务器”)的网站的某个你喜欢的目录下放置下面的脚本 test.php
<?php echo 'hello,world!'; $id=$_GET['id']; #接收GET传参 echo $id; ?>
这里将该脚本放置在SQLInjection目录下,随后我们在URL栏输入路径访问该文件,页面显示效果如下
页面成功的显示了hello,world!,接着我们需改URL栏为
http://192.168.25.147/SQLInjection/test.php?id=1234
并抓取浏览器发送的数据包,数据包内容如下
GET /SQLInjection/test.php?id=1234 HTTP/1.1 Host: 192.168.25.147 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: PHPSESSID=0871f074b43a28eb3e15dcf7b0635b6d Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0
可以看到该数据包使用GET方式进行传参,并在URL字段指定了文件的路径和我们对文件的传参。同时返回的页面显示了我们在URL栏中传递的参数
上面的操作也就验证了通过URL栏的方式传递参数是GET方式的传参。并且测试了我们的文件可以接受GET传参的内容。
将上面的代码修改为
echo 'hello,world!'.'<hr/>'; #1.获取get传参 $id=$_GET['id']; echo '你传入的参数'.$id.'<hr/>'; #2.链接数据库 $conn=mysqli_connect('localhost','root','root','sqlinjection'); if(!$conn) die("<script>alert('Sorry,cannot connect to database')</script>"); #3.构造SQL命令 $sql="select * from users where id=$id"; echo '执行的SQL语句'.$sql.'<hr/>'; #4.查询数据库 $result=mysqli_query($conn,$sql); #5.显示查询结果 echo '查询结果'.'<br/>'; while($row=mysqli_fetch_array($result)) { echo 'id:'.$row['id'].';'.'username:'.$row['username'].';'.'password:'.$row['password'].'<br/>'; } #6.断开链接 mysqli_close($conn);
代码修改完成后,我们接着访问该代码,然后传入参数id=1,页面显示如下
可以看到站点成功的接收了我们的传参,并根据我们的传参访问数据库,并显示了数据库的访问结果。这是正常情况下的传参,可以理解为我们登录用户后,页面通过GET传参的方式从数据库中取出我们的用户信息显示在页面上。因为我们可以控制传参的内容,那我们尝试将URL栏修改为
http://192.168.25.147/sqlinjection/test.php?id=1 union select * from users where id=2
站点显示的内容变成了
也就是说我们修改URL栏中的内容将id的值变成了
id=1 union select * from users where id=2
站点在接收到该值后,将该变量的值与SQL命令进行拼接,形成了如下的SQL命令
select * from users where id=1 union select * from users where id=2
随后执行了该SQL命令,显示了预期之外的数据。在正常操作下id接收的是一个数字,然后将该数字拼接到SQL命令中执行。但是我们通过人为修了id传参的值,也就是说我们可以控制id的值,并将其修改为一个恶意的值;接着系统会将我们输入的内容拼接道SQL命令中,即待执行的代码拼接了用户输入的内容;最后拼接了用户输入内容的代码执行了,获取了数据库中的数据。
理解了前面的GET注入的原理,POST注入的原理和GET完全一致,不同点在于传参方式的不同。
准备了如下的程序将其放置在sqlinjection目录下 login.html
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form action="login.php" method="POST" target="_self"> username:<input type="text" name="username"/><br /> password:<input type="password" name="password"/><br /> <input type="submit" value="login"><hr /> </form> </body> </html>
login.php
<?php #1.从前端获取数据 $username=$_POST["username"]; $password=$_POST["password"]; echo '你输入了如下的内容'.'<br/>'; echo 'username:'.$username.'<br/>'; echo 'password:'.$password.'<hr/>'; ?>
随后我们访问login.html,页面显示如下
输入如下的内容后
点击login并抓取数据包,得到的数据包内容如下
POST /sqlinjection/login.php HTTP/1.1 Host: 192.168.25.147 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 28 Origin: http://192.168.25.147 Connection: close Referer: http://192.168.25.147/sqlinjection/login.html Cookie: PHPSESSID=0871f074b43a28eb3e15dcf7b0635b6d Upgrade-Insecure-Requests: 1 username=Tom&password=123456
可以看到数据包使用的是POST的方式,并且在实体体中携带了我们输入的用户名和密码信息。随后页面会显示下面的内容
可以看到我们在表单是输入的内容是使用POST方式携带在实体体中进行传参的。同时login.php能够成功的接收我们传入的数据
将login.php脚本修改为
<?php #1.接收用户传参 $username=$_POST["username"]; $password=$_POST["password"]; echo '你输入了如下的内容'.'<br/>'; echo 'username:'.$username.'<br/>'; echo 'password:'.$password.'<hr/>'; #2.链接数据库 $conn=mysqli_connect('localhost','root','123456','sqlinjection'); if(!$conn) die("<script>alert('Sorry,cannot connect to database,please login later!')</script>"); #3.构造查询语句 $sql="select * from users where username='$username' and password='$password'"; echo '执行的SQL命令为:'.$sql.'<hr/>'; #4.进行查询并获得结果 $result=mysqli_query($conn,$sql); $row=mysqli_fetch_array($result); if(isset($row)) { echo '---登录成功---'.'<hr/>;'; }else{ echo '---登录失败---'.'<hr/>'; } ?>
当我们输入正确的用户名和密码的时候页面会显示
如果输入的用户名或者密码是错误的时候,页面会显示
也就是说站点能够正确的验证用户名和密码的正确情况,但是我们尝试输入下面的内容
我们发现居然也登录成功了
仔细观察我们输入的内容拼成的SQL语句
select * from users where username='Tom' and password='' or 1=1 or ''
在执行select的时候判断条件有4个,分别是
条件1.username='Tom' 条件2.password='' 条件3.1=1 条件4.''
其中条件1与条件2使用and链接,其他的条件使用or链接,当程序从左往右判断条件成立的时候,执行到条件1的时候能找到使条件成立的记录,虽然执行到条件2的时候发现不成立,但是因为后面使用了or连接了一个恒成立的式子1=1,因此重要1=1存在其他的条件就算不成立也可以查找到对应的记录。所以在这里即使我们不是输入密码也可能成功的登录。这也是常用的万能密码。 以上便是POST注入的基本原理,可以看到和GET注入类似。当我们输入用户名的时候可以输入任意的内容,也就是说用户可以控制输入。当程序带着我们输入的内容查找数据库的时候,也就是拼接我们输入的内容并执行。
究竟什么是SQL注入?介绍到这里,更多sql学习请参考编程字典sql教程 和问答部分,谢谢大家对编程字典的支持。
原文链接:https://blog.csdn.net/m0_46898243/article/details/106211227?ops_request_misc=&request_id=&biz_id=102&utm_term=sql&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~sobaiduweb~default-2-106211227.nonecase&spm=1018.2226.3001.4450