CVE-2024-50387 & CVE-2024-50388
本文复现 PWN2OWN 2024 比赛中涉及 QNAP NAS 的两个漏洞 CVE-2024-50387 & CVE-2024-50388
ARM64 架构,存在漏洞的程序可以点击这里 下载。
CVE-2024-50387 根据官方公告 ,该漏洞影响 QNAP NAS 中的 SMB Service 插件,从 2024 年开始,QNAP 将原本内建在系统中的 SMB 服务剥离出来,成为一个单独的插件。该漏洞影响插件 4.15.x 版本,从历史更新 来看,仅影响 4.15.001 和 h4.15.001 两个版本。
根据 ZDI 在比赛中发布的信息 ,可以知道该漏洞是参数注入 + SQL 注入。考虑插件本身的功能,它主要对外提供 Web 管理接口以及 SMB 服务,而 Web 管理接口是需要权限验证的,因此猜测漏洞存在于 SMB 服务中。而 Samba 是使用非常广泛的开源软件,原版程序不太可能出现此类问题,QNAP 可能对其进行了某些修改, 从而引入了这一个漏洞。
下载最新版的 SMB 插件,和旧版进行补丁对比,发现主要是一些 lib 库发生了变化,逐个分析很快就能找到疑似漏洞点位置,位于 libauth-samba4.so
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 __int64 __fastcall sub_A880 ( int log_type, const char *a2, const char *a3, const char *remote_machine_name, int action_type, const char *append) { const char *log_bin; const char *user; const char *IP; const char *computer_name; char v17[2048 ]; memset (v17, 0 , sizeof (v17)); if ( action_type != 9 ) { log_bin = "/sbin/conn_log_tool" ; if ( a2 ) goto LABEL_3; LABEL_11: user = "Unknown" ; if ( a3 ) goto LABEL_7; LABEL_12: IP = "0.0.0.0" ; if ( remote_machine_name ) goto LABEL_8; LABEL_13: computer_name = "Unknown" ; goto LABEL_9; } log_bin = "/usr/local/samba/sbin/log_ratelimit.sh" ; if ( !a2 ) goto LABEL_11; LABEL_3: if ( *a2 == '\\' ) user = a2 + 1 ; else user = a2; if ( !a3 ) goto LABEL_12; LABEL_7: IP = a3; if ( !remote_machine_name ) goto LABEL_13; LABEL_8: computer_name = remote_machine_name; LABEL_9: snprintf ( v17, 0x800 uLL, "%s -t %d -u '%s' -p '%s' -m '%s' -i %d -n %d -a '%s' -S" , log_bin, log_type, user, IP, computer_name, 1 , action_type, append); smbrun(v17, 0LL , 0LL ); return 0LL ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 size_t __fastcall cmd_filter (const char *data) { size_t result; __int64 v3; bool v4; bool v5; int v7; result = strlen (data); if ( result > 0 ) { v3 = 0LL ; do { while ( 1 ) { v7 = data[v3]; if ( (v7 - 45 ) > 0x4D u || v7 == '/' ) break ; v4 = (v7 - 59 ) <= 5u || v7 == '[' ; v5 = !v4 && (v7 - 93 ) > 1u ; if ( !v5 || v7 == '`' ) break ; if ( result <= ++v3 ) return result; } data[v3++] = '*' ; } while ( result > v3 ); } return result; } __int64 __fastcall sub_A920 ( int log_type, _BYTE *a2, const char *a3, _BYTE *remote_machine_name, int action_type, const char *append) { __int64 v12; const char *v13; __int64 v14; const char *computer_name; const char *log_bin; const char *user; const char *IP; __int64 v20; __int64 v21; char v22[2048 ]; memset (v22, 0 , sizeof (v22)); if ( a2 && *a2 ) { v20 = _talloc_tos("../../source3/smbd/qnap_conn_log.c:104" ); v13 = talloc_strdup(v20, a2); if ( v13 ) goto LABEL_4; return 1LL ; } v12 = _talloc_tos("../../source3/smbd/qnap_conn_log.c:101" ); v13 = talloc_strdup(v12, "Unknown" ); if ( !v13 ) return 1LL ; LABEL_4: if ( action_type == 9 ) cmd_filter(v13); if ( remote_machine_name && *remote_machine_name ) { v21 = _talloc_tos("../../source3/smbd/qnap_conn_log.c:119" ); computer_name = talloc_strdup(v21, remote_machine_name); } else { v14 = _talloc_tos("../../source3/smbd/qnap_conn_log.c:116" ); computer_name = talloc_strdup(v14, "Unknown" ); } if ( computer_name ) { cmd_filter(computer_name); if ( action_type == 9 ) { log_bin = "/usr/local/samba/sbin/log_ratelimit.sh" ; if ( *v13 == '\\' ) user = v13 + 1 ; else user = v13; if ( a3 ) goto LABEL_15; } else { log_bin = "/sbin/conn_log_tool" ; if ( *v13 == 92 ) user = v13 + 1 ; else user = v13; if ( a3 ) { LABEL_15: IP = a3; LABEL_16: snprintf ( v22, 0x800 uLL, "%s -t %d -u '%s' -p '%s' -m '%s' -i %d -n %d -a '%s' -S" , log_bin, log_type, user, IP, computer_name, 1 , action_type, append); smbrun(); _talloc_free(v13, "../../source3/smbd/qnap_conn_log.c:143" ); _talloc_free(computer_name, "../../source3/smbd/qnap_conn_log.c:144" ); return 0LL ; } } IP = "0.0.0.0" ; goto LABEL_16; } _talloc_free(v13, "../../source3/smbd/qnap_conn_log.c:122" ); return 1LL ; }
这个函数是用来记录登录日志的,当 SMB 服务收到一次认证请求,成功或失败,都会调用此函数向系统 Log 数据库插入一条信息,这条信息包括时间、登录用户、客户端名称等。在旧版中,代码获取了必要参数之后,直接将它们拼接到命令中,并使用 smbrun 函数执行。而新版在拼接命令之前对相关参数进行了验证,将一些特殊字符替换为星号。
smbrun 是 Samba 内置的一个函数,用来安全的执行一条命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 int smbrun (const char *cmd, int *outfd, char * const *env) { return smbrun_internal(cmd, outfd, true , env); } static int smbrun_internal (const char *cmd, int *outfd, bool sanitize, char * const *env) { closefrom(3 ); { char *newcmd = NULL ; if (sanitize) { newcmd = escape_shell_string(cmd); if (!newcmd) exit (82 ); } if (env != NULL ) { execle("/bin/sh" ,"sh" ,"-c" , newcmd ? (const char *)newcmd : cmd, NULL , env); } else { execl("/bin/sh" ,"sh" ,"-c" , newcmd ? (const char *)newcmd : cmd, NULL ); } SAFE_FREE(newcmd); } exit (83 ); return 1 ; }
默认情况下 sanitize 参数为 true,函数会调用 escape_shell_string
去过滤命令,将一些危险字符使用反斜杠转义,因此不能直接注入新的命令。但 smbrun 不会转义单引号等字符,所以我们可以传入一些包含单引号的数据,将命令中原本的引号闭合,这样就能控制后续执行程序的参数,实现参数注入。
通过调试分析发现,发送一条 SMB 登录请求会执行 /usr/local/samba/sbin/log_ratelimit.sh
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #!/bin/sh ratelimit_bool="`/sbin/getcfg Samba " ratelimit bool" -d " TRUE" -u`" if [ "x${ratelimit_bool} " != "xTRUE" ]; then /sbin/conn_log_tool $@ exit "$?" fi ratelimit_sec="`/sbin/getcfg Samba " ratelimit sec" -d " 3" -u`" ret=0 argv="$@ " ip= machine= username= dummy= POSITIONAL_ARGS=() while [[ $# -gt 0 ]]; do case $1 in -p) ip="$2 " shift shift ;; -m) computer="$2 " shift shift ;; -u) username=`echo "$2 " | sed 's/\\\\/:/g' 2>/dev/null` shift shift ;; -*) dummy="$2 " shift shift ;; *) POSITIONAL_ARGS+=("$1 " ) shift ;; esac done keyword="${username} :${ip} :${computer} " for pid in `pidof sleep `do grep -i "CMD=${keyword} " /proc/"${pid} " /environ 2>&1 >/dev/null if [ x$? == x0 ]; then exit 1 fi done (/sbin/conn_log_tool $argv && CMD="${keyword} " sleep "${ratelimit_sec} " ) & ret="$?" exit "${ret} "
实际上最后也是执行 /sbin/conn_log_tool
,脚本只是为了限制发送日志的频率。执行的命令模式为
1 /usr/local/samba/sbin/log_ratelimit.sh -t 1 -u '<用户名>' -p '<客户端 IP 地址>' -m '<客户端主机名>' -i 1 -n 9 -a '---' -S
/sbin/conn_log_tool
是指向 /sbin/log_tool
的符号链接,分析 log_tool
程序,它会获取一系列参数,当包含 -a
参数时,会执行以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 else if ( append == 1 ){ memset (v77, 0 , sizeof (v77)); *&v77[4 ] = qword_41A560; if ( qword_41A568 ) strncpy (&v77[64 ], qword_41A568, 0x40 uLL); if ( qword_41A570 ) strncpy (&v77[129 ], qword_41A570, 0x40 uLL); if ( qword_41A578 ) strncpy (&v77[194 ], qword_41A578, 0x40 uLL); if ( qword_41A5C0 ) strncpy (&v77[1556 ], qword_41A5C0, 0x40 uLL); if ( qword_41A5C8 ) strncpy (&v77[1621 ], qword_41A5C8, 0x80 uLL); if ( qword_41A5D0 ) strncpy (&v77[1750 ], qword_41A5D0, 0xFF uLL); strncpy (&v77[259 ], qword_41A558, 0x400 uLL); *&v77[1284 ] = dword_41A580; *&v77[1288 ] = dword_41A584; *&v77[1552 ] = qword_41A5B8; if ( append_msg ) strncpy (&v77[1292 ], append_msg, 0x100 uLL); if ( verbose ) puts ("Appending a log to database..." ); if ( log_engine == 1 ) { v66 = &v77[1750 ]; v67 = &v77[259 ]; v64 = &v77[1556 ]; v65 = &v77[1621 ]; v22 = SendConnToLogEngineEx4(); } else { v22 = (naslog_conn_add2)(v77); } if ( !verbose ) return 0 ; }
在 [1]
处,判断是否传入了 -A
参数,如果是,就把这个参数值保存到结构体 v77 中。在 [2]
处,判断是否传入了 -S
参数,根据前面的命令模式知道正常情况下会进入这个 if 条件,从而执行 SendConnToLogEngineEx4
函数。
SendConnToLogEngineEx4
调用 msgsnd 向消息队列发送一条消息,最终会由 qLogEngined 进程来处理,观察到它会调用 naslog_conn_add_entries2
向数据库添加数据,这个函数定义在 libuLinux_naslog.so.2.0.0
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 memcpy ( v29, "INSERT INTO NASLOG_CONN ( conn_type, conn_date, conn_time, conn_user, conn_ip, conn_comp, conn_res, conn_serv, conn_" "action, conn_app, conn_action_result, conn_client_id, conn_client_app, conn_client_agent ) VALUES ( ?, strftime('%Y-" "%m-%d', CURRENT_TIMESTAMP, 'LOCALTIME'), strftime('%H:%M:%S', CURRENT_TIMESTAMP, 'LOCALTIME'), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? );" , 0x16A uLL); v6 = 0LL ; if ( sqlite3_open("/etc/logs/conn.log" , &v27) )return 4294967276LL ;while ( 1 ){ sqlite3_busy_timeout(v27, 20000LL ); if ( !sqlite3_prepare(v27, v29, 0xFFFFFFFF LL, &v28, 0LL ) && !sqlite3_exec(v27, "BEGIN TRANSACTION;" , 0LL , 0LL , 0LL ) ) break ; sqlite3_close(v27); if ( v6 == 6 ) return 4294967267LL ; if ( v6 ){ ++v6; unlink("/etc/logs/conn.log" ); naslog_conn_create_tbl(0 , 0 ); } else { ++v6; } if ( sqlite3_open("/etc/logs/conn.log" , &v27) ) return 4294967276LL ; }
但是这个函数拼接 SQL 语句时使用了预编译,无法进行 SQL 注入,因此漏洞点可能不在这一条路径上面。
回到 log_tool
中,当没有传入 -S
参数时,代码会在 [4]
处调用 naslog_conn_add2
,这个函数也位于 libuLinux_naslog.so.2.0.0
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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);" , v11, v1, a1 + 129 , a1 + 194 , v78, v12, v13, v56, v57, v58, v59, v60); if ( sqlite3_open("/etc/logs/conn.log" , &v65) ){ sqlite3_free(v14); return 4294967276LL ;} else { sqlite3_busy_timeout(v65, 20000LL ); LODWORD(modes) = sqlite3_exec(v65, v14, 0LL , 0LL , 0LL ); }
那么很明显,函数在拼接 SQL 语句时使用了不安全的 %s
参数,是存在 SQL 注入风险的。其中 INSERT 语句的 conn_app
列就是外部传入的 -A
参数值。
到这里我们就找到了公告中提到的两个漏洞点,参数注入导致 SQL 注入,但是还存在一些关键问题需要解决。
执行 log_tool
命令时默认是带有 -S
参数的,导致无法调用到 naslog_conn_add2
函数,不能触发 SQL 注入。
假设可以触发 SQL 注入,考虑到目标数据库是 sqlite,利用思路应该是写 web 后门。那么这个后门应该写入到系统哪个位置才能触发。
第一个问题,观察 sub_A880
拼接命令的代码,使用了 snprintf 函数,最大长度为 0x800。那么第一个思路是能否通过传入超长的用户名或主机名,让格式化字符串中的 -S
参数被截断。查看 Samba 源代码发现,用户名最大长度为 0x200,主机名最大长度为 256,因此无法截断参数。
另一个思路就是从命令本身出发,能否传入某些字符将后续的参数屏蔽。我想到使用 #
字符来注释后续参数,形成类似如下的命令
1 /usr/local/samba/sbin/log_ratelimit.sh -t 1 -u 'test' -A "sql_payload" -p '127.0.0.1' -m 'linux' -i 1 -n 9 -a '---'
但 escape_shell_string
会在 #
前面也添加反斜杠转义,导致注释失败。
smbrun 最终是调用 sh -c
执行命令的,所以可以考虑一些 SHELL 中特性,当在一条命令中使用 --
,即两个短横线时,表示命令选项结束,后续的内容会被认为是普通数据。所以我们可以构造这样的命令
1 /usr/local/samba/sbin/log_ratelimit.sh -t 1 -u 'test' -A "sql_payload" -p '127.0.0.1' -m 'linux' -i 1 -n 9 -a '---' -- 'dummy' -p '192.168.x.x' -m 'xxx' -i 1 -n 9 -a '---' -S
这样从 dummy 开始的内容都不会被认为是命令选项,就可以屏蔽 -S
参数,最终使 log_tool
调用 naslog_conn_add2
函数。
后续的利用思路就很简单了,在 -A
之后插入 SQL 注入的 payload,可以写入 PHP 后门文件。那么应该将后门放在哪里才能解析?
本博客曾经复现过 QNAP 的一个 SQL 注入漏洞,当时提供的思路为结合 MusicStation 或 VideoStation 等 PHP 编写的插件来利用。实际上在系统本身的 Web 服务中,某些路径下也可以解析 PHP 代码。例如 /mnt/ext/opt/QuLog/opt/www/
,在该目录下放置一个 PHP 文件,访问即可执行且不需要进行身份验证。
那么简单总结一下该漏洞的利用条件和思路
首先需要能够访问到系统的 SMB 以及 Web 服务,并且这个 SMB 服务应该是 SMB Service 插件启动的
利用参数注入漏洞,通过用户名向命令中插入 -A
以及 --
,在 -A
参数中布置 SQL 注入 payload
触发 SQL 注入漏洞,向 /mnt/ext/opt/QuLog/opt/www/
目录下写入了 PHP 后门文件
访问后门实现任意命令执行
CVE-2024-50388 根据官方公告 ,该漏洞影响 QNAP NAS 中的 HBS3 插件,这个插件是用来将本地和云端数据进行同步的,也是 QNAP NAS 中使用非常广泛的一个插件。根据 ZDI 在比赛中发布的信息 ,可以知道这是一个命令注入漏洞。
依旧是下载新版插件进行补丁对比,HBS3 的大部分组件都是用 Python 编写的,而新版本中这些文件被编译成 python3.11 版本的字节码,目前似乎还没有能够完全反编译的工具。不过通过分析,该漏洞可能并不是位于 python 代码中,对比发现新版的 rsync 等程序也发生了变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 sprintf (v22, "%s %s \"%s\" %s \"%s\"" , "/sbin/rsync_util" , "-u" , v19, "-s" , v20);v14 = popen(v22, "r" ); v15 = v14; if ( v14 ){ if ( fgets(v21, 64 , v14) ){ v16 = strchr (v21, 10 ); if ( v16 ) *v16 = 0 ; v17 = strtol(v21, 0LL , 10 ); } else { v17 = -6 ; } pclose(v15); } else { return -6 ;} return v17;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 sprintf (s, "%s %s \"%s\" %s \"%s\"" , "/sbin/rsync_util" , "-u" , en_username, "-p" , password);result = mkstemp(templatea); if ( result != -1 ){ close(result); v3 = 0 ; sub_42C100(templatea, 0LL , 0LL , "/sbin/rsync_util" , "-u" , en_username, "-p" ); v4 = fopen(templatea, "r" ); if ( v4 ){ while ( !feof(v4) ) { while ( fgets(nptr, 65 , v4) ) { v5 = strchr (nptr, 10 ); if ( v5 ) *v5 = 0 ; v3 = strtol(nptr, 0LL , 10 ); if ( feof(v4) ) goto LABEL_8; } } LABEL_8: fclose(v4); } unlink(templatea); return v3;} return result;
很明显,旧版代码拼接命令之后直接使用 popen 来执行,而新版改为使用 sub_42C100
函数,其内部实际上是调用 execve 参数化执行,避免能够直接注入命令。
rsync 这个服务默认情况下是关闭的,需要手动在 HBS3 插件中开启,开启之后程序会监听 873 端口。
下面需要简单了解一下 rsync,弄清楚在什么情况下可以触发到这个命令注入。rsync 是一种 Linux 下的远程数据同步工具,可以快速在两台主机之间同步文件,能够用来实现文件备份等操作,这种服务可能在企业场景下会有更多使用。
它在服务端和客户端之间使用 “rsync 算法” 来同步文件,由于某些原因,两端通信使用的协议并没有被标准化,而且实现了 rsync 的开源项目也比较少,可以参考 samba 提供的文档 以及抓包来大致了解协议结构。
首先我们分析一下漏洞点,它是在 auth_server
函数中被调用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 if ( RConf_Get_Field("/etc/config/rsyncd.conf" , 4705734LL , "HBS3 AuthMode" , auth_mode, 64LL ) ) sub_45AB70(auth_mode, 64LL , 0x472BE2 LL, "RSYNC" ); if ( !strcmp ("RSYNC" , auth_mode) || !strcmp ("RSYNC_AND_NAS" , auth_mode) ){ v55 = qword_6A5DC0; memset (dest, 0 , sizeof (dest)); if ( !qword_6A5DC0 ) v55 = "/etc/config/rsyncd.conf" ; strncpy (dest, v55, 0x1000 uLL); if ( RConf_Get_Field(dest, 4705734LL , "rsync user" , rsync_user, 65LL ) ) strcpy (rsync_user, "rsync" ); if ( RConf_Get_Field(dest, 4705734LL , "rsync enpswd" , rsync_encpwd, 513LL ) ) { if ( RConf_Get_Field(dest, 4705734LL , "rsync pswd" , rsync_pwd, 129LL ) ) { strncpy (src, "rsync" , 0x81 uLL); } else { sprintf (command, "/sbin/get_encstr %s e 2>/dev/null" , rsync_pwd); v56 = popen(command, "r" ); v57 = v56; if ( v56 ) { fgets(rsync_encpwd, 513 , v56); pclose(v57); } rsync_encpwd[strlen (rsync_encpwd)] = 0 ; sub_42B690(dest, 4705734LL , "rsync enpswd" , rsync_encpwd); sub_42B740(dest, 4705734LL , "rsync pswd" ); v58 = Decrypt_Rsync_Password(rsync_encpwd, src, 129LL ); v59 = 342 ; if ( v58 < 0 ) goto LABEL_77; } } else { v58 = Decrypt_Rsync_Password(rsync_encpwd, src, 129LL ); if ( v58 < 0 ) { v59 = 349 ; LABEL_77: sub_429040(3LL , "decrypt password fail (%d), L%d\n" , v58, v59); return __strdup(username); } } *v97 = 0LL ; v98 = 0LL ; v99 = 0LL ; v100 = 0LL ; memset (v106, 0 , sizeof (v106)); v73 = strcpy (v106, src); sub_456A90(v73, v96, v97); if ( !strcmp (rsync_user, username) && !strcmp (v97, password) ) { sub_42C840(0 , a5, 0LL , a7, a8, a9, a10, v53, v54, a13, a14); return __strdup(username); } if ( auth_mode[0 ] != 'N' ) goto LABEL_109; } else if ( auth_mode[0 ] != 'N' ){ goto LABEL_110; } if ( auth_mode[1 ] == 'A' && auth_mode[2 ] == 'S' && !auth_mode[3 ] ) goto nas_auth; LABEL_109: if ( !strcmp ("RSYNC_AND_NAS" , auth_mode) ){ nas_auth: v75 = dword_6A6074; v74 = dword_6A5EC4; LABEL_112: v76 = sub_4105C0(); sub_429040(6LL , "%s:L%d [%s] check nas account, d[%d] s[%d]\n" , "auth_server" , 374 , v76, v74, v75); if ( (a3 != -1 || dword_6A6070 || dword_6A6074 != 1 || dword_6A5EC4 != -1 || dword_694F1C != 1 || dword_6A6078) && (check_nas_pwd(username, password) || sub_42B900(username) != 1 ) ) { sub_42C840(1 , a5, username, a7, a8, a9, a10, v77, v78, a13, a14); return 0LL ; } }
根据函数名称以及部分代码推断,进行 rsync 身份验证时可能会调用到漏洞函数 (check_nas_pwd
),而且传入的参数为用户名和密码。
通过抓包和静态分析代码,客户端在连接 rsync 服务端进行身份验证时,可能会进行以下几个步骤
服务端发送 @RSYNCD
,表明自己的版本,以及支持的身份验证 hash 算法
客户端发送 @RSYNCD
,表明自己的版本,选定一个 hash 算法
服务器发送 AUTHREQD
,要求密码认证
客户端将账户信息发送到服务端
因此可以推断,客户端发送用户名和密码时,就会调用到漏洞函数,如果在用户名中直接构造命令注入 payload,就能实现任意命令执行。所以利用该漏洞的条件和思路应该是
设备开启了 rsync 服务,能够访问到 873 端口
向服务器发送认证信息,在用户名处构造命令注入 payload
IoC 利用这些漏洞可能会在系统中留下日志信息,我在这里做一些简单的记录。
利用 CVE-2024-50387 SMB 漏洞时,如果在 QuLog Center 系统日志中心开启了 SMB 日志记录,可能会看到如下日志信息
1 警告 2024-11-20 18:00:00 WORKGROUP\test 192.168.0.100 linux --- --- SMB --- 登录失败
或者查看 /var/nc.log
是否存在 SMB 登录失败等信息。不过考虑到攻击者可以注入任意参数,或者在获取系统权限之后擦除日志,所以以上仅能作为简单的参考,还可以排查 WEB 路径下是否多出了未知的 PHP 文件。
系统日志中心似乎并不记录和 rsync 相关的日志,因此排查 CVE-2024-50388 的利用可以关注设备是否外联了异常 IP 地址,系统是否存在可疑文件等。
值得一提的是,两个漏洞分别影响 SMB 和 rsync 服务,这些服务可能不会开放在公网环境,受影响的插件版本也有限。此外,QNAP 在漏洞被发现后迅速采取了行动,推出新版本进行修复。而且,当 NAS 连接网络时,还会自动下载 Hot Patch 作为临时缓解方案。在这些努力下,这些漏洞被利用的可能性较低,但依然建议用户将相关组件升级到最新版,确保安全。
参考链接 https://unix.stackexchange.com/questions/11376/what-does-double-dash-double-hyphen-mean
https://www.qnap.com/en/security-advisory/qsa-24-41
https://www.qnap.com/en/security-advisory/qsa-24-42
https://www.bleepingcomputer.com/news/security/qnap-patches-second-zero-day-exploited-at-pwn2own-to-get-root/
https://rsync.samba.org/how-rsync-works.html