Every CVE Search/ Get API key

CVE-2026-114138

Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation

7.5 HighFix available

Django REST framework is a toolkit for building Web APIs. Prior to 3.18.2, DefaultContentNegotiation.get_accept_list in rest_framework/negotiation.py accepts an unbounded number and aggregate size of Accept tokens, while select_renderer in that file repeatedly invokes _MediaType parsing from rest_framework/utils/mediatypes.py for each attacker-controlled token, every renderer, and forced 406 renegotiation. A remote unauthenticated client can send many distinct media types containing large quoted parameter values with numerous semicolons, causing Django's parameter parser and DRF's repeated parsing structure to consume disproportionate CPU. Requests to any reachable endpoint can pin synchronous workers for prolonged periods, and concurrent requests can make the service unavailable until the requests stop. This issue is fixed in version 3.18.2.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
None
Availability
High

Exploit likelihood

0.60%Up 0.2 points since Oct 22, 2026
90%50%10%1%

What each source reports

CVE List Score used 7.5

Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation

Django REST framework is a toolkit for building Web APIs. Prior to 3.18.2, DefaultContentNegotiation.get_accept_list in rest_framework/negotiation.py accepts an unbounded number and aggregate size of Accept tokens, while select_renderer in that file repeatedly invokes _MediaType parsing from rest_framework/utils/mediatypes.py for each attacker-controlled token, every renderer, and forced 406 renegotiation. A remote unauthenticated client can send many distinct media types containing large quoted parameter values with numerous semicolons, causing Django's parameter parser and DRF's repeated parsing structure to consume disproportionate CPU. Requests to any reachable endpoint can pin synchronous workers for prolonged periods, and concurrent requests can make the service unavailable until the requests stop. This issue is fixed in version 3.18.2.

Record
CVE-2026-114138
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400, CWE-407
Updated
2026-11-02
EUVDEUVD-2026-107548 7.5

Django REST framework is a toolkit for building Web APIs. Prior to 3.18.2, DefaultContentNegotiation.get_accept_list in rest_framework/negotiation.py accepts an unbounded number and aggregate size of Accept tokens, while select_renderer in that file repeatedly invokes _MediaType parsing from rest_framework/utils/mediatypes.py for each attacker-controlled token, every renderer, and forced 406 renegotiation. A remote unauthenticated client can send many distinct media types containing large quoted parameter values with numerous semicolons, causing Django's parameter parser and DRF's repeated parsing structure to consume disproportionate CPU. Requests to any reachable endpoint can pin synchronous workers for prolonged periods, and concurrent requests can make the service unavailable until the requests stop. This issue is fixed in version 3.18.2.

Record
EUVD-2026-107548
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Updated
2026-11-02
GitHub AdvisoriesGHSA-33wh-fxxf-88vv 7.5

Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation

Summary

DRF'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.

Details

What happens

get_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. _MediaType.__init__ calls django.utils.http.parse_header_parameters unconditionally, at rest_framework/utils/mediatypes.py:47.

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…

Record
GHSA-33wh-fxxf-88vv
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400, CWE-407
Updated
2026-11-02
NVD 7.5

Django REST framework is a toolkit for building Web APIs. Prior to 3.18.2, DefaultContentNegotiation.get_accept_list in rest_framework/negotiation.py accepts an unbounded number and aggregate size of Accept tokens, while select_renderer in that file repeatedly invokes _MediaType parsing from rest_framework/utils/mediatypes.py for each attacker-controlled token, every renderer, and forced 406 renegotiation. A remote unauthenticated client can send many distinct media types containing large quoted parameter values with numerous semicolons, causing Django's parameter parser and DRF's repeated parsing structure to consume disproportionate CPU. Requests to any reachable endpoint can pin synchronous workers for prolonged periods, and concurrent requests can make the service unavailable until the requests stop. This issue is fixed in version 3.18.2.

Record
CVE-2026-114138
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400, CWE-407
Updated
2026-11-02
OSV 7.5

Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation

Django REST framework is a toolkit for building Web APIs. Prior to 3.18.2, DefaultContentNegotiation.get_accept_list in rest_framework/negotiation.py accepts an unbounded number and aggregate size of Accept tokens, while select_renderer in that file repeatedly invokes _MediaType parsing from rest_framework/utils/mediatypes.py for each attacker-controlled token, every renderer, and forced 406 renegotiation. A remote unauthenticated client can send many distinct media types containing large quoted parameter values with numerous semicolons, causing Django's parameter parser and DRF's repeated parsing structure to consume disproportionate CPU. Requests to any reachable endpoint can pin synchronous workers for prolonged periods, and concurrent requests can make the service unavailable until the requests stop. This issue is fixed in version 3.18.2.

Record
CVE-2026-114138
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400, CWE-407
Updated
2026-11-02
OSVDEBIAN-CVE-2026-114138 7.5

Django REST framework is a toolkit for building Web APIs. Prior to 3.18.2, DefaultContentNegotiation.get_accept_list in rest_framework/negotiation.py accepts an unbounded number and aggregate size of Accept tokens, while select_renderer in that file repeatedly invokes _MediaType parsing from rest_framework/utils/mediatypes.py for each attacker-controlled token, every renderer, and forced 406 renegotiation. A remote unauthenticated client can send many distinct media types containing large quoted parameter values with numerous semicolons, causing Django's parameter parser and DRF's repeated parsing structure to consume disproportionate CPU. Requests to any reachable endpoint can pin synchronous workers for prolonged periods, and concurrent requests can make the service unavailable until the requests stop. This issue is fixed in version 3.18.2.

