当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2016-0176779

漏洞标题:天融信TopADS多处无需登录高危漏洞打包(代码审计基础教程)

相关厂商:天融信

漏洞作者: xfkxfk

提交时间:2016-02-18 17:52

修复时间:2016-05-19 10:30

公开时间:2016-05-19 10:30

漏洞类型:设计缺陷/逻辑错误

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2016-02-18: 细节已通知厂商并且等待厂商处理中
2016-02-19: 厂商已经确认,细节仅向厂商公开
2016-02-22: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2016-04-14: 细节向核心白帽子及相关领域专家公开
2016-04-24: 细节向普通白帽子公开
2016-05-04: 细节向实习白帽子公开
2016-05-19: 细节向公众公开

简要描述:

天融信TopADS多处无需登录高危漏洞打包(代码审计基础教程),包括密码重置,命令执行,文件上传,文件读取,文件删除,SQL注入,未授权访问等

详细说明:

之前提交了几个漏洞,官方说通过其他渠道知道漏洞了。
那么我就把这些漏洞全发出来,当做一个文章了,厂商如果都通过其他渠道得知的话请忽略。
既然这是一个基础教程文章,那么废话不多说,我们来看代码吧
首先这个系统的web代码框架如图:

1.png


这里的目录和文件结构如下:
attachements为附件目录
cachedata为缓存文件目录
config当然就是配置文件目录了
data和help无关紧要不用管了
include这里面都是全局处理函数内容了
libraries为系统全局使用的一些class文件
modules是这里最重要的目录,里面都是系统功能文件,待会主要看这面的内容
剩下的就是一些静态文件目录了
index.php就是这个系统的入口文件了
下面开始进入index.php,一步一步了解这个系统

<?php
/**
* Ngtos
* @package Ngtos
* @author Ngtos Php Dev Team
* @link http://**.**.**.**
* @since Version 1.0
* @date 2014/1/8 10:14
* @filesource
*/
session_start();
setcookie('PHPSESSID', session_id());
/**
* 设置错误级别
*/
error_reporting(E_ERROR | E_PARSE);
/**
* 设置默认时区
*/
$timezone = "+8";
if (file_exists("/tmp/tos_timezone_tmpfile")) {
$timezone = trim(file_get_contents("/tmp/tos_timezone_tmpfile"));
$timezone = strstr($timezone, '+') ? str_replace('+', '-', $timezone) : str_replace('-', '+', $timezone);
}
date_default_timezone_set("Etc/GMT$timezone");
/**
* 设置编码为utf-8
*/
header("Content-type:text/html;charset=utf-8");
/**
* 定义常量 ROOTPATH
*/
define('ROOTPATH', dirname(__FILE__));
/**
* 引入init.inc 文件,初始化环境
*/
require_once ROOTPATH . DIRECTORY_SEPARATOR . 'include' . DIRECTORY_SEPARATOR . 'init.inc.php';
?>


前面都是一些系统设置内容
主要看最后一句,包含了一个init.inc.php文件,跟进
文件/include/init.inc.php

<?php
if (!defined('ROOTPATH'))
exit('No direct script access allowed');
/**
* Ngtos
*
* @package Ngtos
* @author Ngtos Php Dev Team
* @link http://www.**.**.**.**
* @since Version 1.0
* @date 2014/1/8 10:14
* @filesource
*/
require_once ROOTPATH . '/config/config.inc.php';
require_once ROOTPATH . '/config/message.php';
require_once ROOTPATH . '/include/common.inc.php';
require_once ROOTPATH . '/include/function.inc.php';
require_once ROOTPATH . '/include/cfgeng.inc.php';
require_once ROOTPATH . '/include/template.inc.php';
/**
* 获取session中设置的语言,未设置用浏览器默认的语言为网页的语言
*/
$lang = $_SESSION['language'] ? $_SESSION['language'] : get_browse_language();
$GLOBALS['language'] = $lang;
/**
* 引入语言文件
*/
require_once TPLLANGDIR . 'templates.' . $lang . '.lang.php';
$post_submit_action = $_POST['submit_post'];
$get_url_param = $_GET['g'];
require ROOTPATH . '/modules/ads/ads.php';
require_once ROOTPATH . '/modules/ips/ips.mds.php';
$NG = & load_class('Ngtos');
$NG->run();
?>


