Skip to content

Commit fad0914

Browse files
authored
Merge commit from fork
The HTTP/1 request encoder spliced the caller-supplied method into the request line verbatim. An application forwarding attacker-controlled input as the HTTP method was exposed to CRLF injection: a method containing "\r\n" could terminate the request line early, inject arbitrary headers, and pipeline a second attacker-chosen request onto the same connection. The request target has been validated since 1.7.0, but the method had no equivalent check. Validate it as an RFC 9110 token (1*tchar), the same production used for header names, rejecting CRLF, spaces, and other control characters. Fixes GHSA-2pg6-44cx-c49v.
1 parent 47e4802 commit fad0914

3 files changed

Lines changed: 44 additions & 0 deletions

File tree

lib/mint/http1.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ defmodule Mint.HTTP1 do
4848
4949
* `{:invalid_request_target, target}` - when the request target is invalid.
5050
51+
* `{:invalid_request_method, method}` - when the request method is invalid.
52+
5153
* `:invalid_header` - when headers can't be parsed correctly.
5254
5355
* `{:invalid_header_name, name}` - when a header name is invalid.
@@ -1176,6 +1178,10 @@ defmodule Mint.HTTP1 do
11761178
"invalid request target: #{inspect(target)}"
11771179
end
11781180

1181+
def format_error({:invalid_request_method, method}) do
1182+
"invalid request method: #{inspect(method)}"
1183+
end
1184+
11791185
def format_error({:invalid_header_name, name}) do
11801186
"invalid header name: #{inspect(name)}"
11811187
end

lib/mint/http1/request.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ defmodule Mint.HTTP1.Request do
44
import Mint.HTTP1.Parse
55

66
def encode(method, target, headers, body) do
7+
validate_method!(method)
8+
79
body = [
810
encode_request_line(method, target),
911
encode_headers(headers),
@@ -45,6 +47,17 @@ defmodule Mint.HTTP1.Request do
4547
[Integer.to_string(length, 16), "\r\n", chunk, "\r\n"]
4648
end
4749

50+
defp validate_method!(method) do
51+
_ =
52+
for <<char <- method>> do
53+
unless is_tchar(char) do
54+
throw({:mint, {:invalid_request_method, method}})
55+
end
56+
end
57+
58+
:ok
59+
end
60+
4861
defp validate_header_name!(name) do
4962
_ =
5063
for <<char <- name>> do

test/mint/http1/request_test.exs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,31 @@ defmodule Mint.HTTP1.RequestTest do
4242
assert Request.encode("GET", "/", [{"foo", "bar\r\n"}], nil) ==
4343
{:error, {:invalid_header_value, "foo", "bar\r\n"}}
4444
end
45+
46+
test "method with CRLF is rejected" do
47+
method = "GET / HTTP/1.1\r\nX-Smuggled: 1\r\nGET /admin"
48+
49+
assert Request.encode(method, "/", [], nil) ==
50+
{:error, {:invalid_request_method, method}}
51+
end
52+
53+
test "method with a space is rejected" do
54+
assert Request.encode("GET /admin", "/", [], nil) ==
55+
{:error, {:invalid_request_method, "GET /admin"}}
56+
end
57+
58+
test "method with a control character is rejected" do
59+
assert Request.encode("GET\t", "/", [], nil) ==
60+
{:error, {:invalid_request_method, "GET\t"}}
61+
end
62+
63+
test "custom token method is accepted" do
64+
assert encode_request("PROPFIND", "/", [], nil) ==
65+
request_string("""
66+
PROPFIND / HTTP/1.1
67+
68+
""")
69+
end
4570
end
4671

4772
describe "encode_chunk/1" do

0 commit comments

Comments
 (0)