【如需转载,请详细表明来源,请勿设置原创】
嗨,大家好,我是闪石星曜CyberSecurity创始人Power7089。
欢迎大家扫描下方二维码关注 “闪石星曜CyberSecurity” 公众号,这里专注分享渗透测试,Java代码审计,PHP代码审计等内容,都是非常干的干货哦。
今天为大家带来PHP代码审计基础系列文章第一篇之SQL注入篇 。
这是【炼石计划@PHP代码审计】知识星球第二阶段的原创基础系列文章,拿出部分课程分享给零基础的朋友学习。本系列原创基础文章涵盖了PHP代码审计中常见的十余种WEB漏洞,是匹夫老师精心的创作,欢迎关注我的公众号跟着一起学习。
【炼石计划@PHP代码审计】是一个系统化从入门到提升学习PHP代码审计的成长型知识星球。这里不仅注重夯实基础,更加专注实战进阶。强烈推荐加入我们,一起来实战提升PHP代码审计。
PHP代码审计之SQL注入 1.SQL注入原理 SQL注入就是攻击者通过把恶意的SQL语句插入到Web表单的输入页面中,且插入的恶意语句会导致原有的SQL语句发生改变,从而达到攻击者的目的去让它执行一些危险的数据操作,进一步欺骗服务器去执行一些非本意的操作。简单来讲,所有可以涉及到数据库增删改查的系统功能点都有可能存在SQL注入漏洞 。
一个简单的攻击原理举例:
在SQL注入中,我们更应该关注的是业务逻辑,例如业务中可能设计到的增删改查操作,下面通过一个简单的代码小demo来进行SQL注入的演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SQL注入小demo</title> </head> <body> 用户名: <form method="GET"> //form表单通过get方式接收数据 <input type="text" name="name" size="50" /> //通过name参数接收数据 <br> <input type="submit" value="点击查询" style="margin-top:50px;"> <?php $db = mysqli_connect("localhost","root","123456","demo"); //建立mysql连接 if(!$db){ echo "数据库链接失败!"; exit(); } $name = $_GET['name']; //通过前端设置的name参数将数据接收到后端进行处理 $sql = "select * from user where name = '".$name."';"; echo " 输出当前执行的SQL主句:".$sql.""; $result = mysqli_query($db,$sql); //执行SQL语句 while($row=mysqli_fetch_array($result)) //取出一行数据的所有内容以数组的形式返回 { echo "账号:".$row['user'].""; //输出一行数据的user字段值 echo "密码:".$row['passwd'].""; //输出一行数据的passwd字段值 } mysqli_close($db); //关闭mysql连接 ?> </form> </body> </html>
2.1 PHP MySQLi基本函数 PHP MySQLi 函数允许访问 MySQL 数据库服务器。 通俗来说MySQLi 下的函数用来处理PHP中关于数据增删改查的操作。
mysqli_connect()
mysqli_query()
mysqli_fetch_array()
mysqli_close()
为了方便演示实际的业务流程,我们创建demo
数据库,并且创建user
表,在表中插入演示数据。
这里简单演示了一下通过查询来获取数据的操作原理。
1.2 问题引申: 我们知道某些业务功能会涉及到数据库交互,如上所示,我们是不是可以通过正常功能插入恶意SQL语句来实现我们想要达到的目的呢?
例如这样:
1 1' union select 1,2,user(),database();#
2.SQL注入分类 SQL注入大致分为以下五大类,下面我们通过实际的PHP代码进行分析。
2.1 报错注入 2.2.1 示例代码 通过传入id
参数且传入的id
参数未做过滤直接拼接到SQL语句中执行 ,直接使用mysqli_error()
进行报错处理没有对报错进行良好的错误处理,所以导致报错注入的产生。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php if (isset ($_GET ['id' ])) { header ("Content-type:text/html;charset=utf-8" ); $db = mysqli_connect ("localhost:3307" ,"root" ,"123456" ,"demo" ); if (!$db ){ echo "数据库链接失败!" ; exit (); } $id =$_GET ['id' ]; $sql ="SELECT * FROM user WHERE id='$id ' LIMIT 0,1" ; $result =mysqli_query ($db ,$sql ); $row = @mysqli_fetch_array ($result ); if ($row ) { echo 'Your Login name:' . $row ['user' ]; echo "<br>" ; echo 'Your Password:' .$row ['passwd' ]; } else { echo mysqli_error ($db ); } mysqli_close ($db ); }?>
2.1.2 PHP函数介绍 mysqli_error()
2.1.3 测试 当正常查询id=1
时输出user
以及passwd
字段的值php
、123456
。
当输入id=1'
,mysqli_error()捕获错误信息,并将SQL语句报错。
我们闭合SQL语句中的单引号即可执行我们想要的查询的内容
测试语句:
1 1' and (select extractvalue("anything",concat('~',( user()))))%20--%20dsddsa
2.2 宽字节注入 2.2.1 代码示例 mysql中有一个特性,由于gbk
是多字节编码,两个字节代表一个汉字,所以%df
和后面的转义字符\
也就是%5c
会组成了一个汉字運
,而'
就逃逸了出来造成SQL注入。在审计SQL注入时,要着重注意MySQL中设置的字符编码,如果为gbk,那么很可能存在宽字节注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?php if (isset ($_GET ['id' ])) { header ("Content-type:text/html;charset=utf-8" ); $db = mysqli_connect ("localhost:3307" ,"root" ,"123456" ,"demo" ); mysqli_query ($db ,"SET NAMES 'gbk'" ); if (!$db ){ echo "数据库链接失败!" ; exit (); } $id =addslashes ($_GET ['id' ]); $sql ="SELECT * FROM user WHERE id='$id ' LIMIT 0,1" ; echo "输出SQL语句:" .$sql ."<br><br>" ; $result =mysqli_query ($db ,$sql ); $row = @mysqli_fetch_array ($result ); if ($row ) { echo 'Your Login name:' . $row ['user' ]; echo "<br>" ; echo 'Your Password:' .$row ['passwd' ]; } else { echo mysqli_error ($db ); } mysqli_close ($db ); }?>
2.2.2 PHP函数介绍 addslashes()
2.2.3 测试 我们发现输入'
时,'
被addslashes()转义。
我们使用%df
和前面的\
闭合也就是%5c
,然后我们的%27也就是'
直接逃逸出来,这时我们就能构造自己想要执行的SQL语句,造成SQL注入。
测试语句:
1 1%df%27 and extractvalue(1,concat(0x7e,(select user()),0x7e)) -- qwe
2.3 盲注 在盲注中不管是布尔盲注还是时间型盲注,页面显示不会有明显的变化,代码审计中只能通过查看源代码闭合原有的SQL语句,然后利用MySQL中延时的方式来判断是否存在SQL注入。
2.3.1 代码示例 这里为了进行盲注的演示,在代码中特意加入了一条if判断,当mysqli_fetch_array()
查询的结果集为NULL
的时候,返回id=1的数据内容,这就让我们无法判断插入的SQL语句是否执行成功,只能通过延时来进行测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?php if (isset ($_GET ['id' ])) { header ("Content-type:text/html;charset=utf-8" ); $db = mysqli_connect ("localhost:3307" ,"root" ,"123456" ,"demo" ); if (!$db ){ echo "数据库链接失败!" ; exit (); } $id =$_GET ['id' ]; $sql ="SELECT * FROM user WHERE id='$id ' LIMIT 0,1" ; $result =mysqli_query ($db ,$sql ); $row = @mysqli_fetch_array ($result ); if (!$row ) { $sql ="SELECT * FROM user WHERE id='1' LIMIT 0,1" ; $result =mysqli_query ($db ,$sql ); $row = @mysqli_fetch_array ($result ); } if ($row ) { echo 'Your Login name:' . $row ['user' ]; echo "<br>" ; echo 'Your Password:' .$row ['passwd' ]; } mysqli_close ($db ); }?>
2.3.2 测试 当我们查询数据库中存在的数据时返回指定字段的内容
当我们查询数据库中不存在的数据时,由于mysqli_fetch_array()
查询结果为NULL
,所以返回id=1的指定字段内容
由于我们在代码审计时可以一目了然看到SQL语句的构造情况,所以我们只需闭合相应的特殊字符,通过sleep()函数使页面延时,从而判断此处是否存在注入点,进而进行下一步的注入。
测试语句:
1 2312'%20or%20sleep(5)%20--%20qwe
2.4 二次注入 2.4.1 代码示例 二次注入的逻辑是在第一次insert
或update
操作时代码存在转义而无法造成注入,但数据在被插入数据库时转义会消失,正常的SQL语句将会被存储到数据库中,在第二次调用插入的数据时被还原的SQL语句将被拼接到正常的SQL语句中造成二次注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php if (isset ($_GET ['user' ])) { header ("Content-type:text/html;charset=utf-8" ); $db = mysqli_connect ("localhost:3307" ,"root" ,"123456" ,"demo" ); if (!$db ){ echo "数据库链接失败!" ; exit (); } $user =addslashes ($_GET ['user' ]); $sql ="update user set user='$user ' where id=1" ; echo "输出第一次的SQL语句:" .$sql ."<br><br>" ; $result =mysqli_query ($db ,$sql ); $sql = "select * from user where id=1" ; $result =mysqli_query ($db ,$sql ); $row = @mysqli_fetch_array ($result ); $sql = "select * from user where user = '$row [2]'" ; echo "输出第二次查询的SQL语句:" .$sql ."<br><br>" ; $result =mysqli_query ($db ,$sql ); var_dump (mysqli_fetch_row ($result )); mysqli_close ($db ); }?>
2.4.2 PHP函数介绍 mysqli_fetch_row()
2.4.3 测试 输入'
时,'
被addslashes()
转义,这里无法造成注入
但在数据库中存储时转义\
被清除
但在第二次调用时转义后的SQL语句被拼接在正常的SQL语句中,造成二次注入。
测试语句:
1 ' union select 1,2,database(),3 -- qwe
3.SQL注入代码审计总结 1 在审计SQL注入的时候,我们首先要找到对应的传参点,更要多关注代码中正常SQL语句的规则和过滤情况,最重要的是要闭合正常的SQL语句来,最终执行我们想要执行的SQL语句。
ps: Navicat 15安装包以及破解方式同教程一起下发,大家可以根据教程进行破解使用。