本文作者:Z1NG(信安之路 2019 年度优秀作者)
前段时间看到安全狗海青实验室发的一篇关于压缩文件解压导致 getshell 的文章。其中一个漏洞是在 zzzcms1.73 版本。而这款 CMS 自己也算是审过几次,虽然小众,但的确是学习审计的好例子~
安全狗的文章中介绍了 zzzcms 的后台由于上传时存在目录穿越,可以上传到任意路径下,再配合后台解压的功能,就完成了 webshell 的植入。有意思的是,上传功能做了相对严格的后缀检测,而解压功能也只能解压指定文件夹里的压缩包文件,两个功能其实各自都不存在什么大问题,而却因为目录穿越问题导致两个功能点串联在一起,从而完成了 getshell。具体细节可以去看看原文《文件解压引发的 getshell》。
在 zzzcms 的官网上看到做了更新,并且修补了上面提到的漏洞。
下载了最新版的源码,试图绕过修补。然鹅,在尝试数次后,无果。于是,转而看看其他代码。因为之前出现过目录穿越的问题,那么很可能在其他也存在相同的问题。简单翻阅以后,看到如下代码。
很显然,在 69 行我们可以控制一个变量,而这个变量参与了一个路径的构造,因此我们可以控制这个路径跳转到任意目录。而在 78 行处调用了 simplexml_load_file 进行解析 XML,而并没有禁用引用外部实体,所以如果控制了 plug.xml 里的内容,就可以造成一个 XXE 漏洞。
为了控制 plug.xml 文件的内容,需要找到上传功能处。有意思的是,尽管上传功能的目录穿越已经被修补了无法利用,但是这里却存在目录穿越的问题,所以只要上传一个 plug.xml 文件,再控制解析 XML 文件的路径即可完成 XXE。而大多数的上传文件功能都会把文件进行重命名,找到上传功能的代码,如下:
function upload( $file, $type, $folder, $format = NULL, $max_width = NULL, $max_height = NULL ) {
if ( isset( $file ) ) {
$files = $file;
} elseif ( !isset( $_FILES[ $file ] ) ) {
$files = $_FILES[ $file ];
} else {
return array();
}
// 定义允许上传的扩展
if ( !isset( $type ) ) {
return array( 'state' => '上传类型不能为空' );
} else {
switch ( $type ) {
case 'file':
$array_ext_allow = explode( ',', Conf( 'fileext' ) );
$format = Conf( 'fileformat' );
break;
case 'video':
$array_ext_allow = explode( ',', Conf( 'videoext' ) );
$format = Conf( 'videoformat' );
break;
case 'image':
$array_ext_allow = explode( ',', Conf( 'imageext' ) );
$format = Conf( 'imageformat' );
break;
default:
$array_ext_allow = explode( ',', strtolower( $type ) );
break;
}
}
if ( !$files[ 'error' ] ) {
$upfile = $files[ 'name' ];
$file_arr = explode( '.', $upfile );
$file_ext = safe_word(strtolower( end( $file_arr )),'4');
$file_name = str_replace( '.' . end( $file_arr ), '', $upfile );
if(empty($file_ext)) {
return array( 'state' => '上传类型不能为空!' );
}elseif ( in_array( $file_ext, array('php','asp','aspx','exe','sh','sql','bat') ) ) {
return array( 'state' => $file_ext.'格式的文件不允许上传,请重新选择!' );
} elseif ( !in_array( $file_ext, $array_ext_allow ) ) {
var_dump($file_ext);
return array( 'state' => $file_ext . '格式的文件不能上传,请重新选择!');
}
$savefile = array( 'state' => 'SUCCESS', 'ext' => $file_ext, 'title' => $file_name, 'url' => handle_upload( $files[ 'name' ], $files[ 'tmp_name' ], $array_ext_allow, $folder, $format, $file_name, $file_ext, $max_width, $max_height ) );
return $savefile;
} else {
return $files[ 'error' ];
}
}
上面的 代码看到用黑名单限制了后缀名,显然这个黑名单是不严格的,随意上传 php5 这类后缀。检查完后缀之后就来到了 handle_upload 函数,这个函数的主要功能就是将文件进行保存。代码如下:
function handle_upload( $file, $temp, $array_ext_allow, $folder, $format, $file_name, $file_ext, $max_width, $max_height ) {
// 检查文件存储路径
if ( conf( 'datefolder' ) == 1 ) {
$save_path = SITE_DIR . conf( 'uploadpath' ) . $folder . '/' . date( 'Ymd' );
check_dir( $save_path, true );
} else {
$save_path = SITE_DIR . conf( 'uploadpath' ) . $folder;
check_dir( $save_path, true );
}
if ( $format == 'pinyin' ) {
$newname = pinyin( $file_name );
} elseif ( $format == 'yuanming' ) {
$newname = toutf( $file_name );
} else {
$newname = time() . mt_rand( 100000, 999999 );
}
$file_path = $save_path . '/' . $newname . '.' . $file_ext;
if ( is_file( $file_path ) ) {
if ( conf( 'covermark' ) == 1 ) {
del_file( $file_path );
} else {
$file_path = $save_path . '/' . $newname . mt_rand( 1000, 9999 ) . '.' . $file_ext;
}
}
move_uploaded_file( $temp, $file_path ); // 从缓存中转存
$save_file = str_replace( SITE_DIR, SITE_PATH, $file_path );
if ( $format == 'yuanming' )$save_file = togbk( $save_file );
// 如果是图片进行等比例缩放
if ( is_image( $file_path )) {
resize_img( $file_path, $file_path, 0, Conf( 'compresswidth' ), Conf( 'compressheight' ),Conf( 'compressquality') );
}
return $save_file;
}
文件名是否随机取决于变量 $format
,当 $format
的值为 pinyin 时,则就保存为原文件名。因此,已经可以通过上传一个 plug.xml
来控制 XML 解析的内容。
为了方便验证漏洞,在 C 盘下建立一个目标文件,内容如下。
利用漏洞:
首先先准备一个 plug.xml 文件用于上传。
然后准备一个 a.dtd 文件,放在可控服务器的 web 目录下,
然后编写一个上传页面,
将文件上传
然后触发 XXE 漏洞
POST:
http://127.0.0.1/zzzphp/admin/save.php?act=plugkey
plugpath=../upload/file&plugkey=e10adc3949ba59abbe56e057f20f883e
查看 web 日志,可以发现 flag.txt 的内容被携带的传送到了服务器上。
目录穿越其实只是个小问题,有种打辅助的感觉。就像这个漏洞,如果禁用了外部实体,同样也不能进行 XXE,不过也正是因为目录穿越,这个 XXE 才可以被利用。一直以来分享的文章都很基础很简单,充其量只是个入门的水平,让各位大佬见笑了。:)