CVE-2019-3569: Exposure of Resource to Wrong Sphere (CWE-668)
value: HHVM, when used with FastCGI, would bind by default to all available interfaces. This behavior could allow a malicious individual unintended direct access to the application, which could result in information disclosure. This issue affects versions 4.3.0, 4.4.0, 4.5.0, 4.6.0, 4.7.0, 4.8.0, versions 3.30.5 and below, and all versions in the 4.0, 4.1, and 4.2 series.
CWE description: The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource.
CVSS v3 base score: 7.5
Published: 2019-06-26T15:15Z
Repository: https://github.com/facebook/hhvm
Language: PHP
Files changed: hphp/runtime/server/fastcgi/fastcgi-server.cpp, hphp/test/server/debugger/tests/runTest1.php, hphp/test/server/fastcgi/tests/authDigestTest.php, hphp/test/server/fastcgi/tests/disable_ini_zend_compat.php, hphp/test/server/fastcgi/tests/global_variables_server.php, hphp/test/server/fastcgi/tests/headerTest.php, hphp/test/server/fastcgi/tests/httpsTest.php, hphp/test/server/fastcgi/tests/invalid.php, hphp/test/server/util/server_tests.inc
Commit date: 2019-06-11 11:54:48 -0700
Commit message: Fix default FastCGI interface

Reviewed By: alexeyt

Differential Revision: D15567393

fbshipit-source-id: ffab228f689d4ddca9ea4835c70aecf16da2aa9f

