djangorestframework
Web APIs for Django. 🎸
Activity
- Latest release
- 3w ago
- Total releases
- 145
- Cadence
- ~36 days
- Last 12 months
- 7
Reach
- Stars
- 30.2k
Details
- License
- BSD-3-Clause
- First release
- Feb 21, 2011
| Version | Released | |
|---|---|---|
3.18.3
patch
| ||
3.18.2
patch
| ||
3.18.1
patch
3 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev | ||
3.18.0
minor
3 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev | ||
3.17.2
patch
3 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev | ||
3.17.1
patch
5 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev | ||
3.17.0
minor
5 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev | ||
3.16.1
patch
5 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev | ||
3.16.0
minor
5 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev | ||
3.15.2
patch
5 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev | ||
3.15.1
patch
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.15.0
minor
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.14.0
minor
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.13.1
patch
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.13.0
minor
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.12.4
patch
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.12.3
patch
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.12.2
patch
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.11.2
patch
5 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.12.1
patch
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.12.0
minor
6 CVEs
CVE-2026-114137
GHSA-xw6w-gcfp-cf8m
Oct 22, 2026
Django REST framework: TokenProxy.delete() reports success and revokes nothing
7.4
/ 10
High
Network
High
None
None
Unchanged
High
High
None
SummaryDeleting an authentication token through Details
The concrete
That matches a user id against the 40-character key column. Nothing matches, nothing raises, and the
call returns DRF already ships two workarounds for this same identity mismatch instead of a fix. In
and
A fix would drop the PoCSave as
Observed output:
The control is what makes the trigger meaningful: it proves the row was deletable and the credential check live, so the 200 after the proxy delete is a failed revocation and not a broken harness. Reproduced identically on Django 6.1 and 5.2.17 LTS, so this is not a recent Django regression. ImpactFailed credential revocation. An operator who revokes a token through a No untrusted input reaches this code path, which is why the vector is scored Affected versions
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
+ 6 more Show less
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev | ||
3.11.1
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.11.0
minor
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.10.3
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.10.2
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.10.1
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.10.0
minor
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.9.4
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.9.3
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.9.2
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.9.1
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.9.0
minor
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.8.2
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.8.1
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.8.0
minor
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.7.7
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.7.6
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.7.5
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.7.4
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.7.3
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.7.2
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.7.1
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.7.0
minor
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.6.4
patch
6 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-114136
GHSA-3547-9m27-7rxg
Oct 22, 2026
Django REST framework: JSONParser applies an attacker-named codec to the request stream
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryAn unauthenticated attacker can defeat Django's Details
Of the roughly 455 codec names The ordering is what makes this exploitable. Django charges This is DRF's defect and not Django's. Django resolves the charset parameter and assigns
The sink was introduced by A one-line fix at
PoCSave as
Observed output, abbreviated to the summary block:
C1 establishes that the size limit is live and configured, so the 200 in T is a defeated guard and not an absent one. C1 reports its own RSS growth because the client holds the 48 MiB body before the limit rejects it, which is client-side cost and never reaches the view. C2 carries most of the weight: its body is byte-identical to T, and only the codec name differs. RSS figures vary by a few MiB between runs. Repeating all four arms on Django 5.2.17 LTS gave 109 wire bytes to 50,331,648 characters, 461,758x, with C1 and C2 both holding. One note if you re-implement the request yourself: use ImpactUnauthenticated denial of service through memory amplification, against any endpoint that accepts a JSON body with the default parser set. Every deployment on 3.6.4 or later is affected, and no application code or configuration choice opts into it. Memory is per-request and released afterwards, which is why this is rated on availability alone rather than as Critical, but the amplification factor means a small number of concurrent requests can exhaust a worker. A few hundred bytes allocates hundreds of megabytes, and nothing in DRF or Django bounds the expanded size. Affected versions
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
+ 30 more Show less
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.6.3
patch
5 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.6.2
patch
5 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.6.1
patch
5 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.6.0
minor
5 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.5.4
patch
5 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev | ||
3.5.3
patch
5 CVEs
CVE-2026-114138
GHSA-33wh-fxxf-88vv
Oct 22, 2026
Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation
7.5
/ 10
High
Network
Low
None
None
Unchanged
None
None
High
SummaryDRF's content negotiation parses each Accept token several times per request and never limits the number of tokens or their total size. Each parse ends up in Django's parse_header_parameters, which takes quadratic time when a quoted parameter value is packed with semicolons. On my machine a single GET with a 780KB Accept header made of 96 such tokens kept one gunicorn worker busy for 18.87 seconds (Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0, Python 3.13, Docker on macOS). The header is syntactically valid, fits inside gunicorn's default header limits, and needs no authentication. Two concurrent requests, which is one per sync worker, made a default two worker deployment stop answering for my whole 40 second test window. It recovered half a second after I stopped sending. DetailsWhat happensget_accept_list() splits the Accept header on commas with no cap on token count or total size, at rest_framework/negotiation.py:96-97. For every token, select_renderer() calls media_type_matches() once per renderer, and each call builds fresh _MediaType objects for both sides, at rest_framework/negotiation.py:53-56 and rest_framework/utils/mediatypes.py:22-23. When nothing matches, DRF raises NotAcceptable (406). Rendering that error response also needs a renderer, so finalize_response() runs the entire negotiation a second time through perform_content_negotiation(force=True), at rest_framework/views.py:434-437. With the default two renderers this comes to about six parses per Accept token per request. I confirmed the count by instrumenting _MediaType with a counter: six per token, three from the normal pass and three from the forced re-negotiation. The quadratic part is on the Django side. parse_header_parameters splits parameters with _parseparam, which handles quoted values by recounting the quote characters from the start of the string for every candidate separator, the s.count('"', 0, end) calls in django/utils/http.py. A quoted value holding k semicolons costs roughly k rescans over k characters, so a single ~8KB value takes about 31ms per parse here. Numbers from my runs, measured as utime+stime deltas of the server process: one token, ~9KB: 230ms server CPU 24 tokens, 216KB: 5.07s on runserver, which has no per field limit 48 tokens, 432KB: 10.08s on runserver 96 tokens, 780KB: 18.87s under gunicorn defaults Wall time matched CPU time in every run, so this is pure CPU work. RSS stayed flat at 57MB throughout, this is a time attack and not a memory one. A few details that matter for reproducing it:Django rejects a single media type string longer than 10,000 chars (MAX_HEADER_LENGTH), so each token has to stay just under that. Going over gives you a ValueError and a 500, which is a different and much weaker problem. gunicorn defaults are limit_request_field_size 8190 and limit_request_fields 100, so the working shape is 96 separate Accept fields with one ~8.1KB token each. That puts the single request ceiling near 19s, below the default 30s worker timeout, so one request alone does not get a stock gunicorn worker killed by the arbiter. It does not need to. Each request pins one sync worker for its full duration, so on two workers, two held connections wedge the service. order_by_precedence collects tokens into a set, so identical tokens collapse and the cost mostly disappears. Each token needs its own parameter name, x0=, x1=, x2= and so on. Where a fix could go, either of these alone would be enough:Parse each Accept token once per request and reuse the result in the matching loop and in the 406 re-negotiation. Put an aggregate budget in get_accept_list(), say 8KB total or 64 tokens, and treat anything past it as not acceptable. The quadratic behavior itself lives in Django's parser, but DRF is the one calling it six times per token with no cap on how many tokens there are, so both fixes look like DRF changes to me. PoCVerified end to end on Docker Desktop for macOS (Apple Silicon, 8 cores), python:3.13-slim image, Django 6.1, djangorestframework 3.18.0, gunicorn 26.0.0. Nothing was configured beyond defaults. Minimal target, any default DRF project works, this is the one I used. requirements.txt:
pocproj/settings.py:
pocproj/api.py:
pocproj/urls.py:
Start it:
Client, stdlib only, run with python3:
ImpactAvailability only, no confidentiality or integrity impact. Any endpoint the attacker can reach is affected, public ones with no authentication at all. On a stock gunicorn setup with two sync workers, two held connections are enough to stop the service for as long as the attacker keeps them, and one request alone costs about 19 seconds of one core. Recovery is immediate once the attack stops, there is no lasting damage. The requests are valid HTTP and get answered with 406, so they show up in access logs, but nothing about them is malformed, so filters that look for bad requests will not catch them. DRF throttling runs before content negotiation and would cut the rate if configured, but the default configuration ships with no throttle classes, and each request that does get through still costs the full 19 seconds. The negotiation structure, meaning the uncapped token split, the per-renderer re-parsing, and the 406 re-negotiation, has been in place since 2012. The call switched to parse_header_parameters in July 2022 in #8556. I verified the behavior on 3.18.0. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 132 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.17.2
3.18.0
3.18.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.18.2
References
Updated Oct 22, 2026 · Source: OSV.dev
CVE-2026-73228
PYSEC-2026-3827
GHSA-2m8g-3cmr-wg3w
Sep 10, 2026
Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`
5.3
/ 10
Medium
Network
Low
None
None
Unchanged
None
None
Low
SummaryWhile investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level In the tested configurations, Django correctly raises This behavior appears to occur because DRF passes the underlying I am reporting this privately because I am unsure whether this behavior is considered part of DRF's intended security boundary, but it appears to bypass a documented Django request-size protection for common DRF request parsing paths and may have availability implications. What I VerifiedI verified the behavior locally using the following combinations:
For both versions, the observed behavior was:
I also confirmed that:
Technical DetailsThe relevant execution flow is:
The important implementation detail is that DRF assigns the original Django Unlike As a result, DRF's built-in parsers successfully consume oversized request bodies that Django itself would reject through its higher-level request interfaces. Reproduction StepsEnvironmentPython 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure:
Create a simple DRF API view:
Start the application. Send an oversized JSON request:
Example:
Observed:
Now compare against:
Observed:
Likewise, compare against:
using
Observed:
This demonstrates different enforcement depending on which request API is used. Root CauseDjango documents DRF exposes Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's Consequently:
fully consume oversized request bodies despite Django's configured request-size limit. Security ImpactThis does not appear to introduce:
However, it may reduce the effectiveness of deployments relying on Django's Potential consequences include:
The practical impact depends on deployment configuration, including:
Memory ObservationsDuring local testing I observed successful parsing of oversized request bodies despite the configured limit. Representative measurements showed significantly increased memory allocation while parsing large JSON and urlencoded payloads. I intentionally did not perform destructive concurrency testing or attempt to exhaust system resources. ScopeConfirmed affected:
Confirmed not affected:
Suggested Fix DirectionOne possible approach would be for DRF to enforce Django's configured This would preserve Django's configured request-size protection for the common Versions TestedAffected:
I did not perform a complete historical version bisect. DisclosureI have not publicly disclosed this behavior. I am submitting it privately in accordance with the project's security policy because I am unsure whether maintainers consider this part of DRF's intended security boundary. Note:Thank you for taking the time to review this report. If you determine that this behavior should be addressed, I would be happy to help investigate further, develop a fix, add regression tests, and submit a patch if you'd find that helpful. I have experience as a Python/Django software engineer, security researcher, and open-source contributor, and I'd be glad to contribute if you think that would be useful. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2026-73229
PYSEC-2026-3828
GHSA-g47c-3xmw-q6m2
Sep 10, 2026
Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests
4.3
/ 10
Medium
Network
Low
Low
None
Unchanged
Low
None
None
Summary AdminRenderer may disclose data that would normally be protected by GET permissions when rendering a 400 Bad Request response for an invalid write request. If a view allows POST (or another write method) but denies GET, an invalid request rendered through AdminRenderer can invoke the view's GET handler and include data from the GET representation in the generated HTML response. This behavior appears to be specific to AdminRenderer and does not affect the normal JSON rendering path. Details While investigating the AdminRenderer rendering flow, I observed that invalid write requests are rendered by temporarily overriding the request method and invoking the view's GET handler:
This execution path differs from a normal GET request. Under normal request processing, a GET request flows through:
However, during AdminRenderer rendering, the renderer directly invokes: view.get(...) A view whose permission class explicitly allowed POST but denied GET still executed its GET handler while rendering an invalid POST request through AdminRenderer. As a result, data intended to be available only through an authorized GET request was included in the generated HTML response. Proof of Concept Using a standard ListCreateAPIView. Permission class:
Expected Behaviour
Observed Behaviour
Tthe same behavior is shown using a minimal APIView implementation. Observed results:
Generic view reproduction:
These observations indicate that direct GET requests are correctly denied, while the simulated GET used during AdminRenderer rendering can still retrieve the protected representation. Impact This issue may result in information disclosure when all of the following conditions are met: AdminRenderer is enabled. The client negotiates the HTML renderer (for example using Accept: text/html). The application permits POST (or another write method). GET requests are denied by the configured permission class. The invalid write request returns 400 Bad Request. The GET representation contains information that the requester would normally not be permitted to access. This issue does not appear to affect: JSON rendering Standard API responses Successful write requests The behavior appears limited to the HTML rendering path used by AdminRenderer. Suggested Fix Possible approaches include: Perform equivalent permission checks before executing the simulated GET request. Avoid invoking view.get() when the corresponding GET request would not be permitted. Fall back to rendering only serializer/form validation errors instead of retrieving the GET representation. A regression test could create a permission class that allows POST while denying GET, then verify that an invalid POST rendered with AdminRenderer does not include data from the protected GET representation. Environment Repository:
Branch tested:
Commit tested:
Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 129 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.15.2
3.16.0
3.16.1
3.17.0
3.17.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.17.2
References
Updated Sep 10, 2026 · Source: OSV.dev
CVE-2024-21520
PYSEC-2026-1304
GHSA-gw84-84pc-xp82
Jul 07, 2026
Cross-site Scripting in djangorestframework
Medium
Network
Low
None
Versions of the package djangorestframework before 3.15.2 are vulnerable to Cross-site Scripting (XSS) via the break_long_headers template filter due to improper input sanitization before splitting and joining with tags. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 124 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.11.2
3.12.0
3.12.1
3.12.2
3.12.3
3.12.4
3.13.0
3.13.1
3.14.0
3.15.0
3.15.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.15.2
References
Updated Jul 07, 2026 · Source: OSV.dev
CVE-2020-25626
GHSA-fx83-3ph3-9j2q
PYSEC-2020-263
Mar 19, 2021
Cross-site Scripting (XSS) in Django REST Framework
Medium
Network
Low
None
A flaw was found in Django REST Framework versions before 3.12.0 and before 3.11.2. When using the browseable API viewer, Django REST Framework fails to properly escape certain strings that can come from user input. This allows a user who can control those strings to inject malicious tags, leading to a cross-site-scripting (XSS) vulnerability. Affected versions
0.1
0.1.1
0.2.0
0.2.1
0.2.2
0.2.3
0.2.4
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
+ 113 more Show less
2.0.0
2.0.1
2.0.2
2.1.0
2.1.1
2.1.10
2.1.11
2.1.12
2.1.13
2.1.14
2.1.15
2.1.16
2.1.17
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
2.1.7
2.1.8
2.1.9
2.2.0
2.2.1
2.2.2
2.2.3
2.2.4
2.2.5
2.2.6
2.2.7
2.3.0
2.3.1
2.3.10
2.3.11
2.3.12
2.3.13
2.3.14
2.3.2
2.3.3
2.3.4
2.3.5
2.3.6
2.3.7
2.3.8
2.3.9
2.4.0
2.4.1
2.4.2
2.4.3
2.4.4
2.4.5
2.4.6
2.4.7
2.4.8
3.0.0
3.0.1
3.0.2
3.0.3
3.0.4
3.0.5
3.1.0
3.1.1
3.1.2
3.1.3
3.10.0
3.10.1
3.10.2
3.10.3
3.11.0
3.11.1
3.2.0
3.2.1
3.2.2
3.2.3
3.2.4
3.2.5
3.3.0
3.3.1
3.3.2
3.3.3
3.4.0
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.5.0
3.5.1
3.5.2
3.5.3
3.5.4
3.6.0
3.6.1
3.6.2
3.6.3
3.6.4
3.7.0
3.7.1
3.7.2
3.7.3
3.7.4
3.7.5
3.7.6
3.7.7
3.8.0
3.8.1
3.8.2
3.9.0
3.9.1
3.9.2
3.9.3
3.9.4
Fixed in
3.11.2
References
Updated Sep 20, 2024 · Source: OSV.dev |