这里引入了全局配置文件config内容,全局函数处理内容include里面的内容
一般全局的过滤啊,参数处理啊等都在这些include的文件里面
注意最后:

$NG = & load_class('Ngtos');
$NG->run();


一般的run函数出来了,就说明程序已经进入正常运行状态了,来看看run的处理过程
load_class函数在文件/include/common.inc.php

if (!function_exists('load_class')) {
function &load_class($class, $directory = 'libraries') {
static $_classes = array();
if (isset($_classes[$class])) {
return $_classes[$class];
}
if (!file_exists(ROOTPATH . '/' . $directory . '/' . $class . '.class.php')) {
exit('Unable to locate the specified class: ' . $class . '.class.php');
}
require_once ROOTPATH . '/' . $directory . '/' . $class . '.class.php';
is_loaded($class);
$_classes[$class] = new $class();
return $_classes[$class];
}
}


这里可以看到根据传入的class值,调用libraries目录里面的class文件
这里的class为Ngtos,所以加载的文件为:/libraries/Ngtos.class.php
跟进Ngtos.class.php的run函数

public function run() {
$module = trim($_REQUEST['module']) ? trim($_REQUEST['module']) : 'login';
$action = trim($_REQUEST['action']) ? trim($_REQUEST['action']) : '';
if ($module == 'home' || $module == 'main' || $module == 'login' || $module == 'logout' || $module == 'password') {
$action = $module;
$module = 'page_frame';
}
if ($offset = @strpos($module, '_')) {
$folder = substr($module, 0, $offset);
$file = substr($module, $offset + 1);
$filename = ROOTPATH . '/modules/' . $folder . '/' . $file . '.mds.php';
if (!file_exists($filename)) {
$filename = ROOTPATH . '/modules/' . $folder . '/' . $file . '.php';
}
} else {
$filename = ROOTPATH . '/modules/' . $module . '.mds.php';
if (!file_exists($filename)) {
$filename = ROOTPATH . '/modules/' . $module . '.php';
}
}
if (!file_exists($filename)) {
die('Not Found');
}
require_once $filename;
}


这里通过传入的参数module和action,加载对应的modules中的功能文件
看到这里大概知道了,此TopADS系统是如何正常运行,如何正常传参,如何操作各种功能了
剩下的就是审计功能文件了,都在modules文件夹下面了
0x001 任意用户密码重置
文件/modules/page/frame.mds.php

<?php
switch ($action) {
......
case 'change_password':
$rspString = ngtos_mngt_password_modify($_POST['name'], $_POST['password']);
if ($rspString == 0) {
echo $rspString;
} else {
echo getErrorInfo($rspString);
}
break;


当action为change_password时,并没有判断$_SESSION['auth_id']),直接操作ngtos_mngt_password_modify($_POST['name'], $_POST['password'])
所以无需登录即可修改任意用户的密码了

2.png


0x002 命令执行
之前一个漏洞中提到过一个命令执行,这里我们拿出来不同的点
文件modules/ads/ads_report_upload.mds.php

<?php
include_once "ads_filetypevalidation.php";
if ($action == "upload_logo") {
$upFilePath = "/usr/local/apache2/htdocs/attachements/";
if (preg_match("/[\x7f-\xff]/", $_FILES['file']['name'])) {
echo json_encode(array('file_infor' => '上传失败,文件名错误,请勿上传中文名文件'));
return;
}
if ($_FILES['file']['size'] > 819200) {
echo json_encode(array('file_infor' => '上传失败,文件过大,最大800K'));
return;
}
$ok=@move_uploaded_file($_FILES['file']['tmp_name'], $upFilePath.$_FILES['file']['name']);
if($ok === FALSE){
echo json_encode(array('file_infor' => '上传失败'));
}else{
$rst = FileTypeValidation::validation($upFilePath.$_FILES['file']['name'], 'jpg');
if ($rst == false) {
echo json_encode(array('file_infor' => '上传失败,文件非jpeg/jpg格式'));
system("rm -rf " . $upFilePath.$_FILES['file']['name']);
return;
}
echo json_encode(array('file_infor' => '上传成功'));
$fp = fopen("/SE/web/logo.conf", "w");
fwrite($fp, "../../attachements/".$_FILES['file']['name']);
fclose($fp);
}
}
if ($action == "read_logo") {
$fp = fopen("/SE/web/logo.conf", "r");
while (($line = fgets($fp)) != NULL) {
echo $line;
}
}
if ($action == "default_logo") {
$fp = fopen("/SE/web/logo.conf", "w");
fwrite($fp, "../../static/images/image/logo.jpg");
fclose($fp);
echo "../../static/images/image/logo.jpg";
}
?>


