白帽故事 · 2023年7月18日 0

CVE-2023-36934:MOVEit Transfer SQL注入分析

背景介绍:

MOVEit Transfer是一种广泛使用的 Web 应用程序,最近由于一系列 SQL 注入 (SQLi) 漏洞而受到密切关注。这些漏洞影响已发布的 MOVEit Transfer 的各个版本。利用该漏洞可能会授予对 MOVEit Transfer 数据库的未经授权的访问,从而允许攻击者操纵和泄露敏感信息。

补丁分析:

反编译了未打补丁和打补丁版本的 DLL 文件,并对反编译的 C# 代码进行了 Git diff,可以方便地比较未修补版本和修补版本之间的差别。

可以注意到 UserEngine::UserProcessPassChangeRequest() 函数发生了变化,该函数可以从Humans.aspx和machine.aspx中调用,经过仔细检查,很明显,如果可以控制 MyLoginName 变量,则很容易受到 SQL 注入攻击。

最初,通过请求中的 Arg01 参数注入Payloads,可能会出现 SQL 注入,然而,MOVEit 通过 SILUtility.XHTMLClean() 函数来降低风险,从而有效防止任何成功利用此 SQLi 漏洞的行为。

有了这种理解,研究人员重点转向寻找一种绕过清理并以未经清理的方式操纵 Arg01 参数的方法,因此了解 MOVEit 软件如何处理请求非常重要。

对于 human.aspx,参数分配是通过 siGlobs.GetIncomingVariables() 进行的,此函数扫描请求并解析可用参数,包括来自表单帖子、查询字符串、多部分表单、通过“ep”参数加密的查询字符串的参数(如果参数先前在会话中设置为变量)。

所有提到的函数调用主要涉及解析提供的参数并通过使用 XHTMLClean() 方法进行清理,然后,清理后的值存储在 siGlobs 对象中。

此过程的最后一步是尝试从数据库检索会话变量,其背后的逻辑如下:如果通过 cookie (ASP.NET_SessionId) 提供会话,后端系统将检查之前是否为此会话设置了任何变量,如果此类变量存在,它们将覆盖当前请求中收到的参数。

随后的过程涉及确定这些变量在数据库中的存储点,在相关功能中, SaveArgumentsToSessionForRedirect() 由于与未经身份验证的操作相关而脱颖而出。

尝试1:

经过一些 grep 和阅读代码后,研究人员偶然发现了一个引起注意的特定代码片段,该代码段从密码变量中设置了 Arg01 的值,在检查代码后,密码没有经过清理,这意味着在这种情况下 Arg01 将被分配一个完全未经清理的值。

siGlobs.FromSignon = "1";
siGlobs.Transaction = "msgpassword";
siGlobs.Arg01 = siGlobs.Password;
siGlobs.Arg02 = text3;
siGlobs.Password = "";
siGlobs.Arg07 = "";
siGlobs.SaveArgumentsToSessionForRedirect();

然而,研究人员遇到了一个复杂的情况,此代码片段还为transaction和 FromSignon 变量赋值,这破坏了预期的代码流,如果提供的transaction参数被值“msgpassword”覆盖,它会阻止执行“passchangerequest”,尽管如此,研究人员仍决定验证此代码是否确实可以为 Arg01 保存未经处理的值并可能触发 SQL 注入,特别是当transaction变量不是在数据库中设置而是通过研究人员的参数值设置时。

为了确认这一点,研究人员设置了 Rider debugger,并手动从会话的数据库中删除了transaction变量,并观察到虽然 Arg01 中现在存在特殊字符,但并未发生对 UserEngine::UserProcessPassChangeRequest() 的函数调用,当从 Arg01 中删除特殊字符时,函数调用可以正常工作,经过几个小时的调试和研究后,研究人员意识到这是一个死胡同,需要探索其它替代方法。

尝试2:

在探索对 UserProcessPassChangeRequest 函数的其它调用时,研究人员在 SILMachine.cs (machine.aspx) 中遇到了对同一函数的另一个调用:

