CVE-2022-1191: Server-Side Request Forgery (SSRF) (CWE-918)
value: SSRF on index.php/cobrowse/proxycss/ in GitHub repository livehelperchat/livehelperchat prior to 3.96.
CWE description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
CVSS v3 base score: 8.1
Published: 2022-03-31T09:15Z
Repository: https://github.com/livehelperchat/livehelperchat
Language: PHP
Files changed: lhc_web/modules/lhcobrowse/proxycss.php
Commit date: 2022-03-31 00:22:57 -0400
Commit message: Harden to avoid SSRF

Diff: diff --git a/lhc_web/modules/lhcobrowse/proxycss.php b/lhc_web/modules/lhcobrowse/proxycss.php
index fd8aa6a5f65..7838aa53fdf 100644
--- a/lhc_web/modules/lhcobrowse/proxycss.php
+++ b/lhc_web/modules/lhcobrowse/proxycss.php
@@ -15,16 +15,20 @@ if ($Params['user_parameters_unordered']['cobrowsemode'] == 'onlineuser'){
     $browse = erLhcoreClassCoBrowse::getBrowseInstance($chat);
 }
 
 $url = parse_url($_GET['base']);
 
+// Only http/https supported
+if (!in_array($url['scheme'],['http','https']) || (isset($url['port']) && !in_array($url['port'],[80,443]))) {
+    exit;
+}
+
 // Some basic validation
 if (isset($url['host']) && $url['host'] != '' && strpos($_GET['css'], $_SERVER['HTTP_HOST']) === false) {
-    $ch = curl_init();
-        
+
     $urlCSS = parse_url($_GET['css']);
-   
+
     // Just our attempt to fix CSS, BaseURL, Relative path madness
     if (! isset($urlCSS['host']) || $urlCSS['host'] == '') {
         if (strpos($_GET['css'], '../') !== false) {            
             $numberOfTime = substr_count($_GET['css'], '../');
             $match = str_replace('../', '', $_GET['css']);  
@@ -34,21 +38,43 @@ if (isset($url['host']) && $url['host'] != '' && strpos($_GET['css'], $_SERVER['
             
             $urlCSSDownload = implode('/', $imagePathToCss) . $match;                      
         } else {
             $urlCSSDownload = $url['scheme'] . '://' . $url['host'] . '/' . ltrim($_GET['css'], '/');
         }
-    } else { 
+    } else {
+
+        if (!in_array($urlCSS['scheme'],['http','https']) || (isset($urlCSS['port']) && !in_array($urlCSS['port'],[80,443]))) {
+            exit;
+        }
+
         $urlCSSDownload = $_GET['css'];
     }
-    
+
+    $ch = curl_init();
     curl_setopt($ch, CURLOPT_URL, $urlCSSDownload);
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
     curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
     curl_setopt($ch, CURLOPT_TIMEOUT, 5);
     curl_setopt($ch, CURLOPT_REFERER, $_GET['base']);
     curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 GTB5');
-    $cssContent = curl_exec($ch);
+    curl_setopt($ch, CURLOPT_HEADER, true);
+    $response = curl_exec($ch);
+
+    if (curl_errno($ch)) {   // should be 0
+        curl_close($ch);
+        exit;
+    }
+
+    $curl_info = curl_getinfo($ch);
+    $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
+    $header_size = $curl_info['header_size'];
+
+    $cssContent = substr($response, $header_size);
+
+    if ($contentType != 'text/css') {
+        exit;
+    }
 
     if ($cssContent !== false) {        
         if (preg_match_all("/url\(\s*[\'|\"]?([A-Za-z0-9_\-\/\.\\%?&#]+)[\'|\"]?\s*\)/ix", $cssContent, $urlMatches)) {
             $urlMatches = array_unique($urlMatches[1]);
             foreach ($urlMatches as $match) {

Vulnerable code:     $ch = curl_init();
        
   
    } else { 
    
    $cssContent = curl_exec($ch);
Fixed code: // Only http/https supported
if (!in_array($url['scheme'],['http','https']) || (isset($url['port']) && !in_array($url['port'],[80,443]))) {
    exit;
}



    } else {

        if (!in_array($urlCSS['scheme'],['http','https']) || (isset($urlCSS['port']) && !in_array($urlCSS['port'],[80,443]))) {
            exit;
        }


    $ch = curl_init();
    curl_setopt($ch, CURLOPT_HEADER, true);
    $response = curl_exec($ch);

    if (curl_errno($ch)) {   // should be 0
        curl_close($ch);
        exit;
    }

    $curl_info = curl_getinfo($ch);
    $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    $header_size = $curl_info['header_size'];

    $cssContent = substr($response, $header_size);

    if ($contentType != 'text/css') {
        exit;
    }