记录一次代码审计

背景

之所以这么叫,实在不知道这套代码应该怎么叫了

看的这套是代码量最多的一套

控制器数量存在一定差距

1612340515678

前台权限绕过

定位api/controller/Base.phpcheckLogin方法

写的有点搞笑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function checkLogin()
{
$user = session('user');
$user['expire_time'] = time() + C('session.expire');//一天
// dump($session);die;
if (time() > $user['expire_time'] || !$user) DataReturn::returnBase64Json(302, '校验失败,需要重新登录!','/dist/pages/login.html');
/*if($check !== true) DataReturn::returnBase64Json(500,$check);*/
//获取用户的user_id

if(isset($user['user_id']))
{
$user_info = db('users')->where('user_id',$user['user_id'] )->find();

if (!$user_info) DataReturn::returnBase64Json(302, '获取用户信息失败','/dist/pages/login.html');

$this->user_id = $user_info['user_id'];
$this->user_info = $user_info;
if ($user_info['is_lock'] == 1) DataReturn::returnBase64Json(302, '此用户已锁定!','/dist/pages/login.html');
}
return true;
}

猜测开发可能是想,一天内的自动登录问题,但是逻辑出了问题

  • 在未登录的状态下,session为空
  • 此时的$usernull
  • 下面给$user赋值
  • $user 不为空了
  • 基本就是当前时间和一天后的时间进行判断,肯定不满足
  • 本来$user是空的,赋值之后不空了,所以直接跳过了登录
  • 下面又检测如果存在 user_id,不存在
  • 导致直接返回true

前台XSS

未设置全局过滤

都是在写入数据库是实体化的,总会有很多疏漏

1612343457717

或者

1612343478982

多个地方打一打就好了

未授权注入

这个注入点在api接口,对比了一下旧版本的,

三个版本都存在api/controller/Business.phporder_detail方法

1612342784644

复现

1612342961988

但是加密比较难搞盐值太长了

默认盐值JUD6FCtZsqrmVXc2apev4TRn3O8gAhxbSlH9wfPN

加密方式md5(md5(pass).salt)

废弃的上传

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
public function mycode(){
$user_id=$this->user_id;
$wxcode=M('users')->where(['user_id'=>$user_id])->value('wx_code');

if(!empty($wxcode) && file_exists($wxcode)){
DataReturn::returnJson('200','',['imageurl'=>request()->domain().'/'.$wxcode]);
}else{
$paymentPlugin = M('Plugin')->where("code='miniAppPay' and type = 'payment' ")->find(); // 找到微信支付插件的配置
$config_value = unserialize($paymentPlugin['config_value']); // 配置反序列化
$appid = $config_value['appid']; // * APPID
$appsecret = $config_value['appsecret']; // * appsecret
$post_arr = [
// 'page' => 'pages/contact_leader/contact_leader',
'scene' => 'user_id$'.$user_id,
];

$jssdk = new JssdkLogic($appid,$appsecret);
$base64=$jssdk->getwxacodeunlimit($post_arr);

if(preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64, $img)){
$type = $img[2];
}else{
DataReturn::returnJson('400','获取失败');
}
$file = 'public/wxcode/'.date('Ymd', time()).'/';
//检查是否有该文件夹,如果没有就创建
if (!file_exists($file)) {
mkdir($file, 0777, true);
}
$imgpath = $file . md5(time()).'.'.$type;
//将生成的小程序码存入相应文件夹下
file_put_contents($imgpath,base64_decode(str_replace($img[1],'',$base64)));
//写入数据库
M('users')->where(['user_id'=>$user_id])->update(['wx_code'=>$imgpath]);
DataReturn::returnJson('200','',['imageurl'=>request()->domain().'/'.$imgpath]);
}
}

原因是因为,不存在相关字段

1612343216542

后台任意文件上传

后台的这三个方法,都是任意文件上传

  • 限制:必须是超管用户

1612343286489

反序列化漏洞

结合注入使用

  • 限制:后台任意用户

通用的

api/controller/shop/Order.phpreturn_info方法

1为注入点,2为反序列化点