但是,清理过程再次应用于 LoginName 变量…研究人员决定重新访问 git diff 来寻找其它线索,他们有了一个非常有希望的发现——用户名的清理不一致,清理后,调用 UrlDecode(),但它应该在清理过程之前发生,这种不一致的顺序同样会影响 LoginName 变量。

这种清理顺序的不规则性仅在 GetEncryptedQueryParameters() 函数中观察到,这为研究人员创造了一条潜在的路径,可以利用加密参数 ep 在 siGlobs 对象中分配 LoginName 值(URL 解码),同时传递transaction参数(可能通过 POST 参数),因为不允许通过查询(加密或未加密)参数使用transaction。

POST /machine.aspx?ep={encrypted{Username=sql%27injection}} HTTP/2
Host: 192.168.29.73
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Content-Type: application/x-www-form-urlencoded
Content-Length: 29

Transaction=passchangerequest

然而这一方案中研究人员意识到 Machine.aspx 页面不支持加密查询参数,该功能仅限于 human.aspx。

困境:

此时,研究人员有两条可能的路径来探索使用 human.aspx 及其加密参数功能:

1.找到一种方法来设置会话变量,其中包含事务“passchangerequest”并通过加密参数进行未净化的 LoginName

2.找到一种方法,在不设置transaction的情况下,通过加密参数设置未加密的会话变量LoginName

经过一番研究后,研究人员排除了后一种情况的可能性。

最后尝试:

设置调试器后,研究人员检查了 SaveArgumentsToSessionForRedirect() 函数的每次调用,消除了不符合的选项,在此过程中,一个特定的调用引起了研究人员的注意,它深深地隐藏在一系列 if/switch 语句中,在调试器中仔细检查后,transaction和 LoginName 变量完全按照研究人员的期望设置,然而,在代码中达到这一点时仍然遇到了困难。

try
{
  string value3 = siGlobs.MyRequest.Cookies["InitialPage"].Value;
  siGlobs.objDebug.Log(60, string.Format("{0}: InitialPage cookie found: {1}", "Human_Main", value3));
  if (Operators.CompareString(value3, CallingPage, TextCompare: false) == 0)
  {
    break;
  }
  string text6 = "";
  string text7 = value3.ToLower();
  if (Regex.IsMatch(text7, "[a-z0-9]+\\\\.aspx"))
  {
    if (Operators.CompareString(text7, "certtouser.aspx", TextCompare: false) == 0)
    {
      if (!SILUtility.StrToBool(siGlobs.FromCertToUser))
      {
        text6 = MyTarget.Substring(0, MyTarget.LastIndexOf("/") + 1) + value3;
      }
    }
    else
    {
      text6 = MyTarget.Substring(0, MyTarget.LastIndexOf("/") + 1) + value3;
    }
    if (Operators.CompareString(text6, "", TextCompare: false) != 0)
    {
      siGlobs.objDebug.Log(60, string.Format("{0}: Redirecting to {1} due to InitialPage cookie", "Human_Main", value3));
      siGlobs.SaveArgumentsToSessionForRedirect();
      siGlobs.CleanupVariables();
      siGlobs.MyResponse.Redirect(siGlobs.MakeEncryptedURLIfNec(text6));
    }

此特定调用要求调用页面以外的值设置 InitialValue cookie,在本例中为 humans.aspx,这正是阻止调用此函数的缺失环节。

发出此请求后,将收到一个会话 ID,最重要的是,该 ID 会将transaction参数设置为“passchangerequest”,并将 LoginName 参数设置为 SQL Payloads,这刚好符合了 SQL 注入漏洞的利用。

现在,剩下的唯一任务是在 machine.aspx 请求中设置会话 cookie 并观察执行情况,在此阶段,研究人员预计会话变量将被成功设置,从而由于注入的 SQL Payloads而导致 SQL 异常错误。

利用这一点,可以进一步在数据库中插入新的活动会话,可以利用 Nuclei 执行这些连续的请求来生成系统管理会话和访问令牌。

你学会了吗?如果觉得还不错的话,欢迎分享给更多感兴趣的人~