From ba0239e076678d89b122b6ae2743f39d6748f09e Mon Sep 17 00:00:00 2001 From: Timo Mueller Date: Sat, 5 Jun 2021 12:46:02 +0200 Subject: [PATCH 1/2] Prevent potentially dangerous behaviour within proxy script --- playground/proxy.php | 60 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/playground/proxy.php b/playground/proxy.php index 683135e34..cb4715de4 100644 --- a/playground/proxy.php +++ b/playground/proxy.php @@ -6,48 +6,52 @@ // Support < 5.4.0 // https://stackoverflow.com/questions/3258634/php-how-to-send-http-response-code -if(!function_exists('http_response_code')) -{ - function http_response_code($newcode = NULL) - { - static $code = 200; - if($newcode !== NULL) - { - header('X-PHP-Response-Code: '.$newcode, true, $newcode); - if(!headers_sent()) - $code = $newcode; - } - return $code; - } -} -if($_GET && $_GET['url']) { - $headers = getallheaders(); - $headers_str = array(); +if ($_GET && $_GET['url']) { $url = $_GET['url']; - if(strpos($url, 'http') !== 0) { - exit('Only proxying HTTP URLs is allowed. Invalid URL: ' . $url); + $host = parse_url($url, PHP_URL_HOST); + $ip = gethostbyname($host); + + // replace hostname with IP to avoid DNS rebinding attacks + preg_replace("/" . $host . "/", $ip, $url, 1); + + // Check if IP in reserved range: https://www.php.net/manual/de/filter.filters.flags.php + if (filter_var($ip, FILTER_FLAG_NO_PRIV_RANGE, FILTER_FLAG_NO_RES_RANGE)) { + exit('The playground does not allow IPs within private or reserved ranges as JSON-LD @context URIs.'); } - foreach($headers as $key => $value){ - if($key == 'Host') - continue; - $headers_str[]=$key.': '.$value; + if (strpos($url, 'http') !== 0) { + exit('The playground only supports HTTP schema for JSON-LD @context URLs.'); } + // Set Headers + $headers_str = array(); + $headers_str[] = "Host: " . $host; + $headers_str[] = "User-Agent: " . "curl/json-ld.org"; + $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers_str); + + curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP); + curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // Prevent redirect to blocked IP range. If we want to enable redirections we have to ensure the redirect does not contain an IP in a reserved/private range + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers_str); curl_setopt($ch, CURLOPT_ENCODING, ''); curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $result = curl_exec($ch); - http_response_code(curl_getinfo($ch, CURLINFO_HTTP_CODE)); - header('Content-Type: '.curl_getinfo($ch, CURLINFO_CONTENT_TYPE)); + $recieved_content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); + + // Only allow json responses as we only process those on the client anyway + // Prevent XSS payloads by returning HTML content from external sites. + if ($recieved_content_type !== 'application/ld+json' && $recieved_content_type !== 'application/json') { + exit('Invalid content type recieved from JSON-LD @context endpoint'); + } + curl_close($ch); echo $result; } - -?> From 7ea5f11d0451d2f2ddd405739ff9d03a2b863ce8 Mon Sep 17 00:00:00 2001 From: Timo Mueller Date: Sat, 5 Jun 2021 13:11:50 +0200 Subject: [PATCH 2/2] Make the host replace method a little more resilient against regex injection --- playground/proxy.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/playground/proxy.php b/playground/proxy.php index cb4715de4..11f8f74b4 100644 --- a/playground/proxy.php +++ b/playground/proxy.php @@ -7,6 +7,14 @@ // Support < 5.4.0 // https://stackoverflow.com/questions/3258634/php-how-to-send-http-response-code +// https://stackoverflow.com/questions/1252693/using-str-replace-so-that-it-only-acts-on-the-first-match +function str_replace_first($from, $to, $content) +{ + $from = '/'.preg_quote($from, '/').'/'; + + return preg_replace($from, $to, $content, 1); +} + if ($_GET && $_GET['url']) { $url = $_GET['url']; @@ -14,7 +22,7 @@ $ip = gethostbyname($host); // replace hostname with IP to avoid DNS rebinding attacks - preg_replace("/" . $host . "/", $ip, $url, 1); + $url = str_replace_first($host, $ip, $url); // Check if IP in reserved range: https://www.php.net/manual/de/filter.filters.flags.php if (filter_var($ip, FILTER_FLAG_NO_PRIV_RANGE, FILTER_FLAG_NO_RES_RANGE)) {