You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(appsec): transfer-encoding chunked requests are dropped [backport 1.16] (#7394)
Backport 4c065aa from #7378 to 1.16.
#7377 reverts
#7266. This change instead is
an attempt at the proper fix needed to support `Transfer-Encoding:
chunked` requests in appsec.
According to the WSGI spec, `CONTENT_LENGTH` is allowed to be empty or
missing. This can be the case when `Transfer-Encoding: chunked` is sent.
In those cases we are removing the post data from the request causing
errors in the user's application.
Example reproduction:
```python
from flask import Flask, request
app = Flask(__name__)
@app.post("/submit/file")
def submit_file():
user_file = request.files.get("file")
if not user_file:
raise Exception("user_file is missing")
return ""
```
```shell
# Run without ddtrace/asm
$ gunicorn app:app
# Send a chunked request without a content-length header
$ curl -vL --request POST 'http://localhost:8000/submit/file' \
--header 'Content-Type: multipart/form-data' --header 'Content-Length:' --header 'Transfer-Encoding: chunked' \
-F [email protected]
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> POST /submit/file HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.88.1
> Accept: */*
> Transfer-Encoding: chunked
> Content-Type: multipart/form-data; boundary=------------------------d146b48a5158972c
>
* Signaling end of chunked upload via terminating chunk.
< HTTP/1.1 200 OK
< Server: gunicorn
< Date: Thu, 26 Oct 2023 19:05:51 GMT
< Connection: close
< Content-Type: text/html; charset=utf-8
< Content-Length: 7
<
* Closing connection 0
success%
```
```shell
# Run the same with ddtrace + ASM enabled
$ DD_APPSEC_ENABLED=true ddtrace-run gunicorn app:app
# Send a chunked request without a content-length header
$ curl -vL --request POST 'http://localhost:8000/submit/file' \
--header 'Content-Type: multipart/form-data' --header 'Content-Length:' --header 'Transfer-Encoding: chunked' \
-F [email protected]
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> POST /submit/file HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.88.1
> Accept: */*
> Transfer-Encoding: chunked
> Content-Type: multipart/form-data; boundary=------------------------78465a1f7fa07e00
>
* Signaling end of chunked upload via terminating chunk.
< HTTP/1.1 500 INTERNAL SERVER ERROR
< Server: gunicorn
< Date: Thu, 26 Oct 2023 19:09:54 GMT
< Connection: close
< Content-Type: text/html; charset=utf-8
< Content-Length: 265
<
<!doctype html>
<html lang=en>
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
* Closing connection 0
```
```shell
# Run with only tracing enabled
$ ddtrace-run gunicorn app:app
$ curl -vL --request POST 'http://localhost:8000/submit/file' \
--header 'Content-Type: multipart/form-data' --header 'Content-Length:' --header 'Transfer-Encoding: chunked' \
-F [email protected]
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> POST /submit/file HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.88.1
> Accept: */*
> Transfer-Encoding: chunked
> Content-Type: multipart/form-data; boundary=------------------------6137a5c31f27950e
>
* Signaling end of chunked upload via terminating chunk.
< HTTP/1.1 200 OK
< Server: gunicorn
< Date: Thu, 26 Oct 2023 19:10:53 GMT
< Connection: close
< Content-Type: text/html; charset=utf-8
< Content-Length: 7
<
* Closing connection 0
success%
```
Looking over the Werkzeug code for how they handle this, is something
that we should replicate here.
https://github.com/pallets/werkzeug/blob/f3c803b3ade485a45f12b6d6617595350c0f03e2/src/werkzeug/sansio/utils.py#L140-L159
## Checklist
- [x] Change(s) are motivated and described in the PR description.
- [x] Testing strategy is described if automated tests are not included
in the PR.
- [x] Risk is outlined (performance impact, potential for breakage,
maintainability, etc).
- [x] Change is maintainable (easy to change, telemetry, documentation).
- [x] [Library release note
guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
are followed. If no release note is required, add label
`changelog/no-changelog`.
- [x] Documentation is included (in-code, generated user docs, [public
corp docs](https://github.com/DataDog/documentation/)).
- [x] Backport labels are set (if
[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))
## Reviewer Checklist
- [x] Title is accurate.
- [x] No unnecessary changes are introduced.
- [x] Description motivates each change.
- [x] Avoids breaking
[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)
changes unless absolutely necessary.
- [x] Testing strategy adequately addresses listed risk(s).
- [x] Change is maintainable (easy to change, telemetry, documentation).
- [x] Release note makes sense to a user of the library.
- [x] Reviewer has explicitly acknowledged and discussed the performance
implications of this PR as reported in the benchmarks PR comment.
- [x] Backport labels are set in a manner that is consistent with the
[release branch maintenance
policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
- [x] If this PR touches code that signs or publishes builds or
packages, or handles credentials of any kind, I've requested a review
from `@DataDog/security-design-and-guidance`.
- [x] This PR doesn't touch any of that.
Co-authored-by: Brett Langdon <[email protected]>
0 commit comments