AWD代码审计(一)

前言

之前周六参加了一下天汉的AWD。一开始第一个靶机有点bug,一直连不上,后来有事就离开了,现在再来复现一下。

问师傅要了一下WP,然后在自己的VPS上搭了一个来测试,很奇怪,这个貌似和自己之前做的那个靶机不太像。看题解貌似是moxiaoxi师傅写的一个,好了废话不多说啦,开始复现。

漏洞一

写文件+文件包含

123.png

可以大致看到这个框架,通过page参数跳转到不同的php文件,然后通过view来渲染。page参数可控。

跟进一下filter函数:

1
2
3
4
function filter($input)
{
return str_replace('.', '', $input);
}

这里将输入的点替换为空,在跟进一下 echoContent函数:

1
2
3
4
5
6
7
8
9
function echoContent($vId, $data)
{
$this->data = $data;
$content = loadFile("views/".$vId.".php");
$content = $this->parseHeadAndFoot($content);
$content = $this->parseVal($content);
$content = $this->parseIf($content);
echo $content;
}

这里vId参数可控,接着跟进 loadFile()函数:

1
2
3
4
5
6
7
8
9
10
11
12
function loadFile($filePath)
{
global $cfg_basedir;
if(!file_exists($filePath)){
write_log('Try to open Null file:'.$filePath);
return file_get_contents($cfg_basedir.'/error.php');
}
$fp = @fopen($filePath,'r');
$sourceString = @fread($fp,filesize($filePath));
@fclose($fp);
return $sourceString;
}

file_path参数可控,且当路径不存在时,会调用 write_log函数,我们继续跟进这个函数:

1
2
3
4
5
function write_log($input)
{
global $cfg_logfile;
file_put_contents($cfg_logfile, $input, FILE_APPEND);
}

input参数可控,我们再来跟进一下这个 全局变量的值,$cfg_logfile = dirname($_SERVER['SCRIPT_FILENAME']) . DS. "logs/logfile.php";

dirname($_SERVER['SCRIPT_FILENAME']) 返回 web根目录,跟进一下DS,发现:

define('DS', DIRECTORY_SEPARATOR); 然后跟进一下 DIRECTORY_SEPARATOR :

define ('DIRECTORY_SEPARATOR', "/"); 所以最后 DS值为 /

所以到这里基本可以确定,一步一步分析下来, page参数可控,我们写一个不存在的路径,然后这里会把page内容写入到 /var/www/html/html/logs/logfile.php 里面,如果我们能找到一个有文件包含函数的php文件,就可以实现文件包含,getshell 。

可以看到 action.php 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
require_once("library/common.php");
require_once("library/view.php");
$page = filter($_POST['page']).'.php';
$post_data = array();
foreach ($_POST as $key => $value) {
$post_data[$key] = $value;
}
if (file_exists($page))
{
require_once($page);
}
?>

存在require_once()函数,所以漏洞利用链就形成了。

一开始在index.php页面写马

124.png

然后在action.php页面包含:

125.png

发现成功getshell

这里一开始没复现成功,后来才发现原来是 logfile.php 没有给写权限,233333.

漏洞二

变量覆盖+ preg_replace /e 执行(/e 这个5.5版本以上就废弃了)

我们来看一下 normailz.php代码,重点放在 function.php上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function action($post_data, $ip_replacement, $mail_replacement)
{
foreach ($post_data as $key => $value)
{
$$key = $value;
}
try
{
if ($method == '/\\d+\\.\\d+\\.\\d+\\.\\d+/')
{
$res = preg_replace($method, $ip_replacement, $source);
}
else
$res = preg_replace($method, $mail_replacement, $source);

}
catch(Exception $e)
{
write_log($e->getMessage());
$res=$source;
}
return $res;
}

这里可以看到 $$key=$value 这一行,则很有可能可以实现变量覆盖,我们追溯一下 $post_data变量

发现跳转到 action.php,重点在这几行代码:

1
2
3
4
5
$post_data = array();
foreach ($_POST as $key => $value)
{
$post_data[$key] = $value;
}

这里我们传入post值,会进行键值和对应value的赋值。

1
2
3
4
foreach ($post_data as $key => $value) 
{
$$key = $value; //这里的$key本身对应一个value,然后对应的value又对应一个value,比如 post_data[page]=normailz
} //即 $key=page 然后 $value=normailz ,然后$$key=$page=$value=normaliz

然后结合这个,我们实现变量覆盖,从而实现 method , mail_replacement,source的值 全部可控,然后我们利用 /e 这个特性 来执行命令并返回。

我们传入的payload,post传参:

page=normailz&method=/a/e&mail_replace=phpinfo()&source=a

这里经过第一层 foreach:

1
2
3
4
5
6
7
$post_data[page]=normailz

$post_data[method]=/a/e

$post_data[mail_replacement]=phpinfo()

$post_data[source]=a

经过第二次foreach:

1
2
3
4
5
6
7
$page=normailz

$method=/a/e

mail_replacement=phpinfo()

source=a

所以实现了各个参数可控,触发命令执行,复现效果图:

111.png