目录导航
一周内从开放重定向到 RCE
在这篇文章中,我将告诉你我是如何在Mail.Ru Group(或现在的VK)的多个主机上链接多个安全问题以实现 RCE 的故事。对我来说,作为 bug 赏金文章的常客,在不常见的漏洞利用过程中了解猎人的思路总是很有趣,所以我试图尽可能详细地描述我的过程。我希望你会喜欢它。
介绍
我是HackerOne 上Mail.Ru Group漏洞赏金计划的忠实粉丝。VK是Mail.Ru 集团的新名称,有时会收购新公司,并为VK BBP 补充新资产,这为黑客提供了掠夺唾手可得的果实的好机会。
根据我的经验,我可以说访问某些东西是非常有利可图的,在你尝试破解之前没有人尝试过。我订阅了VK BBP 以接收有关新资产的更新,成为第一批可以访问它们的人。
在 2021 年期间,我在 BB 不是很活跃,也没有密切关注我最喜欢的 BBP 的更新,这就是我错过通知的原因,即Seedr是一个视频广告平台,但现在已弃用,已添加到资产中.
我与Seedr的第一次会面发生在 2021 年 10 月。几乎过了几分钟,我发现了几个简单的 XSS,我决定不报告,因为重复的机会太高了。
目前,您可以观察到其他猎手披露的几份报告,并检查那里是如何发现现代应用程序的非典型的漏洞:
- RCE в .api/nr/report/{id}/download
- SSRF + RCE через fastCGI в POST /api/nr/video
- XSS 存储在 https://seedr.ru
- seedr.ru 上的操作系统命令注入
我以为我的火车已经走了,所以我没有花太多时间在Seedr上,继续我的BB拖延症。
引起我注意的功能
我在 12 月在另一个国家度假时回到了Seedr ,当时我只带了一个背包和笔记本电脑。在这种情况下一段时间后,BB 的饥饿感会醒来,并且渴望找到一些有趣的东西。为了增强我的信心,我通常会重新审视已经熟悉的资产。
这次我在recon上花了更多的时间:子域枚举、端口扫描、目录暴力破解等等。幸运的是,我发现了更多有趣的东西:GitLab、Grafana、几个 API 主机、Web 目录中的 cron 文件、堆栈跟踪等等。您找到的切入点越多——找到辛辣食物的机会就越大。对我来说不幸的是,没有一个值得向 BBP 报告,但有一个功能引起了我的注意。
在https://api-stage.seedr.ru/player页面的 HTML 源代码中,我注意到以下注释:
https://player.seedr.ru/video?vid=cpapXGq50UY&post_id=57b6ceef64225d5b0f8b456c&config=https%3A%2F%2Fseedr.com%2Fconfig%2F57975d1b64225d607e8b456e.json&hosting=youtube
我敢打赌,您已经想config
用您的主机修改 GET 参数以接收传入的 HTTP 连接,我就是这样做的。几次尝试后,我没有收到任何连接,继续玩其他参数。
当我在浏览器中打开https://player.seedr.ru/video?vid=cpapXGq50UY&post_id=57b6ceef64225d5b0f8b456c&config=https%3A%2F%2Fseedr.com%2Fconfig%2F57975d1b64225d607e8b456e.json&hosting=youtube并观察到响应时,我注意到 Open Graph 元标记填充了有关视频的一些信息,例如标题、描述、图像等:
post_id
经过一些测试,我了解到config
GET 参数对响应没有显著影响,所以让我们将 URL 简化为https://player.seedr.ru/video?vid=cpapXGq50UY&hosting=youtube。
我认为播放器不太可能只支持 YouTube 并将hosting
GET 参数更改为coub和vimeo :
因此,似乎取决于 GET 参数的值,服务器向 YouTube、Vimeo 或 Coub APIhosting
执行 HTTP 请求,下载有关视频的元数据( GET 参数),对其进行解析,并返回带有此视频的播放器 HTML 页面和填充的 Open Graph 元标记。file_get_contents()
vid
vid
GET 参数是一个注入点,可以控制 file_get_contents()
. 我也可以使用路径遍历 ( /../
) 和有用的符号 ( ?
, #
, @
)。
更有趣的是,在 Vimeo 服务器向http://vimeo.com/api/v2/video/VID.php发出请求的情况下,您可以在之前的屏幕截图中注意到它,事实证明,当您使用.php
路径中的扩展名,Vimeo 返回的不是 JSON 字符串,而是序列化的字符串!
我假设在file_get_contents()
服务器使用unserialize()
来自 Vimeo 的响应之后:
哇,我这里有不安全的反序列化吗?只要响应在 Vimeo 的控制之下,它就是安全的。
可能的情况
那时我已经看到了三种可能的情况:
file_get_contents()
逃离vimeo.com主机的 Fuzzing ,实现盲目的 SSRF 和可能不安全的反序列化;- 在vimeo.com上找到受控响应-> 可能不安全的反序列化;
- 在vimeo.com上找到开放重定向-> 盲 SSRF -> 可能不安全的反序列化。
经过数小时对vid
GET 参数的不同修改和file_get_contents()
本地的模糊测试,我没有发现任何有用的信息,并决定与几个可信赖的伙伴分享有关此发现的所有信息。
好的,第一个场景不起作用,让我们通过vimeo.com上的受控响应转到下一个场景。
具有受控响应的端点应满足以下要求:
- 200 OK HTTP 状态码;
- 可供未经身份验证的用户使用;
- 受控字符串应位于响应正文的开头(PHP 将成功解析类似这样的内容
{VALID_SER_STRING}TRASH
); - 受控字符串应支持
{ }
,存储序列化对象所需的符号"
。
以下是我在vimeo.com 上寻找所需行为的一些尝试:
- 注入不是一个有效的方法。
缺点: 404 Not Found HTTP 状态码,不支持{}
,符号。"
2.注入不是有效的格式。
缺点: 404 Not Found HTTP 状态码,不支持{}
, "
符号。
3.JS回调。
缺点:/**/
一开始,不支持{}
, "
符号。
4、直播聊天导出:
缺点:开头是日期和姓名,需要认证。
不幸的是,第二种情况也没有奏效,所以我最后的希望是在vimeo.com上找到一个开放的重定向。之前我已经在 vimeo.com 上看到了关于 HackerOne 的 2015 年公开重定向报告:https : //hackerone.com/reports/44157,所以我认为还有机会找到更多。实际上,我已经在发现受控响应期间寻找了开放重定向,但没有发现任何有用的东西。
开放重定向
一直以来,当我试图利用此漏洞时,我一直牢记在vimeo.com 上来自 Harsh Jaiswal ( @rootxharsh ) 关于 SSRF 的文章。我清楚地记得他在vimeo.com上链接了几个开放重定向来实现目标。这个发现是在大约 3 年前的 2019 年,所以当然,起初,我认为这些开放重定向是固定的。但这可能是我最后的机会,所以我就这样开始挖掘了。
由于对屏幕截图的审查较少,因此可以通过 GET 参数对端点进行指纹识别。结合这些、一些谷歌搜索和阅读 Vimeo API 文档,我能够猜出 Harsh 在他的链中使用了哪个端点。无论如何,我仍然不清楚我应该提供什么价值。
我很少在利用某些东西时向某人寻求帮助,不算几个伙伴,但因为我陷入了僵局,Harsh是我最后的关键。
在我联系他并向他提供我在该步骤中获得的信息后,他与我分享了一个有效的开放重定向链接,该链接与我假设的相同,但具有正确的 GET 参数值。从那个链接,我了解到这不是vimeo.com 上的安全问题,而只是一个功能(真的,这不是玩笑)。
好的,现在我在vimeo.com 上有一个有效的开放重定向,让我们在工作中尝试一下:
是的,我终于在我的主机上获得了 HTTP 命中。在开始反序列化之前,我决定先玩一下 SSRF:
- https://127.0.0.1
- https://127.0.0.1:22
- http://127.0.0.1:25
因为来自的响应file_get_contents()
直接指向unserialize()
我无法实现完整的 SSRF,但至少我已经拥有能够执行端口扫描的半盲 SSRF:
在了解我几乎使用了这个 SSRF 的全部潜力后,我转而利用unserialize()
.
反序列化
简而言之,成功利用 PHP 中的不安全反序列化需要什么?
- ̶C̶o̶n̶t̶r̶o̶l̶l̶e̶d̶̶i̶n̶p̶u̶t̶;̶
- 具有神奇方法的类(
__wakeup()
,__destroy()
,__toString()
等); - 对魔法方法中的攻击者功能有用,可用于文件操作、RCE、SQLi 等;
- 类已加载。
如您所见,那时我只有 4 个要求中的 1 个。我对主机上的后端代码一无所知,所以利用反序列化的唯一方法是盲目地尝试所有已知的小工具链。为此,我使用了很棒的工具PHPGGC,它是一个 PHPunserialize()
有效负载库以及一个生成它们的工具。在被利用的那一刻,它有近 90 个可用的有效载荷。其中很大一部分是针对不同的 CMS 和框架,如 WordPress、ThinkPHP、Typo3、Magento、Laravr 等,在我的情况下这些都是无用的。所以我押注于常用的库,如 Doctrine、Guzzle、Monolog 和 Swift Mailer。
我使用 PHPGGC 预先生成了所有可用的有效载荷,将它们托管在受控服务器上,然后开始暴力破解。而且……在所有情况下,我都遇到了同样的错误:
发生错误是因为在序列化字符串中存在对尚未包含的类的引用——因此触发 PHP 自动加载机制来加载该类,但由于某种原因而失败。©斯文
那时我已经接受了这个 PHP 脚本非常原始并且不包含任何我可以使用的附加类的事实。伤心,但至少我试过了。当您链接很酷的漏洞,但面对完全阻止您的事情时,通常会发生这种情况。
总结所有发现后,我去了 HackerOne 并提交了一份名为[player.seedr.ru] Semi-blind SSRF的报告,并确定邀请Harsh Jaiswal作为他在vimeo.com上的公开重定向的合作者。
基本上,这就是故事可以结束的地方。但内心有一种感觉让我彻夜难眠,告诉我这还没有结束,我应该尝试其他的。我想你知道那是什么感觉。
Kohana
我不记得确切的位置,但几天后偶然发现了一些关于 PHP 中的 use-after-free 漏洞unserialize()
。碰巧的是player.seedr.ru上的 PHP 版本已经过时,当然,我开始“研究”这个领域。在那次“研究”中,我熟悉了陈涛光的报告,他向 PHP 报告的可能有几十个问题unserialize()
。实际上,与内存相关的漏洞对我来说仍然是一个黑暗领域,但老实说,我尝试构建一些有效载荷。在对本地环境进行了一些探索之后,我返回到player.seedr.ru,将有效负载托管在受控服务器上,发送请求,然后……
“什么?设备上没有剩余空间?真的,我才刚开始?但是,等等,这看起来不像是关于设备空间的默认错误。”
顺便说一句,发生此错误可能是因为我在前几天使用扫描仪发送了太多隐藏目录和文件的请求。
ErrorException [ 2 ]: file_put_contents(/var/www/seedr.backend.v2/application/logs/2021/12/20.php): failed to open stream: No space left on device ~ SYSPATH/classes/kohana/log/file.php [ 81 ]
“用于记录的自定义类?看起来这个“原始”的 PHP 脚本仍然加载了日志记录类,很有趣。Kohana?我在Seedr的安全测试中已经看到了这个词。但是哪里?”
感谢 Burp Suite Professional,我很快在 Proxy 历史中发现了 Kohana 的第一次提及,打开了该页面,并观察到了详细的错误页面。
在这里我会做一个小题外话,给你一些关于Seedr的信息以及v2.nativeroll.tv的来源。我应该注意我可能是错的,但这是我当时的想法。
Seedr和Nativeroll都是视频广告平台。Seedr有一个老式的设计,所以我猜它是在Nativeroll之前创建的。这两个平台都被Mail.Ru Group 收购,可能以某种方式合并并在 HackerOne 上以相同的范围上市。因此,v2.nativeroll.tv /api/ 、api.seedr.ru、api-stage.seedr.ru、player.seedr.ru共享相同的代码库。希望现在更清楚了。
好的,让我们回到漂亮的错误页面。环境,包含的文件,加载的扩展——看起来很有趣。这是我在单击包含文件链接后观察到的内容:
包含近 90 个文件,实际上是加载了类似autoload.php
. Kohana 是某种 CMS 或框架吗?是的。经过一番谷歌搜索后,我发现 GitHub 存储库https://github.com/koseven/kohana/看起来已弃用:
因为v2.nativeroll.ru和api.seed.ru共享相同的代码库,所以我使用相同的有效负载(https://api.seedr.ru/<svg>)成功触发了api.seedr.ru上的错误异常并得到了同样的结果。
为了准确地在api.seedr.ru/video (我攻击的端点)上触发错误异常,我从http://vimeo.com/api/v2/video/123459.php获取结果并将描述属性的值从string
更改为array
.
a:1:{i:0;a:23:{s:2:”id”;i:123456;s:5:”title”;s:30:”London Tornado — The aftermath”;s:11:”description”;a:1:{i:0;i:1337;}s:3:”url”;s:24:”https://vimeo.com/123456";s:11:"upload_date";s:19:"2006-12-14 06:53:32";s:15:”thumbnail_small”;s:111:”https://i.vimeocdn.com/video/46783763-254c2bbf4211bd6657c59e96a682169c8e74fc56e96ebb4e0a2882b103cab878-d_100x75";s:16:"thumbnail_medium";s:112:"https://i.vimeocdn.com/video/46783763-254c2bbf4211bd6657c59e96a682169c8e74fc56e96ebb4e0a2882b103cab878-d_200x150";s:15:"thumbnail_large";s:108:"https://i.vimeocdn.com/video/46783763-254c2bbf4211bd6657c59e96a682169c8e74fc56e96ebb4e0a2882b103cab878-d_640";s:7:"user_id";i:146861;s:9:"user_name";s:11:"wordtracker";s:8:"user_url";s:29:"https://vimeo.com/wordtracker";s:19:"user_portrait_small";s:51:"https://i.vimeocdn.com/portrait/defaults-blue_30x30";s:20:"user_portrait_medium";s:51:"https://i.vimeocdn.com/portrait/defaults-blue_75x75";s:19:"user_portrait_large";s:53:"https://i.vimeocdn.com/portrait/defaults-blue_100x100";s:18:"user_portrait_huge";s:53:"https://i.vimeocdn.com/portrait/defaults-blue_300x300";s:21:"stats_number_of_likes";i:11;s:21:"stats_number_of_plays";i:122560;s:24:"stats_number_of_comments";i:12;s:8:"duration";i:32;s:5:"width";i:320;s:6:"height";i:240;s:4:"tags";s:0:"";s:13:"embed_privacy";s:8:"anywhere";}}
在脚本执行期间,htmlspecialchars()
函数期望 a string
,但得到了array
,这导致了一个带有部分公开模板和堆栈跟踪的错误异常:
正如我所想,有一个作曲家自动加载脚本。在这些包含的文件中,我强调了几个,它们在反序列化过程中很有用:
- Guzzle (/var/www/sentry/vendor/guzzlehttp/…)
- Swift Mailer (MODPATH/email/vendor/swiftmailer/…)
- Symfony (/var/www/sentry/vendor/symfony/…)
- Mustache (MODPATH/kostache/vendor/mustache/…)
- Sentry (/var/www/sentry/vendor/sentry/…)
我知道 PHPGGC 有一些 Guzzle、Swift Mailer 和 Symfony 的小工具链。在我在api-stage.seedr.ru上构建和测试有效载荷后,我得到了新的错误。例如,尝试使用 Guzzle 有效负载返回以下错误:FnStream should never be unserialized
. 这表明该脚本使用了已修补的版本:
Swift Mailer 和 Symfony 根本不起作用,在 Github 上对 Mustache 和 Sentry 代码的分析也没有任何成果,所以第三方库对我没有帮助。是时候潜入Kohana了。
在 Kohana 存储库中搜索神奇的方法,如__wakeup()
, __destruct()
,__toString()
, 是空的:
但是这个 Kohana 存储库有一个系统目录,它实际上是一个专用存储库 Kohana Core:
让我们尝试在这个存储库中搜索神奇的方法。, 几乎没有结果__destruct()
,__wakeup()
但结果__toString()
令人放心:
我短暂地忽略了这些发现,但classes/Kohana/View.php及其render()
功能立即引起了我的注意。
我应该说我过去有一些后端开发经验,尤其是 PHP。我用 Laravel 开发了一些项目,并且已经知道它的 MVC(模型-视图-控制器)模式。对于视图的渲染,Laravel 使用了一个名为 Blade 的引擎。因为这样的渲染引擎通常会加载一些模板(文件)进行渲染,所以我猜想也许我可以通过某种方式传递给我自己的文件或我自己的内容。
让我们仔细看看这个render()
函数。函数render()
接受 1 个调用的参数$file
,然后调用函数 capture():
public function render($file = NULL)
{
if ($file !== NULL)
{
$this->set_filename($file);
}
if (empty($this->_file))
{
throw new View_Exception('You must set the file to use within your view before rendering');
}
// Combine local and global data and capture the output
return View::capture($this->_file, $this->_data);
}
在我的情况下,函数render()
调用不带参数,让我绕过set_filename()
也检查目录中是否存在的$file
函数views
:
public function set_filename($file)
{
if (($path = Kohana::find_file(‘views’, $file)) === FALSE)
{
throw new View_Exception(‘The requested view :file could not be found’, array(‘:file’ => $file,));
}
// Store the file path locally
$this->_file = $path;
return $this;
}
因此我用变量调用capture()
函数:$this->_file
public function render($file = NULL)
{
...
// Combine local and global data and capture the output
return View::capture($this->_file, $this->_data);
}
正如评论capture()
功能中所说,它结合了本地和全局数据并捕获输出。例如,您可以为电子邮件呈现模板文件并在那里使用用户名作为变量。
protected static function capture($kohana_view_filename, array $kohana_view_data)
{
// Import the view variables to local namespace
extract($kohana_view_data, EXTR_SKIP);
if (View::$_global_data)
{
// Import the global view variables to local namespace
extract(View::$_global_data, EXTR_SKIP | EXTR_REFS);
}
// Capture the view output
ob_start();
try
{
// Load the view within the current scope
include $kohana_view_filename;
}
catch (Exception $e)
{
// Delete the output buffer
ob_end_clean();
// Re-throw the exception
throw $e;
}
// Get the captured output and close the buffer
return ob_get_clean();
}
capture()
函数接受 2 个参数:$kohana_view_filename
和$kohana_view_data
. 你们中的一些人可能已经发现了在反序列化过程中可能被滥用的函数:
...
try
{
// Load the view within the current scope
include $kohana_view_filename;
}
...
include()
! 它已经闻起来像 LFI 和 RCE。但是我有控制权$kohana_view_filename
吗?
是的,我愿意!因为$kohana_view_filename
在$this->_file
我的上下文中并且_file
是View类的一个属性。
class Kohana_View {
// Array of global variables protected static
$_global_data = array();
...
// View filename
protected $_file;
// Array of local variables
protected $_data = array();
...
}
在那一刻,我拥有成功的不安全反序列化的所有要素:
- 我控制了输入;
- 我有一个神奇
__toString()
的View类方法,它有一个有用的功能include()
。 - 类视图已加载。
答对了!
链接在一起
一段时间后,我在本地为 PHPGGC 创建了小工具和链,后来提交并添加到主存储库:
<?php
namespace GadgetChain\Kohana;
class FR1 extends \PHPGGC\GadgetChain\FileRead
{
public static $version = ‘3.*’;
public static $vector = ‘__toString’;
public static $author = ‘byq’;
public static $information = ‘include()’;
public function generate(array $parameters)
{
return new \View($parameters[‘remote_path’]);
}
}
<?php
class View
{
protected $_file;
public function __construct($_file) {
$this->_file = $_file;
}
}
然后我只运行 PHPGGC 并得到以下序列化对象:
在受控服务器上托管有效负载,发送请求并……
哎呀,至少这是新的东西。但我希望的是什么?我使用了 not__wakeup()
或__destruct()
分别在对象创建和销毁时触发的方法,我使用了__toString()
. 根据PHP 文档:
The __toString() method allows a class to decide how it will react when it is treated like a string. For example, what echo $obj; will print.
所以我应该以某种方式输出我的View对象。实际上不难理解我应该提供我的View对象作为 title 或 description 属性的值,这是我之前使用 anarray
触发错误异常的技巧。这是我的有效载荷的样子:
a:1:{i:0;a:23:{s:2:”id”;i:123456;s:5:”title”;s:30:”London Tornado — The aftermath”;s:11:”description”;O:4:”View”:1:{s:8:”*_file”;s:11:”/etc/passwd”;}s:3:”url”;s:24:”https://vimeo.com/123456";s:11:"upload_date";s:19:"2006-12-14 06:53:32";s:15:”thumbnail_small”;s:111:”https://i.vimeocdn.com/video/46783763-254c2bbf4211bd6657c59e96a682169c8e74fc56e96ebb4e0a2882b103cab878-d_100x75";s:16:"thumbnail_medium";s:112:"https://i.vimeocdn.com/video/46783763-254c2bbf4211bd6657c59e96a682169c8e74fc56e96ebb4e0a2882b103cab878-d_200x150";s:15:"thumbnail_large";s:108:"https://i.vimeocdn.com/video/46783763-254c2bbf4211bd6657c59e96a682169c8e74fc56e96ebb4e0a2882b103cab878-d_640";s:7:"user_id";i:146861;s:9:"user_name";s:11:"wordtracker";s:8:"user_url";s:29:"https://vimeo.com/wordtracker";s:19:"user_portrait_small";s:51:"https://i.vimeocdn.com/portrait/defaults-blue_30x30";s:20:"user_portrait_medium";s:51:"https://i.vimeocdn.com/portrait/defaults-blue_75x75";s:19:"user_portrait_large";s:53:"https://i.vimeocdn.com/portrait/defaults-blue_100x100";s:18:"user_portrait_huge";s:53:"https://i.vimeocdn.com/portrait/defaults-blue_300x300";s:21:"stats_number_of_likes";i:11;s:21:"stats_number_of_plays";i:122560;s:24:"stats_number_of_comments";i:12;s:8:"duration";i:32;s:5:"width";i:320;s:6:"height";i:240;s:4:"tags";s:0:"";s:13:"embed_privacy";s:8:"anywhere";}}
我再次更新了受控服务器上的有效负载,发送了请求,最后:
我得到了/etc/passwd
内部og:description
元标记的内容。真棒,本地文件读取比半盲 SSRF 更好,但它仍然不是 RCE。
日志
本地文件包含在现代 Web 应用程序中是如此罕见,以至于我必须记住以后可以在哪里存储我的 RCE 有效负载include()
。最常见的技术是:
- 文件上传(在我的情况下,应用程序没有这样的功能);
- 日志(apache、nginx、邮件、ssh、……);
/proc/*/fd,……;
会话文件;
如您所见,我几乎尝试了所有方法,但没有任何效果。
是时候退后一步了,即与“设备上没有剩余空间”相关的错误:
从那个错误中,我可以提取一些日志文件的路径:. 在我尝试在浏览器中打开https://api.seedr.ru/application/logs/2021/12/20.php后,出现以下错误:无法直接访问脚本。实际上,Kohana 框架中几乎每个 PHP 文件的开头都有这样一个字符串:/application/logs/2021/12/20.php
好像我无法.php
直接从浏览器访问带有扩展名的日志文件。我在暂存主机上进行了尝试:http: //api-stage.seedr.ru/application/logs/2021/12/20.php,令我惊讶的是,我收到了 404 HTTP 状态代码。我不知道是什么促使我这样做,但我将.php
扩展名更改为.log
,并且……
是的,我得到了一个巨大的日志文件,它甚至让我的 Burp Suite 冻结了一点。我应该注意到,这样的技巧在生产主机api.seedr.ru上不起作用。我只能猜测Seedr开发人员在暂存环境中进行了一些更改,以使访问日志文件更容易。但像往常一样,它导致了一个安全问题。
还有一次,我打开了一扇新门,开始探索它。你还记得我第一次是如何触发 Error 异常的吗?这是日志文件中有关它的记录:
在对日志文件进行简短分析后,我用这样的记录毒化了它:
使用 PHPGGC,我创建了一个带有_file属性的新序列化视图对象,将其托管在受控服务器上,发送请求并收到以下错误:/var/www/t1.seedr.backend/application/logs/2021/12/20.log
似乎是因为日志文件太大(> 200000 行),某些函数在其中一个符号上失败?
,引发异常并停止脚本的执行。从PHP 文档中我了解到:
因为 12 月 20 日的日志文件被我不成功的有效载荷破坏了,所以该主机上的所有其他测试都没有用,所以我转移到了本地环境。使用日志文件进行数小时的调试和测试include()
并没有产生预期的结果。
在早上洗澡的时候,我想起了来自Charlese Fol Laravel <= v8.4.2 调试模式的另一篇很棒的文章:远程代码执行 (CVE-2021–3129)。它使用具有多个base64解码功能的技术,忽略不base64。首先,我从Orange Tsai 博客中了解到它。我的想法是使用多个 base64 编码的 PHP 有效负载来毒化日志,然后convert.base64-decode
在函数内使用多个过滤器对其进行编码,include()
以绕过带有?
符号的异常。但是因为那是一个不眠之夜,我的大脑无法正常工作,我完全忘记了在 Laravel 案例中它滥用file_get_contents()
了file_put_contents()
函数链内部具有相同的参数,这允许 Charlese 重写日志。我也忘记了这个限制:
由于可预测的日志文件路径/application/logs/2021/12/20.log
(
我将所有收集到的信息发布到 H1 报告中,并在 12 月 21 日之前有一整天的时间。没有浪费时间,我试图利用我在生产环境api.seedr.ru上的发现,因为我最后的所有测试都是在api-stage.seedr 上进行的。唔。还有一次,在 PHPGGC 的帮助下,我创建了一个带有属性的View对象,将它托管在受控服务器上,然后……我没有在响应中观察到文件的内容。我在api-stage.seedr.ru上重复了相同的步骤,一切正常。“糟糕,它只适用于暂存环境吗?”_file/etc/passwd/etc/passwd
空字节
在这里我必须承认,当我使用 PHPGGC 生成序列化对象时,我对其进行了一些修改:
*_file
字符串真的有 8 个符号吗?不,它只有 6 个。这是我每次修改的内容,它在api-stage.seedr.ru 上完美运行。后来在堆栈跟踪中,我注意到以下内容:
protected_file
属性的值为 NULL,但由于某种原因,View对象具有我的有效负载的公共*_file
属性。可能 PHP 专家已经了解了这种行为的原因,但我不得不花一些时间来处理这个问题。
您可以从我使用https://webhook.site/存储有效负载的屏幕截图中注意到,这是接收传入 HTTP 连接和托管有效负载的快速简便的解决方案。不幸的是,那次它对我开了个坏玩笑。问题是将受保护的值存储在序列化字符串中 PHP在“*”符号周围使用空字符( \0 ) ,这就是为什么*_file
有 8 个符号:
因为我只是将有效负载复制粘贴到webhook.site,所以它没有存储这些空字符并传递给unserialize()
public 属性*_file
。为了解决这样的问题,我只是在我的服务器上托管了一个带有空字节的序列化字符串。现在vimeo.com将请求重定向到我的服务器,在那里我只echo()
使用空字符进行负载。在我能够观察到api.seedr.ru/etc/passwd
上的内容后,我再次返回分析下载的日志文件。
最后的毒药
日志文件有很多记录类型,但只有少数可以用于存储有效负载,其中大多数需要身份验证。即使在第一次分析日志文件时,我也注意到以下记录类型:
这种记录类型的好处是,它每条记录只存储一次有效负载,并且不像我之前的尝试那样重复多次。我还注意到一个可能的注入点:user-agent。但问题是我不知道如何在日志中生成这样的记录以及我应该访问哪个端点。我用我的 IP “grepped”了日志,发现今天的日志文件已经有我的 IP 的记录,这意味着我肯定触及了所需的端点。到那时,我的 Burp Proxy 历史记录已经超过 40000 条,因此很难找到合适的端点。将记录的时间与我的 IP 以及我当时执行的活动进行比较,我了解到该记录可能是在我使用dirsearch扫描期间生成的. 我重新运行它,一段时间后找到了生成此类记录的端点:api-stage.seedr.ru/inc。
在本地环境中,我将新的有效载荷隐藏在一个测试日志文件中,include()
它得到了 bash 命令的输出。剩下的就是等到 12 月 21 日和新的日志文件,因为在 12 月 20 日,api.seedr.ru和api-stage.seedr.ru的日志文件被我不成功的有效载荷毒害了。
第二天,我用以下请求毒化了日志:
生成有效负载,托管在服务器上,发送请求……
是的,我忘记在本地测试后更改$argv[1]
为$_GET[1]
……期待再等一天我记得今天我在api-stage.seedr.ru 上再尝试一次:
流程图:
感谢:
- @rootxharsh用于共享开放重定向;
- @act1on3和我的个人 PHP 专家成为我的橡皮鸭。
参考:
https://infosecwriteups.com/vimeo-ssrf-with-code-execution-potential-68c774ba7c1e
https://github.com/ambionics/phpggc
https://www.ambionics.io/blog/laravel-debug-rce
http://blog.orange.tw/2018/10/
转载请注明出处及链接