-
Notifications
You must be signed in to change notification settings - Fork 70
Logstash plugin http input fails when uri contains either 'J' or 'M', including within query parameter names and values #205
Description
Logstash information:
Please include the following information:
- Logstash version (e.g.
bin/logstash --version)
bash-5.1$ bin/logstash --version
Using bundled JDK: /usr/share/logstash/jdk
logstash 9.2.4
- Logstash installation source (e.g. built from source, with a package manager: DEB/RPM, expanded from tar or zip archive, docker)
docker, docker.elastic.co/logstash/logstash:9.2.4, id= 3c2cac9a329e - How is Logstash being run (e.g. as a service/service manager: systemd, upstart, etc. Via command line, docker/kubernetes)
docker - How was the Logstash Plugin installed
pre-existing with docker image
JVM (e.g.java -version):
If the affected version of Logstash is 7.9 (or earlier), or if it is NOT using the bundled JDK or using the 'no-jdk' version in 7.10 (or higher), please provide the following information:
- JVM version (
java -version)
openjdk version "21.0.9" 2025-10-21 LTS
OpenJDK Runtime Environment Temurin-21.0.9+10 (build 21.0.9+10-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.9+10 (build 21.0.9+10-LTS, mixed mode, sharing)
- JVM installation source (e.g. from the Operating System's package manager, from source, etc).
default from docker image - Value of the
JAVA_HOMEenvironment variable if set.
OS version (uname -a if on a Unix-like system):
bash-5.1$ uname -a
Linux 5a8fcafa9a97 5.14.0-611.16.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Sun Dec 7 05:52:24 EST 2025 x86_64 x86_64 x86_64 GNU/Linux
Description of the problem including expected versus actual behavior:
Using stock docker deployment of Logstash 9.2.4, and basic http input pipeline, all GET or POST requests that contain either 'J' or 'M' in the uri in either path or query parameters, the plugin will fail due to bad client request. Both letters triggering this condition are plain ASCII, 'J' (hex \x4A) and 'M' (hex \x4D). no other regular characters, including their lower case counterparts trigger this error. This behavior does not exist in 9.2.3, only 9.2.4 presently.
Steps to reproduce:
1. Snag a default install of logstash (though fully configured behaves the same)
Using the below basic docker config and pipeline config the issue can be created:
input {
http {
port => 6767
codec => plain { charset => "UTF-8" }
}
}
# Generally this would contain logic to store query params in key/values, but not necessary to trigger the issue
output {
stdout { codec => rubydebug { metadata => true } }
}
docker-compose.yml
services:
ls:
image: docker.elastic.co/logstash/logstash:9.2.4
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline
- /docker/lsbug/queue:/queue
environment:
LS_JAVA_OPTS: "-Xms2048m -Xmx2048m"
ulimits:
memlock:
soft: -1
hard: -1
ports:
- 6767:6767
restart: always
- Send data, both GET and POST seem to behave the same. My use full use case pulls query params off a GET and stores, but issue seems to be agnostic of request type.
sending this:
curl localhost:6767/M
results in this:
ls-1 | {
ls-1 | "@timestamp" => 2026-01-24T23:25:09.883491167Z,
ls-1 | "@metadata" => {
ls-1 | "input" => {
ls-1 | "http" => {
ls-1 | "request" => {
ls-1 | "headers" => {
ls-1 | "request_method" => "GET",
ls-1 | "request_path" => "/bad-request",
ls-1 | "http_host" => nil,
ls-1 | "http_user_agent" => nil,
ls-1 | "http_accept" => nil,
ls-1 | "http_version" => "HTTP/1.0"
ls-1 | }
ls-1 | }
ls-1 | }
ls-1 | }
ls-1 | },
ls-1 | "host" => {
ls-1 | "ip" => "172.24.0.1"
ls-1 | },
ls-1 | "@version" => "1",
ls-1 | "http" => {
ls-1 | "method" => "GET",
ls-1 | "version" => "HTTP/1.0"
ls-1 | },
ls-1 | "message" => "",
ls-1 | "url" => {
ls-1 | "path" => "/bad-request"
ls-1 | }
ls-1 | }
Notes:
- URL length, or order of characters do not seem to matter, simply the presence of either of these characters in the uri will cause the input plugin to treat the request as a bad.
- attempting the
gsubthe character out in a filter block does not change the behavior, it issue appears trigger before filter runs. - testing more characters that do not require escaping/encoding, highlights the two problem characters.
loop across uppercase lowercase, and -, _, ., and ~.
# for char in `cat chars` ; do curl localhost:6767/$char ; done
# docker compose logs ls | grep request_path
ls-1 | "request_path" => "/A",
ls-1 | "request_path" => "/B",
ls-1 | "request_path" => "/C",
ls-1 | "request_path" => "/D",
ls-1 | "request_path" => "/E",
ls-1 | "request_path" => "/F",
ls-1 | "request_path" => "/G",
ls-1 | "request_path" => "/H",
ls-1 | "request_path" => "/I",
ls-1 | "request_path" => "/bad-request",
ls-1 | "request_path" => "/K",
ls-1 | "request_path" => "/L",
ls-1 | "request_path" => "/bad-request",
ls-1 | "request_path" => "/N",
ls-1 | "request_path" => "/O",
ls-1 | "request_path" => "/P",
ls-1 | "request_path" => "/Q",
ls-1 | "request_path" => "/R",
ls-1 | "request_path" => "/S",
ls-1 | "request_path" => "/T",
ls-1 | "request_path" => "/U",
ls-1 | "request_path" => "/V",
ls-1 | "request_path" => "/W",
ls-1 | "request_path" => "/X",
ls-1 | "request_path" => "/Y",
ls-1 | "request_path" => "/Z",
ls-1 | "request_path" => "/a",
ls-1 | "request_path" => "/b",
ls-1 | "request_path" => "/c",
ls-1 | "request_path" => "/d",
ls-1 | "request_path" => "/e",
ls-1 | "request_path" => "/f",
ls-1 | "request_path" => "/g",
ls-1 | "request_path" => "/h",
ls-1 | "request_path" => "/i",
ls-1 | "request_path" => "/j",
ls-1 | "request_path" => "/k",
ls-1 | "request_path" => "/l",
ls-1 | "request_path" => "/m",
ls-1 | "request_path" => "/n",
ls-1 | "request_path" => "/o",
ls-1 | "request_path" => "/p",
ls-1 | "request_path" => "/q",
ls-1 | "request_path" => "/r",
ls-1 | "request_path" => "/s",
ls-1 | "request_path" => "/t",
ls-1 | "request_path" => "/u",
ls-1 | "request_path" => "/v",
ls-1 | "request_path" => "/w",
ls-1 | "request_path" => "/x",
ls-1 | "request_path" => "/y",
ls-1 | "request_path" => "/z",
ls-1 | "request_path" => "/-",
ls-1 | "request_path" => "/_",
ls-1 | "request_path" => "/",
ls-1 | "request_path" => "/~",
Provide logs (if relevant):
Similarly in query params:
# for char in `cat chars` ; do curl "localhost:6767/data?key=value&anotherkey=$char" && sleep .05 ; done
okokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokok
# docker compose logs ls | grep request_path
ls-1 | "request_path" => "/data?key=value&anotherkey=A",
ls-1 | "request_path" => "/data?key=value&anotherkey=B",
ls-1 | "request_path" => "/data?key=value&anotherkey=C",
ls-1 | "request_path" => "/data?key=value&anotherkey=D",
ls-1 | "request_path" => "/data?key=value&anotherkey=E",
ls-1 | "request_path" => "/data?key=value&anotherkey=F",
ls-1 | "request_path" => "/data?key=value&anotherkey=G",
ls-1 | "request_path" => "/data?key=value&anotherkey=H",
ls-1 | "request_path" => "/data?key=value&anotherkey=I",
ls-1 | "request_path" => "/bad-request",
ls-1 | "request_path" => "/data?key=value&anotherkey=K",
ls-1 | "request_path" => "/data?key=value&anotherkey=L",
ls-1 | "request_path" => "/bad-request",
ls-1 | "request_path" => "/data?key=value&anotherkey=N",
ls-1 | "request_path" => "/data?key=value&anotherkey=O",
ls-1 | "request_path" => "/data?key=value&anotherkey=P",
ls-1 | "request_path" => "/data?key=value&anotherkey=Q",
ls-1 | "request_path" => "/data?key=value&anotherkey=R",
ls-1 | "request_path" => "/data?key=value&anotherkey=S",
ls-1 | "request_path" => "/data?key=value&anotherkey=T",
ls-1 | "request_path" => "/data?key=value&anotherkey=U",
ls-1 | "request_path" => "/data?key=value&anotherkey=V",
ls-1 | "request_path" => "/data?key=value&anotherkey=W",
ls-1 | "request_path" => "/data?key=value&anotherkey=X",
ls-1 | "request_path" => "/data?key=value&anotherkey=Y",
ls-1 | "request_path" => "/data?key=value&anotherkey=Z",
ls-1 | "request_path" => "/data?key=value&anotherkey=a",
ls-1 | "request_path" => "/data?key=value&anotherkey=b",
ls-1 | "request_path" => "/data?key=value&anotherkey=c",
ls-1 | "request_path" => "/data?key=value&anotherkey=d",
ls-1 | "request_path" => "/data?key=value&anotherkey=e",
ls-1 | "request_path" => "/data?key=value&anotherkey=f",
ls-1 | "request_path" => "/data?key=value&anotherkey=g",
ls-1 | "request_path" => "/data?key=value&anotherkey=h",
ls-1 | "request_path" => "/data?key=value&anotherkey=i",
ls-1 | "request_path" => "/data?key=value&anotherkey=j",
ls-1 | "request_path" => "/data?key=value&anotherkey=k",
ls-1 | "request_path" => "/data?key=value&anotherkey=l",
ls-1 | "request_path" => "/data?key=value&anotherkey=m",
ls-1 | "request_path" => "/data?key=value&anotherkey=n",
ls-1 | "request_path" => "/data?key=value&anotherkey=o",
ls-1 | "request_path" => "/data?key=value&anotherkey=p",
ls-1 | "request_path" => "/data?key=value&anotherkey=q",
ls-1 | "request_path" => "/data?key=value&anotherkey=r",
ls-1 | "request_path" => "/data?key=value&anotherkey=s",
ls-1 | "request_path" => "/data?key=value&anotherkey=t",
ls-1 | "request_path" => "/data?key=value&anotherkey=u",
ls-1 | "request_path" => "/data?key=value&anotherkey=v",
ls-1 | "request_path" => "/data?key=value&anotherkey=w",
ls-1 | "request_path" => "/data?key=value&anotherkey=x",
ls-1 | "request_path" => "/data?key=value&anotherkey=y",
ls-1 | "request_path" => "/data?key=value&anotherkey=z",
ls-1 | "request_path" => "/data?key=value&anotherkey=-",
ls-1 | "request_path" => "/data?key=value&anotherkey=_",
ls-1 | "request_path" => "/data?key=value&anotherkey=.",
ls-1 | "request_path" => "/data?key=value&anotherkey=~",
# for char in `cat chars` ; do curl "localhost:6767/post$char" -d "somedata" && sleep .05 ; done
okokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokokok
# docker compose logs ls | grep request_path
ls-1 | "request_path" => "/postA",
ls-1 | "request_path" => "/postB",
ls-1 | "request_path" => "/postC",
ls-1 | "request_path" => "/postD",
ls-1 | "request_path" => "/postE",
ls-1 | "request_path" => "/postF",
ls-1 | "request_path" => "/postG",
ls-1 | "request_path" => "/postH",
ls-1 | "request_path" => "/postI",
ls-1 | "request_path" => "/bad-request",
ls-1 | "request_path" => "/postK",
ls-1 | "request_path" => "/postL",
ls-1 | "request_path" => "/bad-request",
ls-1 | "request_path" => "/postN",
ls-1 | "request_path" => "/postO",
ls-1 | "request_path" => "/postP",
ls-1 | "request_path" => "/postQ",
ls-1 | "request_path" => "/postR",
ls-1 | "request_path" => "/postS",
ls-1 | "request_path" => "/postT",
ls-1 | "request_path" => "/postU",
ls-1 | "request_path" => "/postV",
ls-1 | "request_path" => "/postW",
ls-1 | "request_path" => "/postX",
ls-1 | "request_path" => "/postY",
ls-1 | "request_path" => "/postZ",
ls-1 | "request_path" => "/posta",
ls-1 | "request_path" => "/postb",
ls-1 | "request_path" => "/postc",
ls-1 | "request_path" => "/postd",
ls-1 | "request_path" => "/poste",
ls-1 | "request_path" => "/postf",
ls-1 | "request_path" => "/postg",
ls-1 | "request_path" => "/posth",
ls-1 | "request_path" => "/posti",
ls-1 | "request_path" => "/postj",
ls-1 | "request_path" => "/postk",
ls-1 | "request_path" => "/postl",
ls-1 | "request_path" => "/postm",
ls-1 | "request_path" => "/postn",
ls-1 | "request_path" => "/posto",
ls-1 | "request_path" => "/postp",
ls-1 | "request_path" => "/postq",
ls-1 | "request_path" => "/postr",
ls-1 | "request_path" => "/posts",
ls-1 | "request_path" => "/postt",
ls-1 | "request_path" => "/postu",
ls-1 | "request_path" => "/postv",
ls-1 | "request_path" => "/postw",
ls-1 | "request_path" => "/postx",
ls-1 | "request_path" => "/posty",
ls-1 | "request_path" => "/postz",
ls-1 | "request_path" => "/post-",
ls-1 | "request_path" => "/post_",
ls-1 | "request_path" => "/post.",
ls-1 | "request_path" => "/post~",