Anatomy of a vulnerability – cURL web download toolkit holed by authentication bug

You may not have heard of cURL, but you’ve probably used software that uses it.

It’s an open-source programming toolkit that helps you deal with writing client-side code that deals with URLs.

In the words of the project itself, “cURL groks those URLs.”

It’s popular because it’s a URL Swiss Army knife, making it easy to handle popular protocols like HTTP, SMTP, POP3 and many more. It also supports uploads, downloads, authentication, proxies, cookies and SSL/TLS.

It even supports Gopher, if you remember that far back.

One risk with an all-singing, all-dancing library, of course, is that there’s more code to go wrong.

And sometimes, even obscure bits of code you thought you’d never use might get triggered. Worse still, they might be triggerable by external circumstances you never predicted.

That’s the curly problem here.

The vulnerable code was introduced in the 7.26.0 release, when support for DIGEST_MD5 authentication was added to the cURL software.

DIGEST_MD5 is an rudimentary way of allowing you to login over an unencrypted connection, for example to an HTTP or POP3 server, without sending your actual password.

The server sends a random challenge string, or nonce, together with a bunch of other authentication-related data; you reply with a cryptographic hash of your password mixed up with that server-supplied data:

So a cracker who sniffs your reply can’t directly recover your password from it, and since the challenge is random and varies every time you log in, the cracker can’t re-use your reply later.

As an aside: avoid using DIGEST_MD5 authentication. Encrypt the entire session using TLS instead.

A cracker who sniffs a DIGEST_MD5 reply can’t re-use it directly, but he can use it to try to recover your password offline using a dictionary attack.

TLS not only prevents this, but also keeps the entire transaction secret, including the contents of your web session or email. That’s a much better security outcome.

Here’s the buggy code from a vulnerable version of cURL:

Don’t worry if you aren’t familiar with C. I’ll explain.

In C, the management and use of memory is left up to the programmer. You can use library code to help you deal safely with variable-length data, such as user-supplied text strings, or you can deal directly with memory yourself.

Above, the programmer has done the latter.

Firstly, he allocates a series of fixed-length memory blocks on the stack.

Then he copies text strings supplied by the caller of the function into those blocks, but be uses the system functions strcpy() and strcat(), which stand for “string copy” and “string concatentate” (tack one string on the end of another) respectively.

In modern code, you should never use those functions, because you can’t limit how much data they copy.

They simply duplicate every byte from the input string into the output string, until a NUL (zero) byte has been found. A NUL is how the end of a text string is denoted in C.

So, if the server sends too much data in its authentication challenge, for example an overly-long realm string (the contents of which can be whatever the server chooses), this function will stuff too much data into the buffer it uses to compute the authentication response.

A buffer overflow will result, and in this case, since the destination data blocks were allocated automatically on the stack, the function will crash when it ends.

That’s because the stack also stores the address in memory from which the function was called, so the cURL software can return there when it’s finished. The return address is overwritten in the above code if the string response get over-filled.

The fix was a simple one.

The uncontrollable strcpy() and strcat() functions have been replaced with the function snprintf(), which stands for “formatted print of string into at most n bytes”:

You can still make mistakes with snprintf, since it’s up to you to specify n, and if you aren’t careful, you may get it wrong.

But the point is that is is at least possible to restrict the output of snprintf to a known buffer size, which you simply can’t do with the old-fashioned strcpy() and strcat().

→ The updated cURL code above still isn’t perfect. The programmer should really check the return value of snprintf(), which reports how many bytes it wanted to write. If your buffer wasn’t big enough, then the output will be incomplete and therefore incorrect. You ought not to use it: increase the size of your buffer and try again, or report an error instead.

You’re probably thinking, at this point, that exploiting this vulnerability would be hard because most programs that use cURL do so in the background. They aren’t interactive.

Autoupdating software, which might use the cURL library (known as libcurl) typically comes pre-configured with a list of known-good URLs, or asks you to enter a URL at install time, and that’s that.

An attacker who could talk you unto switching your known-good autoupdate URL for a dodgy one, or who could persuade you to change from the POP3 email server you’ve always used to one you’ve never heard of, would surely find it easier to infect you simply by getting you to run his malware directly.

That’s true, but there’s still a risk.

If an attacker can redirect the requests from your autoupdater or your POP3 client, for example by fiddling with your DNS settings, or by hacking a server at the edge of your service provider’s network, he could send you off to an imposter site and attempt to exploit you from there.

→ As @kyprizel points out in the comments below, cURL only actually calls the vulnerable function from its POP3 and SMTP protocol handlers, so an HTTP request cannot directly put you in harm’s way. But cURL follows HTTP redirects, even unusual ones that send you off to a POP3 server, so a crook who can modify your HTTP replies may be able to attack you nevertheless.

Because of the buffer overflow, this could lead to a drive-by download, where cURL itself is tricked into misbehaving, cutting your informed consent out of the loop altogether.

The lessons?

  • Don’t use strcpy() or strcat().
  • Ever.
  • Use snprintf (or strlcpy() and strlcat(), or similar) instead.
  • Always check the return value of string-handling functions so you don’t end up using incorrect results.

Your next problem is to find out which software you are using, if any, includes cURL code with these bugs. (Versions of cURL from 7.26.0 to 7.28.1 inclusive are affected.)

Best start asking around…

NB. Although several Sophos products use libcurl, none of them use code from the vulnerable versions.