Diff: diff --git a/hphp/runtime/server/fastcgi/fastcgi-server.cpp b/hphp/runtime/server/fastcgi/fastcgi-server.cpp
index bce9f3195cb5..32d7bf675037 100644
--- a/hphp/runtime/server/fastcgi/fastcgi-server.cpp
+++ b/hphp/runtime/server/fastcgi/fastcgi-server.cpp
@@ -78,11 +78,12 @@ FastCGIServer::FastCGIServer(const std::string &address,
                  RequestPriority::k_numPriorities) {
   folly::SocketAddress sock_addr;
   if (useFileSocket) {
     sock_addr.setFromPath(address);
   } else if (address.empty()) {
-    sock_addr.setFromLocalPort(port);
+    sock_addr.setFromHostPort("localhost", port);
+    assert(sock_addr.isLoopbackAddress());
   } else {
     sock_addr.setFromHostPort(address, port);
   }
   m_socketConfig.bindAddress = sock_addr;
   m_socketConfig.acceptBacklog = RuntimeOption::ServerBacklog;
diff --git a/hphp/test/server/debugger/tests/runTest1.php b/hphp/test/server/debugger/tests/runTest1.php
index b4b506b9155c..1c290e7c1d0b 100644
--- a/hphp/test/server/debugger/tests/runTest1.php
+++ b/hphp/test/server/debugger/tests/runTest1.php
@@ -3,13 +3,12 @@
 require_once('test_base.inc');
 
 function test1Controller($hphpdOutput, $hphpdProcessId, $serverPort) {
   // Request a page so that the client can debug it.
   waitForClientToOutput($hphpdOutput, "Waiting for server response");
-  $url = "http://".php_uname('n').':'.$serverPort.'/test1.php';
   echo "Requesting test1.php\n";
-  request(php_uname('n'), $serverPort, 'test1.php', 10); // ignore response
+  request('localhost', $serverPort, 'test1.php', 10); // ignore response
 
   // Let client run until script quits
   waitForClientToOutput($hphpdOutput, "quit");
 }
 
diff --git a/hphp/test/server/fastcgi/tests/authDigestTest.php b/hphp/test/server/fastcgi/tests/authDigestTest.php
index f32eba113f50..42a4c89476a1 100644
--- a/hphp/test/server/fastcgi/tests/authDigestTest.php
+++ b/hphp/test/server/fastcgi/tests/authDigestTest.php
@@ -6,21 +6,21 @@ function BadAuthDigestTestController($serverPort) {
   $args = array('Authorization' => 'Digest "username="admin", ' .
     'realm="Restricted area", nonce="564a12f5c065e", ' .
     'uri="/test_auth_digest.php", cnonce="MjIyMTg2", nc=00000001, ' .
     'qop="auth", response="6dfbea52fbf13016476c1879e6436004", ' .
     'opaque="cdce8a5c95a1427d74df7acbf41c9ce0"');
-  var_dump(request(php_uname('n'), $serverPort, "test_auth_digest.php",
+  var_dump(request('localhost', $serverPort, "test_auth_digest.php",
                   [], [], $args));
 }
 
 function GoodAuthDigestTestController($serverPort) {
   $args = array('Authorization' => 'Digest username="admin", ' .
     'realm="Restricted area", nonce="564a12611dae8", ' .
     'uri="/test_auth_digest.php", cnonce="MjIyMTg1", nc=00000001, ' .
     'qop="auth", response="e544aaed06917adea3e5c74dd49f0e32", ' .
     'opaque="cdce8a5c95a1427d74df7acbf41c9ce0"');
-  var_dump(request(php_uname('n'), $serverPort, "test_auth_digest.php",
+  var_dump(request('localhost', $serverPort, "test_auth_digest.php",
                   [], [], $args));
 }
 
 runTest("BadAuthDigestTestController");
 runTest("GoodAuthDigestTestController");
diff --git a/hphp/test/server/fastcgi/tests/disable_ini_zend_compat.php b/hphp/test/server/fastcgi/tests/disable_ini_zend_compat.php
index 86b8fe226c44..e55c60c45a25 100644
--- a/hphp/test/server/fastcgi/tests/disable_ini_zend_compat.php
+++ b/hphp/test/server/fastcgi/tests/disable_ini_zend_compat.php
@@ -4,11 +4,11 @@
 // per dir diff (D2099778)
 
 require_once('test_base.inc');
 
 function disableIniZendCompatController($port) {
-  echo request(php_uname('n'), $port, 'test_disable_ini_zend_compat.php');
+  echo request('localhost', $port, 'test_disable_ini_zend_compat.php');
 }
 
 echo "---Enable Ini Zend Compat ON---\n";
 runTest("disableIniZendCompatController",
         "-dhhvm.enable_zend_ini_compat=true");
diff --git a/hphp/test/server/fastcgi/tests/global_variables_server.php b/hphp/test/server/fastcgi/tests/global_variables_server.php
index f364dd14527e..b9f84bf076f9 100644
--- a/hphp/test/server/fastcgi/tests/global_variables_server.php
+++ b/hphp/test/server/fastcgi/tests/global_variables_server.php
@@ -21,8 +21,8 @@ foreach($requests as $request) {
   runTest(function($port) use($request) {
     list($options, $extra) = $request;
     $path = 'global_variables.php?var=GET&get=1';
     $post = array('var' => 'POST', 'post' => 2);
     $headers = array('Cookie' => 'var=COOKIE;cookie=3;');
-    echo request(php_uname('n'), $port, $path, $post, $headers, $extra) . "\n";
+    echo request('localhost', $port, $path, $post, $headers, $extra) . "\n";
   }, $request[0]);
 }
diff --git a/hphp/test/server/fastcgi/tests/headerTest.php b/hphp/test/server/fastcgi/tests/headerTest.php
index 9bc9cf7ef414..91da6e5e1f13 100644
--- a/hphp/test/server/fastcgi/tests/headerTest.php
+++ b/hphp/test/server/fastcgi/tests/headerTest.php
@@ -2,10 +2,10 @@
 
 require_once('test_base.inc');
 
 function headerTestController($serverPort) {
   $args = array('Authorization' => 'foo');
-  var_dump(request(php_uname('n'), $serverPort, "test_headers.php",
+  var_dump(request('localhost', $serverPort, "test_headers.php",
                   [], ['PROXY' => 'foobar'], $args));
 }
 
 runTest("headerTestController");
diff --git a/hphp/test/server/fastcgi/tests/httpsTest.php b/hphp/test/server/fastcgi/tests/httpsTest.php
index 51ef72dc3cb4..7db48a7077dc 100644
--- a/hphp/test/server/fastcgi/tests/httpsTest.php
+++ b/hphp/test/server/fastcgi/tests/httpsTest.php
@@ -2,10 +2,10 @@
 
 require_once('test_base.inc');
 
 function httpsTestController($serverPort) {
   $args = array('HTTPS' => '');
-  var_dump(request(php_uname('n'), $serverPort, "test_https.php",
+  var_dump(request('localhost', $serverPort, "test_https.php",
                   [], [], $args));
 }
 
 runTest("httpsTestController");
diff --git a/hphp/test/server/fastcgi/tests/invalid.php b/hphp/test/server/fastcgi/tests/invalid.php
index 462fce0312fd..e36eaac31df7 100644
--- a/hphp/test/server/fastcgi/tests/invalid.php
+++ b/hphp/test/server/fastcgi/tests/invalid.php
@@ -1,11 +1,11 @@
 <?hh
 
 require_once('test_base.inc');
 
 function invalidTestController($port) {
-  $host = php_uname('n');
+  $host = 'localhost';
 
   $filename = __DIR__.'/request-doesnotexist.dat';
   $file = fopen($filename, 'rb');
   $req_dat = fread($file, filesize($filename));
   fclose($file);
diff --git a/hphp/test/server/util/server_tests.inc b/hphp/test/server/util/server_tests.inc
index 28b8d8ce42cc..55366c55f3cb 100644
--- a/hphp/test/server/util/server_tests.inc
+++ b/hphp/test/server/util/server_tests.inc
@@ -177,11 +177,11 @@ function startServer(&$serverPort, &$adminPort, &$debugPort, $home, $root,
   dumpLogFilesToStdoutAndDie();
 }
 
 // Check if the server id is in the expected list of ids.
 function checkServerId($serverPort, $expectedIds) {
-  $host = php_uname('n');
+  $host = 'localhost';
   $r = request($host, $serverPort, "hello.php");
   if (preg_match('/Hello, World!(.*+)/', $r ?? '', &$matches)) {
     foreach ((array)$expectedIds as $id) {
       if ($matches[1] == $id) return true;
     }
@@ -215,11 +215,11 @@ function stopServer($adminPort, $serverProc) {
   $test_run_id = ServerUtilServerTests::$test_run_id;
 
 
   $r = "";
   for ($i = 1; $i <= 10; $i++) {
-    $r = request(php_uname('n'), $adminPort, 'stop?instance-id='.$test_run_id);
+    $r = request('localhost', $adminPort, 'stop?instance-id='.$test_run_id);
     if ($r === "OK") break;
     usleep(100000);
   }
   if ($r != "OK") {
     tlog("Server did not stop. Response was $r");
@@ -284,11 +284,11 @@ function http_request($host, $port, $path, $timeout = 1200, $curl_opts = '') {
       $s[] = "-H '$h: $v'";
     }
     $headers = implode(" ", $s);
   }
   $url = "http://$host:$port/$path";
-  $host_name = "hphpd.debugger.".php_uname('n');
+  $host_name = "hphpd.debugger.".\php_uname('n');
 
   $LOG_ROOT = ServerUtilServerTests::$LOG_ROOT;
   $test_run_id = ServerUtilServerTests::$test_run_id;
   $cmd = "curl $post --trace-ascii ${LOG_ROOT}_curl$test_run_id.log ".
     "--silent $curl_opts --connect-timeout $timeout ".
@@ -302,11 +302,11 @@ function requestAll(array $requests, $customArgs = '', $repoArgs = '') {
   runTest(
     function($serverPort) use ($requests) {
       foreach ($requests as $request) {
         $r = is_array($request) ? $request[0] : $request;
         echo "Requesting '$r'\n";
-        var_dump(request(php_uname('n'), $serverPort, $request));
+        var_dump(request('localhost', $serverPort, $request));
       }
     },
     $customArgs,
     $repoArgs,
   );

Vulnerable code:     sock_addr.setFromLocalPort(port);
  $url = "http://".php_uname('n').':'.$serverPort.'/test1.php';
  request(php_uname('n'), $serverPort, 'test1.php', 10); // ignore response
  var_dump(request(php_uname('n'), $serverPort, "test_auth_digest.php",
  var_dump(request(php_uname('n'), $serverPort, "test_auth_digest.php",
  echo request(php_uname('n'), $port, 'test_disable_ini_zend_compat.php');
    echo request(php_uname('n'), $port, $path, $post, $headers, $extra) . "\n";
  var_dump(request(php_uname('n'), $serverPort, "test_headers.php",
  var_dump(request(php_uname('n'), $serverPort, "test_https.php",
  $host = php_uname('n');
  $host = php_uname('n');
    $r = request(php_uname('n'), $adminPort, 'stop?instance-id='.$test_run_id);
  $host_name = "hphpd.debugger.".php_uname('n');
        var_dump(request(php_uname('n'), $serverPort, $request));
Fixed code:     sock_addr.setFromHostPort("localhost", port);
    assert(sock_addr.isLoopbackAddress());
  request('localhost', $serverPort, 'test1.php', 10); // ignore response
  var_dump(request('localhost', $serverPort, "test_auth_digest.php",
  var_dump(request('localhost', $serverPort, "test_auth_digest.php",
  echo request('localhost', $port, 'test_disable_ini_zend_compat.php');
    echo request('localhost', $port, $path, $post, $headers, $extra) . "\n";
  var_dump(request('localhost', $serverPort, "test_headers.php",
  var_dump(request('localhost', $serverPort, "test_https.php",
  $host = 'localhost';
  $host = 'localhost';
    $r = request('localhost', $adminPort, 'stop?instance-id='.$test_run_id);
  $host_name = "hphpd.debugger.".\php_uname('n');
        var_dump(request('localhost', $serverPort, $request));