Record
DEBIAN-CVE-2026-114138
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Updated
2026-11-02
Show 4 moreShow less
OSVGHSA-33wh-fxxf-88vv 7.5

Django REST framework: Accept header with many large quoted parameters burns ~19s of CPU per request in content negotiation

Summary

DRF'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.

Details

What happens

get_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. _MediaType.__init__ calls django.utils.http.parse_header_parameters unconditionally, at rest_framework/utils/mediatypes.py:47.

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…

Record
GHSA-33wh-fxxf-88vv
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness
CWE-400, CWE-407
Updated
2026-11-02
OSVUBUNTU-CVE-2026-114138 7.5

Django REST framework is a toolkit for building Web APIs. Prior to 3.18.2, DefaultContentNegotiation.get_accept_list in rest_framework/negotiation.py accepts an unbounded number and aggregate size of Accept tokens, while select_renderer in that file repeatedly invokes _MediaType parsing from rest_framework/utils/mediatypes.py for each attacker-controlled token, every renderer, and forced 406 renegotiation. A remote unauthenticated client can send many distinct media types containing large quoted parameter values with numerous semicolons, causing Django's parameter parser and DRF's repeated parsing structure to consume disproportionate CPU. Requests to any reachable endpoint can pin synchronous workers for prolonged periods, and concurrent requests can make the service unavailable until the requests stop. This issue is fixed in version 3.18.2.

Record
UBUNTU-CVE-2026-114138
CVSS 3.1
7.5 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Updated
2026-11-02
OSVopenSUSE-SU-2026:12277-1 No score

python313-djangorestframework-3.18.2-1.1 on GA media

These are all security issues fixed in the python313-djangorestframework-3.18.2-1.1 package on the GA media of openSUSE Tumbleweed.

Record
openSUSE-SU-2026:12277-1
Updated
2026-11-02
OSVROOT-APP-PYPI-CVE-2026-114138 No score

CVE-2026-114138 in djangorestframework - Patched by Root

Root has patched CVE-2026-114138 in the djangorestframework package for Root:PyPI. Multiple fixed versions available.

Record
ROOT-APP-PYPI-CVE-2026-114138
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
djangorestframework
PyPI
before 3.18.2
3.18.2
djangorestframework
Debian:12
All versions, plus listed versions
3.14.0-23.14.0-2+deb12u13.15.1-13.15.1-23.15.2-1
Show 9 moreShow less
3.15.2-23.16.0-13.16.1-13.16.1-33.16.1-43.17.1+ds1-13.17.1-13.18.1+ds1-13.18.3+ds1-1
None known
djangorestframework
Debian:13
All versions, plus listed versions
3.16.0-13.16.1-13.16.1-33.16.1-43.17.1+ds1-13.17.1-13.18.1+ds1-13.18.3+ds1-1
None known
djangorestframework
Debian:14
before 3.18.3+ds1-1, plus listed versions
3.16.0-13.16.1-13.16.1-33.16.1-43.17.1+ds1-13.17.1-13.18.1+ds1-1
3.18.3+ds1-1
djangorestframework
PyPI
before 3.18.2, plus listed versions
0.10.1.10.2.00.2.10.2.2
Show 45 moreShow less
0.2.30.2.40.3.00.3.10.3.20.3.30.4.02.0.02.0.12.0.22.1.02.1.12.1.102.1.112.1.122.1.132.1.142.1.152.1.162.1.172.1.22.1.32.1.42.1.52.1.62.1.72.1.82.1.92.2.02.2.12.2.22.2.32.2.42.2.52.2.62.2.72.3.02.3.12.3.102.3.112.3.122.3.132.3.142.3.22.3.3
3.18.2
djangorestframework
Root:PyPI
before 3.12.4+aikido.2
3.12.4+aikido.23.16.1+aikido.13.17.2+aikido.13.15.1+aikido.13.14.0+aikido.2
djangorestframework
Ubuntu:16.04:LTS
All versions, plus listed versions
2.4.3-23.3.1-13.3.2-13.3.2-2
None known
djangorestframework
Ubuntu:18.04:LTS
All versions, plus listed versions
3.4.0-23.7.7-1
None known
djangorestframework
Ubuntu:20.04:LTS
All versions, plus listed versions
3.9.0-23.10.2-1
None known
djangorestframework
Ubuntu:22.04:LTS
All versions, plus listed versions
3.12.1-13.12.4-13.12.4-23.12.4-2ubuntu13.12.4-2.1ubuntu1
None known
Show 4 moreShow less
Affected packages
PackageAffectedFixed
djangorestframework
Ubuntu:24.04:LTS
All versions, plus listed versions
3.14.0-2
None known
djangorestframework
Ubuntu:26.10
All versions, plus listed versions
3.16.1-33.16.1-43.17.1+ds1-1
None known
djangorestframework
Ubuntu:26.04:LTS
All versions, plus listed versions
3.16.1-13.16.1-3
None known
python-djangorestframework
openSUSE:Tumbleweed
From an advisory covering 3 CVEs
before 3.18.2-1.1
3.18.2-1.1

History

History
RecordedChangedSources
affected, facts
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-114138 is a high vulnerability (CVSS 7.5) in Encode Django-rest-framework, published October 21, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.60%, the 48th percentile. Fixed in djangorestframework 3.18.2.

References

Show 7 moreShow less
This page doesn't list everything

Left out here: 94 versions and the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open