Sky's blog

Xman线下赛源码审计

Word count: 1,938 / Reading time: 9 min
2017/08/21 Share

Web

upload.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
27
28
if (isset($_FILES['file'])) {
$uploadsCount = count($_FILES['file']['name']);
if($uploadsCount > 0) {
$errors = array();
$messages = array();
for ($i=0; $i < $uploadsCount; $i++) {
if ($_FILES["file"]["error"][$i] > 0) {
$errors[] = i18n_r('ERROR_UPLOAD');
} else {

//set variables
$count = '1';
$file = $_FILES["file"]["name"][$i];

$extension = pathinfo($file,PATHINFO_EXTENSION);

$name = pathinfo($file,PATHINFO_FILENAME);
$name = clean_img_name(to7bit($name));
$base = $name . '.' . $extension;

$file_loc = $path . $base;

//prevent overwriting
while ( file_exists($file_loc) ) {
$file_loc = $path . $count.'-'. $base;
$base = $count.'-'. $base;
$count++;
}

利用了pathinfo()函数,$extension里存放了文件的后缀名,$name里存放了文件名
然后用了一个自己定义的clean_img_name()函数

1
2
3
4
5
6
7
8
9
10
11
function clean_img_name($text)  {
$text = getDef('GSUPLOADSLC',true) ? strip_tags(lowercase($text)) : strip_tags($text);
$code_entities_match = array(' ?',' ','--','&quot;','!','#','$','%','^','&','*','(',')','+','{','}','|',':','"','<','>','?','[',']','\\',';',"'",',','/','*','+','~','`','=');
$code_entities_replace = array('','-','-','','','','','','','','','','','','','','','','','','','','','','');
$text = str_replace($code_entities_match, $code_entities_replace, $text);
$text = urlencode($text);
$text = str_replace('--','-',$text);
$text = str_replace('%40','@',$text); // ensure @ is not encoded
$text = rtrim($text, "-");
return $text;
}

首先这个过滤,使用了strip_tags()函数,剥去文件名中的 HTML、XML 以及 PHP 的标签。
然后进行了str_replace()函数替换,去除各种符号,替换为空格或者-
再对文件名进行了url编码,再把”–”替换为-,”%40”替换为@
然后使用了rtrim()函数,移除”-“右侧的空白字符或其他预定义字符
然后把文件后缀和处理过的文件名拼接,再拼上路径
最后检查是否有重写,也就是这个文件是否存在
如果存在就在文件名前面加个’个数-‘
然后是检查文件是否安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (validate_safe_file($_FILES["file"]["tmp_name"][$i], $_FILES["file"]["name"][$i])) {
move_uploaded_file($_FILES["file"]["tmp_name"][$i], $file_loc);
if (defined('GSCHMOD')) {
chmod($file_loc, GSCHMOD);
} else {
chmod($file_loc, 0644);
}
exec_action('file-uploaded');

// generate thumbnail
require_once('inc/imagemanipulation.php');
genStdThumb($subFolder,$base);
$messages[] = i18n_r('FILE_SUCCESS_MSG').': <a href="'. $SITEURL .'data/uploads/'.$subFolder.$base.'">'. $SITEURL .'data/uploads/'.$subFolder.$base.'</a>';
} else {
$messages[] = $_FILES["file"]["name"][$i] .' - '.i18n_r('ERROR_UPLOAD');
}

这里使用了自己写的validate_safe_file()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function validate_safe_file($file, $name, $mime = null){
global $mime_type_blacklist, $mime_type_whitelist;

include(GSADMININCPATH.'configuration.php');

$file_extension = lowercase(pathinfo($name,PATHINFO_EXTENSION));

if ($mime && $mime_type_whitelist && in_arrayi($mime, $mime_type_whitelist)) {
return true;
}

if ($mime && in_arrayi($mime, $mime_type_blacklist)) {
return false;
} else {
return true;
}
}

这里用到了全局变量:
$mime_type_blacklist:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$mime_type_blacklist = array(
# HTML may contain cookie-stealing JavaScript and web bugs
'text/html', 'text/javascript', 'text/x-javascript', 'application/x-shellscript',
# PHP scripts may execute arbitrary code on the server
'application/x-php', 'text/x-php',
# Other types that may be interpreted by some servers
'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh',
# Client-side hazards on Internet Explorer
'text/scriptlet', 'application/x-msdownload',
# Windows metafile, client-side vulnerability on some systems
'application/x-msmetafile',
# MS Office OpenXML and other Open Package Conventions files are zip files
# and thus blacklisted just as other zip files
'application/x-opc+zip'
);

但是,不管他的安全函数写的多好……有一个严重的逻辑问题,他的mine初始是Null……然而他调用的时候并没有改变它:
validate_safe_file($_FILES["file"]["tmp_name"][$i], $_FILES["file"]["name"][$i])
直接导致

1
2
3
4
5
6
7
8
9
if ($mime && $mime_type_whitelist && in_arrayi($mime, $mime_type_whitelist)) {
return true;
}

if ($mime && in_arrayi($mime, $mime_type_blacklist)) {
return false;
} else {
return true;
}

这一块成了摆设,两个if都直接过不了,直接到了else,return了true,构成了任意文件上传,黑名单成为了摆设

backups/admin.xml.bak

这个文件里直接存放了管理员的账号和密码

1
2
<?xml version="1.0" encoding="UTF-8"?>
<item><USR>admin</USR><NAME/><PWD>qweasd</PWD><EMAIL>123@eee.com</EMAIL><HTMLEDITOR>1</HTMLEDITOR><TIMEZONE/><LANG>en_US</LANG></item>

可见账号是admin,密码是qweasd

download.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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php 


// Setup inclusions
$load['plugin'] = true;

// Include common.php
include('inc/common.php');

# check if all variables are set
if(isset($_GET['file'])) {

$file = removerelativepath($_GET['file']);

$extention = pathinfo($file,PATHINFO_EXTENSION);
header("Content-disposition: attachment; filename=".$file);

# set content headers
if ($extention == 'gz') {
header("Content-type: application/x-gzip");
} elseif ($extention == 'mpg') {
header("Content-type: video/mpeg");
} elseif ($extention == 'jpg' || $extention == 'jpeg' ) {
header("Content-type: image/jpeg");
} elseif ($extention == 'txt' || $extention == 'log' ) {
header("Content-type: text/plain");
} elseif ($extention == 'xml' ) {
header("Content-type: text/xml");
} elseif ($extention == 'js' ) {
header("Content-type: text/javascript");
} elseif ($extention == 'pdf' ) {
header("Content-type: text/pdf");
} elseif ($extention == 'css' ) {
header("Content-type: text/css");
} else {
header("Content-type: application/octet-stream");
}

# plugin hook
exec_action('download-file');

# get file
if (file_exists($file)) {
readfile($file, 'r');
}
exit;

} else {
echo 'No such file found';
die;
}

exit;

在download.php文件里,我没有找到有关确认登录的函数,例如login_cookie_check();这相当危险,直接构成了任意文件下载

settings.php任意更改管理员密码

同样没有login_cookie_check(),十分危险
存在任意更改密码问题
直接访问settings.php发现username不能写入

1
2
3
<div class="leftsec">
<p><label for="user" ><?php i18n('LABEL_USERNAME');?>:</label><input class="text" id="user" name="user" type="text" readonly value="<?php if(isset($USR1)) { echo $USR1; } else { echo $USR; } ?>" /></p>
</div>

发现只是前端限制,加了个readonly,然后后端传入当前的登录用户名
……我们可以前端随意更改用户名
然后他直接接受了我们post的用户名

1
2
3
if(isset($_POST['user'])) { 
$USR = strtolower($_POST['user']);
}

然后对于密码,直接输入新密码即可:

1
2
3
4
5
6
7
8
9
10
if(isset($_POST['sitepwd'])) { $pwd1 = $_POST['sitepwd']; }
if(isset($_POST['sitepwd_confirm'])) { $pwd2 = $_POST['sitepwd_confirm']; }
if ($pwd1 != $pwd2 && $pwd2 != '') {
#passwords do not match
$error = i18n_r('PASSWORD_NO_MATCH');
} else {
# password cannot be null
if ( $pwd1 != '' && $pwd2 != '') {
$PASSWD = $pwd1;
}

然后$PASSWD就成了我们需要的密码,$USR也成了我们需要的用户名
然后他直接执行了插入:

1
2
3
4
5
6
7
8
9
10
createBak($file, GSUSERSPATH, GSBACKUSERSPATH);
if (file_exists(GSUSERSPATH . _id($USR).'.xml.reset')) { unlink(GSUSERSPATH . _id($USR).'.xml.reset'); }
$xml = new SimpleXMLExtended('<?xml version="1.0" encoding="UTF-8"?><item></item>');
$xml->addChild('USR', $USR);
$xml->addChild('NAME', var_out($NAME));
$xml->addChild('PWD', $PASSWD);
$xml->addChild('EMAIL', var_out($EMAIL,'email'));
$xml->addChild('HTMLEDITOR', $HTMLEDITOR);
$xml->addChild('TIMEZONE', $TIMEZONE);
$xml->addChild('LANG', $LANG);

如果有重复,就直接重置……导致我们可以轻易的改掉管理员的密码,或者是自己添加用户进去

Babyblog

这里用到了MVC的架构,使用了laravel框架。
乍一看文件内容很多……但是我还是只主要看了controllers的代码,毕竟这一块和逻辑牵涉比较多
这里就没有一一看过去了,选择了PostsController.php,也是漏洞比较明显的一个文件

uploadImage()

首先还是上传图片函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function uploadImage()
{
$data = [
'success' => false,
'msg' => 'Failed!',
'file_path' => ''
];

if ($file = Input::file('upload_file'))
{
$fileName = $file->getClientOriginalName();
$extension = $file->getClientOriginalExtension() ?: 'png';
$folderName = '/uploads/images/' . date("Ym", time()) .'/'.date("d", time()) .'/'. Auth::user()->id;
$destinationPath = public_path() . $folderName;
$safeName = str_random(10).'.'.$extension;
$file->move($destinationPath, $safeName);
$data['file_path'] = $folderName .'/'. $safeName;
$data['msg'] = "Succeeded!";
$data['success'] = true;
}
return $data;
}

判断文件的唯一方式就是检测文件后缀名是否为png……可以说很爆笑了,在图片里插个小马他也不知道
但是唯一有点特别的就是,存放的文件夹用了时间戳,文件名用了随机数
然而,他后面却把这个路径回显了出来,我就看不太懂了……估计是故意留的坑,这样一来,前面的时间戳和随机数还有什么意义……直接就是图片小马,按给的路径连接就好

resolveImage()

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
public function resolveImage()
{
$resp = [
'success' => false,
'msg' => 'Failed!',
'file_path' => ''
];

$data = Input::only('resolve_file');

$content = @file_get_contents($data['resolve_file']);
if ($content)
{
$extension = 'png';
$folderName = '/uploads/images/' . date("Ym", time()) .'/'.date("d", time()) .'/'. Auth::user()->id;
$destinationPath = public_path() . $folderName;
$safeName = str_random(10).'.'.$extension;
@mkdir($destinationPath, 0755, true);
@file_put_contents($destinationPath .'/'. $safeName, $content);
$resp['file_path'] = $folderName .'/'. $safeName;
$resp['msg'] = "Succeeded!";
$resp['success'] = true;
}
return $resp;
}

这个函数也是很爆笑了……存在SSRF攻击。服务端并未对资源地址进行限制,导致的可以探测服务器内网或任意本地文件读取。
值得注意的是,他还特意加了一个
@mkdir($destinationPath, 0755, true);
其中0755的意思是:
目录的创建者在目录中:可写、可读、可执行
同组或其他人其他人,不可写、可读、可执行。
等于这个代码明摆着告诉你,这个路径可执行…………………………简直是找打

CATALOG
  1. 1. Web
    1. 1.1. upload.php之任意文件上传
    2. 1.2. backups/admin.xml.bak
    3. 1.3. download.php任意文件下载
    4. 1.4. settings.php任意更改管理员密码
  2. 2. Babyblog
    1. 2.1. uploadImage()
    2. 2.2. resolveImage()