Welcome to this introductory series on web security. I’ll use simple snippets and hands-on examples to introduce fundamentals on the topic. Most server-side code will be in Go but you’ll be able to understand it if you know any C-like language.
This first post is about the fundamentals: URI, HTTP, TLS, HTML, escaping and cookies. If you are already familiar with those concepts please feel free to skip to the next post.
It starts with a URI
Universal Resource Identifier(URI) is something you deal with every day, whether you know about them or not. URIs are strings that commonly look like “https://github.com/empijei". Don’t be fooled by the apparent simplicity of these things, they are not simple and often times can trip up experts in the field.
URIs are composed by the following parts:
// [block] means that `block` is optional [scheme:][//[userinfo@]host[:port]][/]path[?query][#fragment]
The most common schemes are
mailto are also broadly used.
This first part instruct the application that needs to use the URI on how to interpret the rest. It is not unusual to have custom schemes for specific applications, so that everything else in the URI is determined by that application and can use a custom format. One example for this is
smb which allows on some operating systems to browse remote files on other machines.
Whatever comes after the scheme depends on it, so if it is omitted the client application (the browser from now on) needs to decide a default one when the user types a URI in the address bar. Sometimes in web pages even the host is not specified, so the browser must provide a default host and potentially a prefix for the specified path to locate a resource.
We will focus on the dangerous consequences of allowing
It is transported over HTTP
When the browser needs to retrieve a resource it usually performs a TCP(not described here) connection and then, over that, an HTTP(HyperText Transfer Protocol) request to the server. Modern browsers all use HTTP 1.1 or above and the conversation usually looks like this:
Client connects to the server, and sends:
GET /hello HTTP/1.1 Host: localhost
Server reads the request and responds:
HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 11 hello world
You can try this yourself by connecting to port 80 of any HTTP server with netcat or telnet and writing the request manually (hit return twice after done typing the host).
Let’s unwrap what is going on here:
GETis the HTTP method or verb we are using. There is a wide range of possible ones, the most common being OPTIONS, GET, POST and HEAD.
/hellois the path we want. Usually this is taken from the URI as anything that comes after the host[:port] portion and before the
HTTP/1.1is the protocol we want to use to talk to the server. This implies that a “Host” header will follow.
Host: localhostis a header. Headers are always in the form
Key: value. In this case this means that on the machine we are connected to we want to talk to the host that is responding to the name “localhost”. This allows multiple virtual hosts to be served by the same machine. We will focus on the dangerous consequences of having the Host header specified so openly in the chapter on DNS rebinding attacks.
- An empty line is needed to signal that headers are finished and the request body (if any) will follow. In this case we are using “GET” and we didn’t specify a “Content-Length” header so the server starts responding as no body is expected.
Note: in HTTP lines are separated by “\r\n” and not just “\n”. While some modern servers might accept single separators it is always better to use both, so our request will end with bytes
[0x0d 0x0a 0x0d 0x0a]in this case.
The server then responds:
HTTP/1.1means the server agreed on the protocol, we can proceed.
200 OKis the status. This means everything went fine with our request. If we requested, for example, a resource that was not on the server we would have gotten
404 Not found. The standard statuses are numerous and can be found on Wikipedia.
Content-Type: text/plain; charset=utf-8is a composed header that instructs the client on how to interpret the response body. In this case it says that it is just plain text and uses UTF 8 as encoding. We will see the dangers of this header or the lack thereof in the chapter on XSS.
Content-Length: 11tells the client that the body is eleven bytes long.
- Empty line signals to the client that headers are over and the body will follow. We will see the harmful consequences of using such a simple separator mechanism in the chapter on http splitting.
hello worldis the response body.
There are also HTTP2 and QUIC in the party of protocols that are currently used, but you can assume they roughly behave like described above. The only difference is in speed and the ability for the server to push data to the client without the need for a pending request or open websocket.
It is optionally (hopefully) transported securely over TLS
Most modern web services support secure connections over HTTPS. HTTPS is nothing else than the HTTP protocol over TLS(Transport Layer Security) usually over TCP. This protocol secures the connection against data theft and data tampering, and authenticates the server to be the one we want to connect to. TLS can potentially provide mutual authentication, thus allowing the server to authenticate the client.
The TLS handshake is briefly summarized below. Please note this is not an exhaustive description of TLS and it is simplified to the point it is not precise.
- Client: Hello server, I’d like to connect securely, I support cipher suites A, B and C.
- The Server checks client suites against its own to find a common one.
- Server: Hello client, this is my certificate with my public key and I’d like to use cipher suite B.
- Client validates the server certificate:
- the current date is within the validity range (certificate is neither expired nor not yet valid);
- it is not revoked (this needs certificate revocation lists to be updated);
- the signature is valid and emitted by an authority the client recognizes(this requires the client to have some trusted root certificates);
- these steps can recursively climb up the certificate tree.
- The client and the server then exchange a shared, symmetric secret based on some of these facts:
- the client trusts who has the private key matching the public one it has validated;
- only who has the private key can decipher what is encrypted with the public one;
- only who has the private key can sign a certificate in a way that matches the public one.
- The rest of the communication is encrypted with the shared secret.
The cipher suite agreed in the first part determine some important algorithms, the most relevant are:
- how to validate the certificate
- how to encrypt the traffic
- how to check for message integrity
It’s described by HTML
HyperText Markup Language(HTML) is a twisted, fuzzy derivation of XML that describes how web pages should look and behave. Over the years HTML evolved from a markup language that allowed hyperlinks to a fully blown style-markup-code descriptor.
Here is an example:
Having data mixed with code is never a good idea. We’ll see why in the chapter on XSS.
Which requires escaping
As you probably noticed from the previous paragraph snippet, depending on the context I was writing I had to use different comments delimiters. This is due to the fact that when HTML is processed there are several parsers at play.
For example when the HTML processor sees the
<style> tag, it knows that all the content of that tag until
var scr = "</script>"; and it is necessary to do something like
var scr = "</scr" +"ipt>"; to prevent the HTML processor from switching context. We will see the proper way to do it in a few lines.
The right way to tackle this problem is to not use HTML special characters in non-HTML blocks. All characters that might be relevant for the HTML processor need to be “escaped” somehow.
For example the symbol
< will be encoded as
<. When the HTML processor sees that sequence it knows that it needs to decode it as a
var scr = "</script>"; into
var scr = "</script>" which is not ambiguous anymore.
Please note that also URIs need encoding of some characters that are not allowed (e.g. spaces are translated to
%20) or that are part of the special set (e.g.
%2f) and this depends on the position of the URI those character appear. We will see something about the consequence of this in the next chapters.
As you may imagine this requires some careful management by the programmer that is interpolating user data in a webpage, or the risk is that something that should be treated as data will be treated as code.
Stay tuned! Next post is on Cross-Site-Scripting(XSS) and will explain what happens when developers are not careful.
And can be authenticated
As you might have noticed, HTTP it is a stateless protocol. Roughly every time you need to fetch a resource you have to issue a new request and if you need to access some restricted endpoint you need to authenticate again. As you can imagine this would cause some degradation in user experience, so the platform provides some ways to get around the problem.
Cookies are a built-in mechanism: whenever a server response contains a
Set-Cookie header the browser will store the value in a so-called jar. From that moment on and until the cookie expires the browser will attach the cookie value to every request that is directed to that “endpoint” (more about this below).
When a user authenticates to the server the cookie is bound to their identity and to their credentials on the server side, so that the user won’t need to authenticate again.
Steps of a standard authentication process:
- User visits the website and might be issued a unique cookie;
- User logs into the website with their credentials;
- The server issues a new unique cookie and stores it in its database together with a reference to the user;
- Every time the user agent(usually a browser) requests a resource in the scope of the cookie it also sends the cookie along;
- When the server receives a requests it will lookup the cookie in the database and respond based on the user permissions.
The server might not use a database but might decide to sign the cookie somehow to recognize the user later. Moreover some applications might use Authorization tokens instead of cookies. These are carried over different headers or in the body of the request. For the purpose of most of the following posts you don’t need to pay too much attention in the difference between the two.
If you want to send some content to a client and display it, you will have:
- The content escaped as many times as many nested contexts it is sent in, no more, no less;
- HTML sent with HTTP;
- HTTP, with potential authentication, fetches a resource over TLS over TCP;
- The resource is described by a URI that needs escaping itself;