可以看到,这里上传时,没有任何判断,上传后判断了文件类型及文件头,这里不做详细分析
当判断文件格式失败时,会把上传的文件删除掉,此时,将$_FILES['file']['name']拼接到命令中,进入system函数执行,导致命令执行

3.png


0x003 多处文件上传
第一处文件上传
文件modules/ads/ads_report_upload.mds.php

<?php
include_once "ads_filetypevalidation.php";
if ($action == "upload_logo") {
$upFilePath = "/usr/local/apache2/htdocs/attachements/";
if (preg_match("/[\x7f-\xff]/", $_FILES['file']['name'])) {
echo json_encode(array('file_infor' => '上传失败,文件名错误,请勿上传中文名文件'));
return;
}
if ($_FILES['file']['size'] > 819200) {
echo json_encode(array('file_infor' => '上传失败,文件过大,最大800K'));
return;
}
$ok=@move_uploaded_file($_FILES['file']['tmp_name'], $upFilePath.$_FILES['file']['name']);
if($ok === FALSE){
echo json_encode(array('file_infor' => '上传失败'));
}else{
$rst = FileTypeValidation::validation($upFilePath.$_FILES['file']['name'], 'jpg');
if ($rst == false) {
echo json_encode(array('file_infor' => '上传失败,文件非jpeg/jpg格式'));
system("rm -rf " . $upFilePath.$_FILES['file']['name']);
return;
}
echo json_encode(array('file_infor' => '上传成功'));
$fp = fopen("/SE/web/logo.conf", "w");
fwrite($fp, "../../attachements/".$_FILES['file']['name']);
fclose($fp);
}
}


上传时没有任何判断,但是上传后进行了判断
跟进FileTypeValidation::validation

private static $_fileFormats = Array(
'jpg' => 'FFD8FF',
);
public static function validation($filePath, $fileExt)
{
// 文件格式未知
if (!isset(self::$_fileFormats[$fileExt]))
{
return false;
}
$length = strlen(self::$_fileFormats[$fileExt]);
$bin = self::_readFile($filePath, $length);
$fileHead = @unpack("H{$length}", $bin);
// 判断文件头
if (strtolower(self::$_fileFormats[$fileExt]) == $fileHead[1])
{
return true;
}
return false;
}

private function _readFile($filePath, $size)
{
$file = fopen($filePath, "rb");
$bin = fread($file, $size);
fclose($file);
return $bin;
}


从这里可以看出,判断时,从上传的文件中读取文件头是不是等于jpg的头FFD8FF,如果相等就返回true,此时上传成功
那么我们上传一个php文件,然后加上jpg的头FFD8FF就ok了,成功上传文件

4.png


4-1.png


4-2.png


第二处文件上传
文件modules/ads/ads_staticwlist.php

if ($action == "import") {
$upFilePath = "/usr/local/apache2/htdocs/attachements/";
if ($_FILES['file']['size'] > 819200) {
echo json_encode(array('file_infor' => 'error:上传失败,文件过大,最大800K'));
return;
}
$ok=@move_uploaded_file($_FILES['file']['tmp_name'], $upFilePath.$_FILES['file']['name']);
if($ok === FALSE){
echo json_encode(array('file_infor' => 'error:上传失败'));
} else {
$err = "";
$fp = fopen($upFilePath.$_FILES['file']['name'], "r");
while($line = fgets($fp)) {
$param['whitelist'] = formatpost($line);
$rspString = getResponse("ddos global", "add", $param, 2);
if (is_string($rspString)) {
$err = $rspString;
} else{
$sql = "INSERT INTO ads_static_wlist values"."('".formatpost($line)."') ";
$db->query($sql);
}
}
fclose($fp);
echo $err;
}
}


直接上传,无任何过来
直接发送请求:

POST /index.php?module=ads_ads_staticblist&action=import HTTP/1.1
Host: **.**.**.**
Connection: keep-alive
Content-Length: 196
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: https://**.**.**.**
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytMQws7FgWq9XfnQY
Referer: https://**.**.**.**/index.php?g=ads_report_set_handle
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
------WebKitFormBoundarytMQws7FgWq9XfnQY
Content-Disposition: form-data; name="file"; filename="2.php"
Content-Type: image/jpeg
<?php phpinfo()?>
------WebKitFormBoundarytMQws7FgWq9XfnQY--


5.png


第三处文件上传
文件modules/ads/ads_staticblist.php

if ($action == "import") {
$upFilePath = "/usr/local/apache2/htdocs/attachements/";
if ($_FILES['file']['size'] > 819200) {
echo json_encode(array('file_infor' => '上传失败,文件过大,最大800K'));
return;
}
$ok=@move_uploaded_file($_FILES['file']['tmp_name'], $upFilePath.$_FILES['file']['name']);
if($ok === FALSE){
echo json_encode(array('file_infor' => '上传失败'));
} else {
$err = "";
$fp = fopen($upFilePath.$_FILES['file']['name'], "r");
while($line = fgets($fp)) {
$param['blacklist'] = formatpost($line);
$rspString = getResponse("ddos global", "add", $param, 2);
if (is_string($rspString)) {
$err = $rspString;
} else{
$sql = "INSERT INTO ads_static_blist values"."('".formatpost($line)."') ";
$db->query($sql);
}
}
fclose($fp);
echo $err;
}

}


原理同第二处上传,无过滤,直接上传
直接发送第二处的请求即可上传成功,不在证明
0x004 文件删除
文件modules/ads/ads_ads_capture_tcpdump.php

if ($post_submit_action == "adscapture_tcpdump_delete_pcap")
{
$pcap_name = $_POST["pcap_name"];
$file = "/SE/ads_tcpdump/".$pcap_name;
unlink($file);
return;
}


当$post_submit_action == "adscapture_tcpdump_delete_pcap"时,直接拼接参数进行unlink
这里的$post_submit_action是前面我们分析入口是提到的$post_submit_action = $_POST['submit_post'];
所以那前面的的文件上传,删除上传的1.php为例,成功删除

6.png


0x005 文件读取
文件读取见http://**.**.**.**/bugs/wooyun-2010-0176586这个漏洞
0x006 SQL注入
SQL注入漏洞的原因在漏洞http://**.**.**.**/bugs/wooyun-2010-0176616这个漏洞中已经给出了一部分,还有很多,但是原因相同,都是make_where惹的祸

function make_where($POST)
{
$sql_where = "";
foreach (array_keys($POST) as $key)
{
//除去与搜索条件不相关的字段
if ('submit_post' == $key || 'filename' == $key || 'export_type' == $key || 'chart' == $key || 'param' == $key || 'usrname' == $key || 'table' == $key || 'limit' == $key || '' == $POST[$key])
{
continue;
}
if($key == 'time_start')
{
$sql_where .= "AND cur_t >= '$POST[$key]' ";
}
elseif($key == 'time_end')
{
$sql_where .= "AND cur_t <= '$POST[$key]' ";
}
else
{ //组装搜索条件
$sql_where .= "AND $key = '$POST[$key]' ";
}
}
//除去最开始的'AND'
$sql_where = substr($sql_where, 3);
return $sql_where;
}


这里将$_POST的内容处理时,在最后的else条件中$sql_where .= "AND $key = '$POST[$key]' ";
直接将key带入sql语句,导致sql注入漏洞
之前的漏洞没有给出数据,厂商说不行,这里不上sqlmap的结果

7.png


8.png


全局搜索make_where(即可,因为用到此函数的,基本都存在sql注入问题
0x007 未授权访问
这里主要是从有两部分原因:
一个是直接没有走入口文件进入应用,直接访问的文件
二个是从入口进入应用了,但是没有进行$_SESSION['auth_id']判断,也是可以直接访问的

漏洞证明:

修复方案:

版权声明:转载请注明来源 xfkxfk@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:7

确认时间:2016-02-19 10:27

厂商回复:

已确认,谢谢提交。

最新状态:

暂无