четверг, 9 декабря 2021 г.

How Acunetix addresses HTTP/2 vulnerabilities

  (It's a repost from https://www.acunetix.com/blog/web-security-zone/how-acunetix-addresses-http-2-vulnerabilities/)  

In the latest release of Acunetix, we added support for the HTTP/2 protocol and introduced several checks specific to the vulnerabilities associated with this protocol. For example, we introduced checks for misrouting, server-side request forgery (SSRF), and web cache poisoning. In this article, we’d like to explain how these vulnerabilities happen so that you can understand the logic behind the checks.

An introduction to HTTP/2

To understand HTTP/2, it’s best to compare it with its predecessor, HTTP/1.x.

How HTTP/1.x works

HTTP/1.x is a text-based protocol. An HTTP request consists of headers and possibly a body. To separate headers between themselves as well as headers from the body, you use the character sequence \r\n (CRLF).

The first header is the request line, which consists of a method, a path, and a protocol version. To separate these elements, you usually use whitespaces. Other headers are name and value pairs separated by a colon (:). The only header that is required is Host.

The path may be represented in different ways. Usually, it is relative and it begins with a slash such as /path/here, but it may also be an absolute URI such as http://virtualhost2.com/path/here. Moreover, the hostname from the path takes precedence over the value of the Host header.

GET /path/here HTTP/1.1
Host: virtualhost.com
Other-header: value

When the web server receives an HTTP/1.x request, it parses it using certain characters as separators. However, due to the fact that HTTP is an old protocol and there are many different RFCs dedicated to it, different web servers parse requests differently and have different restrictions regarding the values ​​of certain elements.

How HTTP/2 works

HTTP/2, on the other hand, is a binary protocol with a completely different internal organization. To understand its vulnerabilities, you must know how the main elements of the HTTP/1.x protocol are now represented.

HTTP/2 got rid of the request line and now all the data is presented in the form of headers. Moreover, since the protocol is binary, each header is a field consisting of length and data. There is no longer a need to parse data on the basis of special characters.

HTTP/2 has four required headers called pseudo-headers. These are :method, :path, :scheme, and :authority. Note that pseudo-header common names start with a colon, but these names are not transmitted – instead, HTTP/2 uses special identifiers for each.

  • :method and :path are straight analogs of the method and path in HTTP/1.1.
  • :scheme is a new header that indicates which protocol is used, usually http or https.
  • :authority is a replacement for the Host header. You are allowed to send the usual Host header in the request but :authority has a higher priority.

Misrouting and SSRF

Today’s web applications are often multi-layered. They often use HTTP/2 to interact with user browsers and HTTP/1.1 to access backend servers via an HTTP/2 reverse proxy. As a result, the reverse proxy must convert the values ​​received from HTTP/2 to HTTP/1.1, which extends the attack surface. In addition, when implementing HTTP/2 support in a web server, developers may be less strict about the values ​​in various headers.

Envoy Proxy

For example, when I was doing research for the talk “Weird proxies/2 and a bit of magic” at ZeroNights 2021, I found that the Envoy Proxy (tested on version 1.18.3) allows you to use arbitrary values ​​in :method, including a variety of special characters, whitespace, and tab characters. This makes misrouting attacks possible.

Let’s say that you specify :method to be GET http://virtualhost2.com/any/path? and :path to be /. Envoy sees a valid path / and routes to the backend. However, when Envoy creates a backend request in the HTTP/1.x protocol format, it simply puts the value from :method into the request line. Thus, the request will be:

GET http://virtualhost2.com/any/path? / HTTP/1.1
Host: virtualhost.com

Depending on the type of backend web server, it can accept or reject such a request (because of the extra space). In the case of nginx, for example, this will be a valid request with the path /any/path? /. Moreover, we can reach an arbitrary virtual host (in the example, virtualhost2.com), to which we otherwise would not have access.

On the other hand, the Gunicorn web server allows arbitrary values ​​in the protocol version in the request line. Therefore, to achieve the same result as with nginx, we set :method to GET http://virtualhost2.com/any/path HTTP/1.1. After Envoy processes the request, it will look like this:

GET http://virtualhost2.com/any/path? / HTTP/1.1 / HTTP/1.1

Haproxy

A similar problem exists in Haproxy (tested on version 2.4.0). This reverse proxy allows arbitrary values ​​in the :scheme header. If the value is not http or https, Haproxy puts this value in the request line of the request sent to the backend server. If you set :scheme to test, the request to the web server will look like this:

GET test://virtualhost.com/ HTTP/1.1
Host: virtualhost.com

We can achieve a similar result as for Envoy by simply setting :scheme to http://virtualhost2.com/any/path?. The final request line to the backend will be:

GET http://virtualhost2.com/any/path?://virtualhost.com HTTP/1.1

This trick can be used both to access arbitrary virtual hosts on the backend (host misrouting) and to bypass various access restrictions on the reverse proxy, as well as to carry out SSRF attacks on the backend server. If the backend has an insecure configuration, it may send a request to an arbitrary host specified in the path from the request line.

The latest release of Acunetix has checks that discover such SSRF vulnerabilities.

Cache poisoning

Another common vulnerability of tools that use the HTTP/2 protocol is cache poisoning. In a typical scenario, a caching server is located in front of a web server and caches responses from the web server. To know which responses are cached, the caching server uses a key. A typical key is method + host + path + query.

As you can see, there are no headers in the key. Therefore, if a web application returns a header in a response, especially in an unsafe way, an attacker can send a request with an XSS payload in this header. The web application will then return it in the response, and the cache server will cache the response and return it to other users who requests the same path (key).

HTTP/2 adds new flavors to this attack. They are associated with the :scheme header, which may not be included in the key of a cache server, but through which we can influence the request from the cache server to a backend server as in the misrouting examples.

The attack may also take advantage of :authority and Host headers. Both are used to indicate the hostname but the cache server may handle them incorrectly and, for example, use the Host header in the cache key, but forward the request to the backend using the value of the :authority header. In such case, :authority will be an unkeyed header and an attacker can put a payload for cache poisoning attack in it.

Cache poisoning DoS

There is also a variation of the cache poisoning attack called the cache poisoning DoS. This happens when a cache server is configured to cache error-related responses (with a response status 400, for example). An attacker can send a specifically crafted request which is valid for the cache proxy but invalid for the backend server. It’s possible because servers parse requests differently and have different restrictions.

HTTP/2 offers us a fairly universal method for this attack. In HTTP/2, to improve performance, each cookie is supposed to be sent in a separate cookie header. In HTTP/1.1, you can only have one Cookie header in the request. Therefore, the cache server, having received a request with several cookie headers, has to concatenate them into one using ; as a separator.

Most servers have a limit on the length of a single header. A typical value is 8196. Therefore, if an attacker can send HTTP/2 request with two cookie headers with a length of 5000, they do not exceed the length and will be processed by a cache server. But the cache server concatenates them into one Cookie header, so the length of the Cookie header for the backend is 10000, which is above the limit. As a result, the backend returns a 400 error. The cache server caches it and we have a cache poisoning DoS.

The latest release of Acunetix includes checks for both web cache poisoning and CPDoS via HTTP/2.

More HTTP/2 in the future

The vulnerabilities listed above are the most common HTTP/2 vulnerabilities but there are more. We plan to add more checks in future scanner releases.

If this topic is of interest to you, I recommend looking at the following papers:

пятница, 23 апреля 2021 г.

Remote debuggers as an attack vector

 (It's a repost from https://www.acunetix.com/blog/web-security-zone/remote-debuggers-as-an-attack-vector/)  

Over the course of the past year, our team added many new checks to the Acunetix scanner. Several of these checks were related to the debug modes of web applications as well as components/panels used for debugging. These debug modes and components/panels often have misconfigurations, which may lead to the disclosure of sensitive information or even to remote command execution (code injection).

As I was working on these checks, I remembered cases when I discovered that applications expose a special port for remote debugging. When I was working as a penetration tester, I often found that enterprise Java applications exposed a Java Debug Wire Protocol (JDWP) port, which would easily allow an attacker to get full control over the application.

When I was writing the new Acunetix checks, I became curious about similar cases regarding other programming languages. I also checked what capabilities Nmap has in this respect and found only checks for JDWP. Therefore, I decided to research this blind spot further.

Low-hanging fruit

Every developer uses some kind of a debugging tool but remote debugging is less common. You use remote debugging when you cannot investigate an issue locally. For example, you use it when you need to debug an enterprise Java application that is too big to develop locally and that has strong connections with the environment or processed data. Another typical scenario for remote debugging is debugging a Docker container.

A debugger is a very valuable target for an attacker. The purpose of a debugger is to give the programmer maximum capabilities. It means that, in almost all cases, the attacker can very easily achieve remote code execution once they access the remote debugger.

Moreover, remote debugging usually happens in a trusted environment. Therefore, many debuggers don’t provide security features and use plain-text protocols without authentication or any kind of restrictions. On the other hand, some debuggers make the attack harder – they provide authentication or client IP restrictions. Some go even further and don’t open a port but instead initiate the connections to the IDE. There are also cases when the programmer passes a remote connection to the debugger through SSH.

Below you can find examples of RCE attacks on various debuggers. I tried to cover all common languages but focused on the most popular debuggers only and those that are most commonly misconfigured.

Attacks on debuggers

Java(JVM)/JPDA

JPDA is an architecture for debugging Java applications. It uses JDWP, which means that you can easily detect its port using Nmap. The port is, however, not always the same – it typically depends on the application server. For example, Tomcat uses 8000, ColdFusion uses 5005.
To gain access to a shell through a successful RCE attack, I used an exploit from Metasploit: exploit/multi/misc/java_jdwp_debugger.

Also note that all other JVM-based languages (Scala, Kotlin, etc.) also use JPDA, so this presents an attacker with a wide range of potential targets.

PHP/XDebug

XDebug is different from all other debuggers described in this article. It does not start its own server like all other debuggers. Instead, it connects back to the IDE. The IP and port of the IDE are stored in a configuration file.

Due to the nature of XDebug, you cannot detect it and attack it using a port scan. However, with a certain configuration of XDebug, you can attack it by sending a special parameter to the web application, which makes it connect to our IDE instead of the legitimate IDE.

Acunetix includes a check for such a vulnerable configuration. Details of this attack are available on this blog.

Python/pdb/remote_pdb

pdb is a common Python debugger and the remote_pdb package (and other similar packages) enables remote access to pdb. The default port is 4444. After you connect using ncat, you have full access to pdb and can execute arbitrary Python code.

Python/debugpy/ptvsd

debugpy is a common debugger for Python, provided by Microsoft. There is also a deprecated version of this debugger called ptvsd.

debugpy uses a debug protocol developed by Microsoft – DAP (Debug Adapter Protocol). This is a universal protocol that may also be used for debuggers for other languages. The protocol is similar to JSON messages with a preceding Content-Length header. The default port is 5678.

Microsoft uses this protocol in VSCode so the easiest way to communicate using this protocol is by using VSCode. If you have VSCode with an installed default Python extension, all you need to do is to open an arbitrary folder in VSCode, click the Run and Debug tab, click Create a launch.json file, choose PythonRemote Attach, and enter a target IP and port. VSCode will generate a launch.json file in the .vscode/ directory. Then click RunStart Debugging and when you connect, you can enter any Python code in the Debug console below, which will be executed on your target.

Ruby/ruby-debug-ide

The ruby-debug-ide (rdebug-ide) gem uses a custom but simple text protocol. This debugger typically uses the 1234 port.

To execute arbitrary code, you can use VSCode and follow the same steps as for Python. Note that if you want to disconnect from a remote debugger, VSCode sends quit instead of detach (like RubyMine would do), so VSCode stops the debugger completely.

Node.js/Debugger

Versions of Node.js earlier than v7 use the Node.js Debugger. This debugger uses the V8 Debugger protocol (which looks like HTTP headers with a JSON body). The default port is 5858.

The Node.js Debugger allows you to execute arbitrary JS code. All you need to do is use Metasploit with the exploit/multi/misc/nodejs_v8_debugger/ module.

Node.js/Inspector

Newer versions of Node.js use the Node.js Inspector. From the attacker’s point of view, the main difference is that the WebSocket transport protocol is now used and the default port is now 9229.

You can use several methods to interact with this debugger. Below you can see how to do it directly from Chrome, using chrome://inspect.

Golang/Delve

Delve is a debugger for Go. For remote debugging, Delve uses the JSON-RPC protocol, typically on port 2345. The protocol is quite complex, so you definitely need to use, at least, delve itself (dlv connect server:port).

Go is a compiled language and I was unable to find a direct way to achieve RCE as with other languages. Therefore, I recommend that you use a proper IDE (for example, Goland) because you will have to do some debugging yourself to be able to achieve RCE. Note that the source code is not necessary but it comes in handy.

Below is an example of connecting to Delve using Goland.

Delve provides a way to invoke functions imported to an application. However, this feature is still in beta testing and it doesn’t allow to pass static strings as function arguments.

The good news is that we can change the values of local variables and pass them to a function.  Therefore, we need to pause an application in a non-runtime thread within a scope that interests us. We can use standard libraries for that.

Below you can see how to pause an application on a standard HTTP library and invoke the os.Environ() function, which returns the env of the application (possibly containing sensitive data). If you want to execute arbitrary OS commands, you need to execute exec.Command(cmd,args).Run(). However, if so, you need to find and stop in a position with variables of type String and []String.

gdbserver

The gdbserver allows you to debug apps remotely with gdb. It has no security features. For communication, it uses a special plain-text protocol – the GDB Remote Serial Protocol (RSP).

The most convenient way to interact with this debugger is by using gdb itself: target extended-remote target.ip:port. Note that gdb provides very convenient commands remote get and remote put (for example, remote get remote_path local_path), which allow you to download/upload arbitrary files.

 

понедельник, 4 января 2021 г.

Cache poisoning denial-of-service attack techniques

 (It's a repost from https://www.acunetix.com/blog/web-security-zone/cache-poisoning-dos-attack-techniques/)  

Attacks related to cache poisoning represent a clearly visible web security trend that has emerged in recent years. The security community continues to research this area, finding new ways to attack.

As part of the recent release of Acunetix, we have added new checks related to cache poisoning vulnerabilities and we continue to work in this area to improve coverage. In this article, I’d like to share with you a few techniques related to one of the new checks – Cache Poisoning DoS (CPDoS).

What Is a Cache Poisoning Denial-of-Service Attack

In 2019, Hoai Viet Nguyen and Luigi Lo Iacono published a whitepaper related to CPDoS attacks. They explained various attack techniques and analyzed several content delivery networks and web servers that could be affected by such attacks.

CPDoS attacks are possible if there is an intermediate cache proxy server, located between the client (the user) and the web server (the back end), which is configured to cache responses with error-related status codes (e.g. 400 Bad Request). The attacker can manipulate HTTP requests and force the web server to reply with such an error status code for an existing resource (path). Then, the proxy server caches the error response, and all other users that request the same resource get the error response from the cache proxy instead of a valid response.

The whitepaper presents 3 attack types that allow the attacker to force a web application to return a 400 status code:

  • HTTP Header Oversize (HHO) – when the size of a header exceeds the maximum header length
  • HTTP Meta Character (HMC) – when the header of the attacker’s request contains a special “illegal” symbol
  • HTTP Method Override (HMO) – when the header of the attacker’s request changes the verb (method) to an unsupported one

New HHO Attack Tricks

While analyzing these attacks and working on my project dedicated to reverse proxies, I’ve managed to come up with a couple of tricks that can be used to perform an HHO attack.

Basically, an HHO attack is possible when the maximum header length is defined differently in the cache proxy and the web server. Different web servers, cache servers, and load balancers have different default limits. If the cache proxy has a maximum header limit that is higher than the limit defined in the web server, a request with a very long header can go through the cache server to the web server and cause the web server to return a 400 error (which will then be cached by the cache server).

For example, the default maximum header length for CloudFront is 20,480 bytes. On the other hand, the default maximum header length for the Apache web server is 8,192 bytes. Therefore, if an attacker sends a request with a header that is 10,000 bytes long and CloudFront cache proxy passes it to an Apache server, the Apache web server returns a 400 error.

However, an HHO attack is possible even if the cache server has the same header length limit as the web server or one that is a little lower. There are two reasons for this:

  • The web server maximum header length limit is a string length limit. The web servers that I have tested don’t perform any normalization and probably don’t even parse the header before applying the length check.
  • However, cache proxies send correct (normalized) headers to the back end.

 

Same-Limit HHO Attack Example

A practical HHO attack could be performed as follows:

  1. The attacker sends a request with a header that is 8192 bytes long (including \r\n) but with no space between the header name and the value. For example:
    header-name:abcdefgh(…)
    (8192 characters in total)
  2. The cache proxy checks the length of the header and finds that it is not more than 8192 characters long. Therefore, it parses the header and disregards the missing space.
  3. Then, the cache proxy prepares the correct version of the header to be sent to the web server:
    header-name: abcdefgh(…)
    (8193 characters in total)
  4. The cache proxy does not check that the final length of the header exceeds 8192 characters and sends the header to the web server.
  5. The web server that receives the header sees that it exceeds the limit by one byte, and therefore it returns the 400 error page.

Similar-Limit HHO Attack Example

If the cache proxy maximum header length limit is a bit lower than the web server limit, we cannot use the trick described above (1 byte is not enough). However, in such a case, we can misuse another feature.

Many proxy servers add headers to requests that are forwarded to the web server. For example, X-Forwarded-For, which contains the IP address of the user. However, if the original request also contains the X-Forwarded-For header, the proxy server often concatenates the original value with the value set by the proxy server (the user IP).

This allows us to perform the following attack:

  1. The attacker sends a request with the following header:
    X-Forwarded-For: abcdefgh(…)
    (8192 characters in total)
  2. The proxy concatenates this request with its own value:
    X-Forwarded-For: abcdefgh(…)12.34.56.78
    (8203 characters in total)
  3. The proxy sends the value to the web server, which replies with an error code because the header is too long.

Depending on the type of a proxy and its configuration such added headers may be different and the lengths of added values may be different as well. You can check some of them on my project page.

The Impact of CPDoS Attacks

When we were testing our new CPDoS script on bug bounty sites, we noticed that many sites are vulnerable to such attacks. However, in some cases, the impact of the attack is questionable. This is because quite a few cache proxies are configured to cache responses with error status codes only for a few seconds, which makes it difficult to exploit.