1612344809721

payload

地刘刘哥字段为16进制的反序列化paylaod

1
GET /shop/order/return_info?id=1) union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0xa1a1a1a1,17,18,19,20,21,22,23-- #

需要使用get方法,不然post方法会走update报错

新版本的反序列化漏洞

和上面一样的权限

在goods控制器中,同样的操作

1612345591197

payload

第14个字段为反序列化数据

1
GET /shop/goods/addEditGoods?id=1)%20union%20select%201,2,3,4,5,6,7,8,9,10,11,12,13,0xa1a1a1,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--+

文件包含漏洞

三个版本都不一样

  • 限制:后台低权

第一种

/shop/order/order_print

  • 参数:order_id存在注入
  • 参数template存在文件包含
  • 需要构造注入,使之正常运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function order_print(){
$order_id = I('order_id');
$orderLogic = new OrderLogic();
$order = $orderLogic->getOrderInfo($order_id);
$order['province'] = getRegionName($order['province']);
$order['city'] = getRegionName($order['city']);
$order['district'] = getRegionName($order['district']);
$order['full_address'] = $order['province'].' '.$order['city'].' '.$order['district'].' '. $order['address'];
$orderGoods = $orderLogic->getOrderGoods($order_id);
$shop = tpCache('shop_info');
$this->assign('order',$order);
$this->assign('shop',$shop);
$this->assign('orderGoods',$orderGoods);
$template = I('template','print');
return $this->fetch($template);
}

/shop/promotion/search_goods

1612346079566

最新版可选项

/mobile/index/index

  • 限制:前台用户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function index( $viewname = null ){
$ad = Db::name('ad')->where(['status'=> 1])->select();
$config = Db::name('config')->where(['name'=> 'web_site_description'])->limit(1, 1)->value('value');
$user = session('user');
$user["payment_count"] = M('user_payment')->where('user_id', $this->user_id)->count();
$user["subaccount_count"] = M('users_sub')->where('user_id', $this->user_id)->count();
$users = db('users')->where(['user_id'=>$this->user_id])->find();
$real_name_status = $users["real_name_status"];

$user["is_identity"] = ($users["real_name_status"]==2)?"已认证":(($users["real_name_status"]==1)?"认证中":(($users["real_name_status"]==0)?"未认证":(($users["real_name_status"]==-1)?"已驳回":"")));
$user["is_identity_i"] = ($users["real_name_status"]==2)?1:0;;

$this->assign('ad', $ad);
$this->assign('user', $user);
$this->assign('conent', $config);
$this->assign('real_name_status', $real_name_status);
if( $viewname==null )
return $this->fetch();
else
return $this->fetch($viewname);
}

远古版本

只有order下的控制器存在

1612346281634

通用复现

getOrderInfo存在注入

1
2
3
4
5
6
7
8
9
public function getOrderInfo($order_id)
{
// 订单总金额查询语句
$order = M('order')->where("order_id = $order_id")->find();
if(empty($order))return false;
$order['address2'] = $this->getAddressName($order['province'],$order['city'],$order['district']);
$order['address2'] = $order['address2'].$order['address'];
return $order;
}

getOrderGoods存在注入

1
2
3
4
5
6
7
8
9
10
public function getOrderGoods($order_id,$is_send =''){
$where = '';
if($is_send){
$where=" and o.is_send < $is_send";
}
$sql = "SELECT g.*,o.*,(o.goods_num * o.member_goods_price) AS goods_total FROM __PREFIX__order_goods o ".
"LEFT JOIN __PREFIX__goods g ON o.goods_id = g.goods_id WHERE o.order_id = $order_id ".$where;
$res = DB::query($sql);
return $res;
}

但是由于一个是45个字段一个是20个字段,没办法使用注入了

只能正常的搞了

后台代码执行

继承关系如下

admin模块通过base控制器来鉴权

1612347246750

调用authcheck方法

1612347285020

调用getAuthList方法

代码执行

1612347332040

只要存在可以控制数据库condition的就可以

可以通过admin模块下的menu控制器来添加

1612347398103

end