Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bypassing Request Body scanning with trailers #288

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions demo/trailer_bypass_attack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# HTTP Trailers Vulnerability

This folder contains the configuration, certificates and the code necessary
for demonstrating the HTTP Trailers vulnerability in Coraza WASM Project.

# Overview

The Envoy exposes set of functions for HTTP Filters, which include methods such
as:

* onRequestHeaders
* onRequestData
* onRequestTrailers

etc. (depending on the language SDK names of these methods may vary)

The onRequestData call provides two parameters:
* payload chunk
* end_of_stream

The end_of_stream parameter definition may be unclear - it is set to
true if:
* there are no more payload chunks to process
* there are no HTTP trailers
Copy link
Member

@M4tteoP M4tteoP Aug 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware of the convoluted (and lack of) documentation, but by any chance, do you have any reference about this? I just found these lines that might be making me think in that direction: https://github.com/proxy-wasm/spec/pull/42/files#diff-39c0fcbaec7f6ffb311e442b6f0774bf3c050cd9b3318f51f55c2d6c38d4976dR609-R610, but any reference to docs/code would be great

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


The second point is important as trailers are processed AFTER the payload.
Which means that under the presence of trailers, the onRequestData will
**NOT** be called with `end_of_stream = true`. It may happen with HTTP 2
requests as Envoy Proxy ignores request trailers for HTTP 1 requests.

## Coraza bug

The present implementation triggers Coraza Request Body Threat scanning
after the entire payload is collected. It is verified by checking if
end_of_stream variable is set to true.

However, when HTTP 2 request contains trailers, such scanning will be
bypassed, as Envoy instead of calling `onRequestData(..., eos = true)`
will call `onRequestTrailers(...)`. This method is not overwritten
so the scanning will not be performed at this stage and so the payload
will be sent entirely to the upstream - while holding a potential web
attack.

Since existing implementation performs scanning at the end of the stream
as well, the attack will be detected after processing the response.
The problem is that, malformed payload should not receive the server.
And that's the existing vulnerability:

```
The request payload scanning will occur to late for HTTP 2 requests
with trailers resulting in malicious payloads reaching the upstream
server.
```

# Solution

The fix implements `onRequestTrailers` method which calls `onRequestBody`
method with parameter `end_of_stream` set to true to trigger the
existing scanning implementation.

# Testing

To demonstrate the vulnerability, the testing client and server
were introduced along with example envoy configuration and certificates.

## Code

### Client

The Client is a Go client which sends an HTTP 2 POST Request to
`localhost:8080` with malicious payload containing XSS attempt.
```bash
go run client.go
```

To send a request with HTTP trailer, add `-a` argument
```bash
go run client.go -a
```

### Server

The server is a Python Flask server which starts listening for
HTTP requests on `0.0.0.0:8005`.

The server waits 2 seconds before sending the response so that
the tester can observe logs of the flask server and envoy proxy.

### Envoy Config

Envoy configuration is similar to the configuration from
`example/envoy/envoy-config.yaml` with the difference that it
includes certificates for SSL connection and HTTP2 connections.

### Certs

Sample generated certificates for testing purposes.

## Testing analysis

### Before the fix

To examine the issue, build the WASM filter without the fix
(either omit this commit or comment `OnHttpRequestTrailers`
method from wasmplugin/plugin.go).

Then:

1. Install and start flask server
```bash
pip3 install -r server/requirements.txt
python3 server/server.py
```

1. Run Envoy with the configuration
```bash
envoy -c envoy-config.yaml
```

1. Keep both terminals open to observe the log and in the
third terminal run the request

```bash
cd client
go run client.go
```

The Envoy should log information about detected XSS Attack.

The Flask server should not log anything as the request
should not reach it (although something may reach the server
as the payload is being sent to the upstream as the envoy
may not complete the scanning before payload chunks reach
the upstream - this is another issue)/

1. Now run again in the third terminal the client.go again but
this time with request trailer

```bash
go run client.go -a
```

This time, the XSS Attack will be detected by the Envoy after
2 seconds, after the Flask server returned the response.

### After the fix

Now build the WASM binary with the fix applied and redo the steps.

This time, the behaviour should not change for requests with trailers
and for requests with no trailers.
22 changes: 22 additions & 0 deletions demo/trailer_bypass_attack/certs/ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIUcJerPi++zMVmfxJbpdyVyETaBUswDQYJKoZIhvcNAQEL
BQAwazELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xEDAOBgNVBAsMB09yZ1VuaXQxFDASBgNV
BAMMC2V4YW1wbGUuY29tMB4XDTI0MDcxOTEyMDgzN1oXDTI1MDcxOTEyMDgzN1ow
azELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRUw
EwYDVQQKDAxPcmdhbml6YXRpb24xEDAOBgNVBAsMB09yZ1VuaXQxFDASBgNVBAMM
C2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAso3d
0rB3iPDIaNi5+ySU3O6CE0bjsRm+4v0tiKgwp5ZRL5KW4yb/AKY9QXjRrEbqwf84
8DNxjv3Db7oMFIpQt1gG2hPVpJafXRbhAEaSkTLuWLv+9/u/f4b0Bl/WZui9xWfP
97iKwyVIVX3/DU211Z2abzIdNkbyTavs2Ju+aZG3yyOCgSiYSRWPNSwabMGb48MO
h+CqX23FAdSlNZOJUAeBtgVA6qGl+KI6f7/4bxiZs1ZXfniEx5LWMPvROmf9rWU/
cFTnCKoyznRp3oqUzfYMCDwt5WWzcyDKRXKklI6AtdCPHVdnTO4q35+FRI7h9Uhl
U2IrU0YEIaAeo9QQtQIDAQABo1MwUTAdBgNVHQ4EFgQUcy8dpkVaaZT4Oa8sHQri
plL6LacwHwYDVR0jBBgwFoAUcy8dpkVaaZT4Oa8sHQriplL6LacwDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAhqUGSp07Lo0Ctyy+Uvudv2zeReTD
jsjsYazMxZz+XmvUXQT/a11bE9JFSR8Ok+n42QSqgCUG/LDdliR7FhMePkfcfNAA
QRu+deXRbBNSCNWg/n9AzJMNLv/xCBaaycwIYYQLiE7BYZOeAqUFQ9+9uidW82Ah
uLTa2uiiIwCQRuWfRTGfNXOLzAIuUUSMv1GQzxs/IexjW4SpHQC+YpTGDqQlBnxd
Td1yTSBfOZDPBSWlIMY5B5xMXdejDH06T5B3fX6/HzjQkjkZLIQYKDolqmF+jTPK
/69a63OLEZCfFfXBpvcbAgpQo7kw84xMu3poYiGcx/pEi2qTe2LSJKBHIg==
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions demo/trailer_bypass_attack/certs/ca.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCyjd3SsHeI8Mho
2Ln7JJTc7oITRuOxGb7i/S2IqDCnllEvkpbjJv8Apj1BeNGsRurB/zjwM3GO/cNv
ugwUilC3WAbaE9Wklp9dFuEARpKRMu5Yu/73+79/hvQGX9Zm6L3FZ8/3uIrDJUhV
ff8NTbXVnZpvMh02RvJNq+zYm75pkbfLI4KBKJhJFY81LBpswZvjww6H4KpfbcUB
1KU1k4lQB4G2BUDqoaX4ojp/v/hvGJmzVld+eITHktYw+9E6Z/2tZT9wVOcIqjLO
dGneipTN9gwIPC3lZbNzIMpFcqSUjoC10I8dV2dM7irfn4VEjuH1SGVTYitTRgQh
oB6j1BC1AgMBAAECggEABClDkX9vVGUC0VQLsYtUCiS81KTYSu5Qq+A+nsAHXOsn
OovuBvPdCYTWSfT+M0ehJ6LtDsauz32hKl9pnKjvN0v5N5KIGMKHNZeCPYwSKl7H
feXpC+/Ksgwmb7ztYDntrPC9wLDaUFtJaO9QxwdCmkHcBtoMzZEFte2weAkufNil
5TH4+JDZzdCn2EdyUqbGYxqr7jIOz9Y1XQ610Dqv2kXtQx9ToewX0nE1KtovG6C1
JUUAGKtLYbx/YGq6bZXvRMq4RDmmlaKigkDJuiVK5ffPilSi4wFkc4PTL3MSOn93
xQkInOyG92D12oLu+e0WUaxDC0KHipAqA98Nh+3UWQKBgQDY1tYC3AduOoJMOZI1
LGxqLtcALmFMfqFX1iQFpdOiW4Yv28slAstrrxLXIizAkEAhkAZTuL9m+65Z/6to
sZ48KdqTqbdsrnd3B6igk67aFAyXNSdKyd3JeBl8+wgeTK8d4HFOvIQJB8Uz7Y8u
f26BhQZct/v3PvcqV09JknhriwKBgQDSzP3pJo5itcZ/DguoyFyeVv5R628baNct
CtNXOA9yznAwfUVhT7DWTPaXULjFJLcKaVmeemZOraF6I8aZ3xqDfLo1D0LU3vdf
40vYccwFt47qk5UvmU4fdudWkzqHlQZzZg32aZLpZ3obIGqZq8AgSH4HyxKZIlM5
NjDc1j38vwKBgEUSSiGnDQbjxFWbkSM2/2HjgcEhBQVk4Ogl4luaMwvos5nTHaaw
eTPYFNxKmo7MZGFMi3dnxjB7w1IPyv4SdiEcA/A9g5wvBwb3fZOI942oDUqtN9Lu
8qMWiqfxHujn7HBL6kv2aOinfP3Jkm5xUTYYtaobQTvE5t6p6Su6aHl5AoGAN/K0
SVc0XzqDjmE58vgKrPPF2BQ2jv3Kbmf7I6D7aKsl15jH+0XdV2Nh51NDVv+hnR/M
62TtFmC7BOHN6jTuootOGJsOT9VFrqtzC+VYEwRe6B93bwSvWWaDi9TTqfyBk8s4
VXg7x7rxC5YU9OhCu87BtGvPadlUYVDisxohpZ0CgYEAuPrXMiVbp4ZW0gnoQi3G
074ZIRtylNBpBod2SakBp4ZJtYy3jDkO1dOCWhlpozGRqMPHLclgifmfeXiVfMNO
vbMFSJGUQFJqAgjEzlXTkxv60jrhBo+eII+ILBZCHTSVdF8L27PUPHQmu4okNX67
8obKQvrapWNM+Ssfk7sAlD0=
-----END PRIVATE KEY-----
1 change: 1 addition & 0 deletions demo/trailer_bypass_attack/certs/ca.srl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
23ED2E8B37935D2C40617C47B39E9C4CFB193CC9
22 changes: 22 additions & 0 deletions demo/trailer_bypass_attack/certs/server.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDpjCCAo6gAwIBAgIUI+0uizeTXSxAYXxHs56cTPsZPMkwDQYJKoZIhvcNAQEL
BQAwazELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xEDAOBgNVBAsMB09yZ1VuaXQxFDASBgNV
BAMMC2V4YW1wbGUuY29tMB4XDTI0MDcxOTEyMDg0NFoXDTI1MDcxOTEyMDg0NFow
azELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRUw
EwYDVQQKDAxPcmdhbml6YXRpb24xEDAOBgNVBAsMB09yZ1VuaXQxFDASBgNVBAMM
C2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw/65
MiG3J5aZPq7byf5DEHPXIcMTF6DJ/wpD5W0gaMKbDUhkOWqg1NBtqp+6FaHKWbRM
UIAf6qoep/jgaiaTIE0h9HzvPMCHfCZiRsxjZs2EyfJOrMOnlc5qve5JGR5uVVBi
/aOCrIgX8jnZDpfQxwI9bLPN3pT0zWiUg0i9H69LpULyvPxK2Rg1z8+lYsndkP9v
dxGH72YnTiz9zVteCZT1LE2lf4+yd0d8Wrty1tkme/Uh3y6geMkgccvrSockMBDg
Ec/mqres5Y3tqD9uFD1evd5oNPLk67BYKpOfo82v4QHA/ZSw+tu0DGGsg7RQFBEK
hKjEkKpJQsafSJB2NwIDAQABo0IwQDAdBgNVHQ4EFgQUD1vyhU1ZaIt4irkL6D7l
8sg2IQAwHwYDVR0jBBgwFoAUcy8dpkVaaZT4Oa8sHQriplL6LacwDQYJKoZIhvcN
AQELBQADggEBAFbAdEhpZvD/9uPoiG2hwBJwKjDmHFDSw7h864aMAJZf+0LUJ5cw
gd0LVeI4cN0dz1HTtZatgWDBOCDqILcfBTWKjUPHX5hxovKl/rb33SurHPA5CJ4Y
nDuYp2764dYVHF9Vvea/H0UUV6wx17jL1vE2pMYoaG8mhgdG548IIImlff7Yrl5P
m9flRW8HJoODwL8G4lB6hGdm+E5RpjhEFJz9ULXbdtnY5eDeYwq9NYr2VWeEMhqP
qxvxVbQXAY786rwgmFfS6l4QFxpaIA78NuqMJW9UonGs0TUvp184qCp6AKfzreQZ
K0+xBS0KWwAc/HCD9q+n1cyo0xL2KssKKXE=
-----END CERTIFICATE-----
17 changes: 17 additions & 0 deletions demo/trailer_bypass_attack/certs/server.csr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICsDCCAZgCAQAwazELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYD
VQQHDARDaXR5MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xEDAOBgNVBAsMB09yZ1Vu
aXQxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAw/65MiG3J5aZPq7byf5DEHPXIcMTF6DJ/wpD5W0gaMKbDUhkOWqg
1NBtqp+6FaHKWbRMUIAf6qoep/jgaiaTIE0h9HzvPMCHfCZiRsxjZs2EyfJOrMOn
lc5qve5JGR5uVVBi/aOCrIgX8jnZDpfQxwI9bLPN3pT0zWiUg0i9H69LpULyvPxK
2Rg1z8+lYsndkP9vdxGH72YnTiz9zVteCZT1LE2lf4+yd0d8Wrty1tkme/Uh3y6g
eMkgccvrSockMBDgEc/mqres5Y3tqD9uFD1evd5oNPLk67BYKpOfo82v4QHA/ZSw
+tu0DGGsg7RQFBEKhKjEkKpJQsafSJB2NwIDAQABoAAwDQYJKoZIhvcNAQELBQAD
ggEBAGREXqIdwh2whOQohg8xlhlaU6loqCSuRUPRvH38bdv+E0ko6l81yv82/+H4
qi4cr3l24EWIWXQtD8n2ZYIMsUTu6OGP7xUMbZZZZ50OPJv7XFj583GQxfc47+v9
W23lVGtg2QMOCECq1jAt80VlYK4CmSq5uqTHEmO33UNLAXsra+4K79bjllQFNoXm
LFQAHDALLq9+m6OX07GcDrcmRfWxDinY9nwG/tce0S3ZVSn/dJ4rddNiL71HePIk
RqloKJQxwDWPsoQuuOD2nToNfGvNwHNuxySoJqXbyH7V6z+ZYRnpUm3X5Ev5nyqc
ayzTYNOGj3k06iGEg4JkgMElRSY=
-----END CERTIFICATE REQUEST-----
28 changes: 28 additions & 0 deletions demo/trailer_bypass_attack/certs/server.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDD/rkyIbcnlpk+
rtvJ/kMQc9chwxMXoMn/CkPlbSBowpsNSGQ5aqDU0G2qn7oVocpZtExQgB/qqh6n
+OBqJpMgTSH0fO88wId8JmJGzGNmzYTJ8k6sw6eVzmq97kkZHm5VUGL9o4KsiBfy
OdkOl9DHAj1ss83elPTNaJSDSL0fr0ulQvK8/ErZGDXPz6Viyd2Q/293EYfvZidO
LP3NW14JlPUsTaV/j7J3R3xau3LW2SZ79SHfLqB4ySBxy+tKhyQwEOARz+aqt6zl
je2oP24UPV693mg08uTrsFgqk5+jza/hAcD9lLD627QMYayDtFAUEQqEqMSQqklC
xp9IkHY3AgMBAAECggEABeoW4+4Oh9xMjY6wmpIuAJNuUi9/0P5iJMHGNhMqn4lX
185vxSfJk1fNfLugiEHp8vZfjdeqnuXj/O+kCLAB+pSOn2vHEGSYR2UlIWZ44sOg
QTPVr0hMM1ds8y77TQ2/s0VfZ1tazc1hjkaz8nPJshJdkNKFi4pKhN77Xvwqm4fH
e/1ORmpJKxa74H3hGn+dL96eKnA9Q5pnJEnHhGWe24oDIsJF3SXH2d4jjIt9EIGF
r25a+77bQxFYdSYKDkVmFXWBvDe10/DrM+RY4jhwqqye7feZNuy5XE1YwmeYB/A9
nYBnYRGPguh8OEL2BA4r/hQb/LtLlxFyxNg1S9nAMQKBgQDkrZ5oor74nmxN4GwN
FNTJBrYGPZDQfees2vitLbeABZBV/EjzWNvg9iDlOkZjhOlb+S8g/49o5twjBlf1
PgMxDgTQggLnwO3OVe+lUAsLqvQ1W3a8bqSjtWEUXungjs/ZDBJly7YupNQx1c/V
6+dC8sUCigHVOwOZOJPwfaYAWQKBgQDbaXPVbsf30+eKqXoVpSdjkZqnskQhpvR6
tKu7ZK5y5oV2T/tLk3CBxLj7fOtsb2rec5R1XIOv9tCHu6UUr/I8B3o8pPjfM3nP
cL00uYyCHFCXYU0RaGwpsV0mkrRZApM74DG76mxOaTANOiu2iBIVKpqYlx13HnWQ
B2VzSIPZDwKBgQDF6NjS+B7NLtbO427AN4oc3PkGF5xQRNcPy4cy4gERBD+xmyFL
sljBrmIz4SZwFOSd2+AE8AieokZc7a2MKvo6J0bVad+30Uo+rDM2YDrfAzpNP3ZY
iG1m3aBCMA67cP3De+YkQZTPc4nOA5zXKE5Cq7cDhosljuiDX+rxVN0pKQKBgBdh
vX5ZN+YpJtuYA/KajwAFXD3SuX/8ksEgz8xAhnaoKkDZdSYwqCSsGipyvYPou7LJ
DmETYtU1sDNGw+jYdy/+fABKdFsU2T5J/V/JBjg3XD9Flzjrr9shk5OwvpddtuMC
mUo1SSVyADTopg/loEexKphoQjOXg1+96gnHCIXxAoGBAJQB8kgUkFe7wabr2Dvm
569xitq6CxqWAAHBtXu0/Yjm0M54SxJaeRGOPmSRWXWGkwMaGnFFuG5WfsytZpTx
gKf1rzRF8GQZxn5l5sHNrPqG/PGaG7CdhH49peb3PzhDdDgSOuBDyilOGftNKjSm
LNw4KiiJTmAxTNqFTANqthnd
-----END PRIVATE KEY-----
61 changes: 61 additions & 0 deletions demo/trailer_bypass_attack/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright The OWASP Coraza contributors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"crypto/tls"
"fmt"
"io"
"net/http"
"os"
"strings"

"golang.org/x/net/http2"
)

func main() {
url := "https://localhost:8080"

req, err := http.NewRequest(
"POST",
url,
strings.NewReader(
"{\"foo\": \"<script foo>\"}",
),
)
if err != nil {
fmt.Println("Error creating request:", err)
return
}

req.Header.Set("Content-Type", "application/json")

if len(os.Args) > 1 && os.Args[1] == "-a" {
req.Trailer = http.Header{
"Custom-Trailer": {"This is a custom trailer"},
}
}

client := &http.Client{
Transport: &http2.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}

resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response body:", err)
return
}
fmt.Println("Response body:", string(body))
}
8 changes: 8 additions & 0 deletions demo/trailer_bypass_attack/client/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module flask.com

go 1.22.5

require (
golang.org/x/net v0.27.0 // indirect
golang.org/x/text v0.16.0 // indirect
)
4 changes: 4 additions & 0 deletions demo/trailer_bypass_attack/client/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
Loading
Loading