Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 290e1e3

Browse files
committed
improve validation of HTTP methods
1 parent 3060843 commit 290e1e3

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed

src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,10 +807,21 @@ protected RequestLineStatus TakeStartLine(SocketInput input)
807807
}
808808

809809
method = begin.GetAsciiString(scan);
810+
810811
if (method == null)
811812
{
812813
RejectRequest("Missing method.");
813814
}
815+
816+
// Note: We're not in the fast path any more (GetKnownMethod should have handled any HTTP Method we're aware of)
817+
// So we can be a tiny bit slower and more careful here.
818+
for (int i = 0; i < method.Length; i++)
819+
{
820+
if (!IsValidTokenChar(method[i]))
821+
{
822+
RejectRequest("Invalid method.");
823+
}
824+
}
814825
}
815826
else
816827
{
@@ -938,6 +949,31 @@ protected RequestLineStatus TakeStartLine(SocketInput input)
938949
}
939950
}
940951

952+
private static bool IsValidTokenChar(char c)
953+
{
954+
// Determines if a character is valid as a 'token' as defined in the
955+
// HTTP spec: https://tools.ietf.org/html/rfc7230#section-3.2.6
956+
return
957+
(c >= '0' && c <= '9') ||
958+
(c >= 'A' && c <= 'Z') ||
959+
(c >= 'a' && c <= 'z') ||
960+
c == '!' ||
961+
c == '#' ||
962+
c == '$' ||
963+
c == '%' ||
964+
c == '&' ||
965+
c == '\'' ||
966+
c == '*' ||
967+
c == '+' ||
968+
c == '-' ||
969+
c == '.' ||
970+
c == '^' ||
971+
c == '_' ||
972+
c == '`' ||
973+
c == '|' ||
974+
c == '~';
975+
}
976+
941977
private bool RequestUrlStartsWithPathBase(string requestUrl, out bool caseMatches)
942978
{
943979
caseMatches = true;

test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class BadHttpRequestTests
4545
// Missing method
4646
[InlineData(" ")]
4747
// Missing second space
48-
[InlineData("/ HTTP/1.1\r\n\r\n")]
48+
[InlineData("/ ")] // This fails trying to read the '/' because that's invalid for an HTTP method
4949
[InlineData("GET /\r\n")]
5050
// Missing target
5151
[InlineData("GET ")]
@@ -67,13 +67,33 @@ public class BadHttpRequestTests
6767
[InlineData("GET / 8charact\r")]
6868
// Missing LF
6969
[InlineData("GET / HTTP/1.0\rA")]
70+
// Bad HTTP Methods (invalid according to RFC)
71+
[InlineData("( / HTTP/1.0\r\n")]
72+
[InlineData(") / HTTP/1.0\r\n")]
73+
[InlineData("< / HTTP/1.0\r\n")]
74+
[InlineData("> / HTTP/1.0\r\n")]
75+
[InlineData("@ / HTTP/1.0\r\n")]
76+
[InlineData(", / HTTP/1.0\r\n")]
77+
[InlineData("; / HTTP/1.0\r\n")]
78+
[InlineData(": / HTTP/1.0\r\n")]
79+
[InlineData("\\ / HTTP/1.0\r\n")]
80+
[InlineData("\" / HTTP/1.0\r\n")]
81+
[InlineData("/ / HTTP/1.0\r\n")]
82+
[InlineData("[ / HTTP/1.0\r\n")]
83+
[InlineData("] / HTTP/1.0\r\n")]
84+
[InlineData("? / HTTP/1.0\r\n")]
85+
[InlineData("= / HTTP/1.0\r\n")]
86+
[InlineData("{ / HTTP/1.0\r\n")]
87+
[InlineData("} / HTTP/1.0\r\n")]
88+
[InlineData("get@ / HTTP/1.0\r\n")]
89+
[InlineData("post= / HTTP/1.0\r\n")]
7090
public async Task TestBadRequestLines(string request)
7191
{
7292
using (var server = new TestServer(context => TaskUtilities.CompletedTask))
7393
{
7494
using (var connection = server.CreateConnection())
7595
{
76-
await connection.SendEnd(request);
96+
await connection.SendAllEnd(request);
7797
await ReceiveBadRequestResponse(connection);
7898
}
7999
}
@@ -84,6 +104,26 @@ public async Task TestBadRequestLines(string request)
84104
[InlineData("GET ")]
85105
[InlineData("GET / HTTP/1.2\r")]
86106
[InlineData("GET / HTTP/1.0\rA")]
107+
// Bad HTTP Methods (invalid according to RFC)
108+
[InlineData("( ")]
109+
[InlineData(") ")]
110+
[InlineData("< ")]
111+
[InlineData("> ")]
112+
[InlineData("@ ")]
113+
[InlineData(", ")]
114+
[InlineData("; ")]
115+
[InlineData(": ")]
116+
[InlineData("\\ ")]
117+
[InlineData("\" ")]
118+
[InlineData("/ ")]
119+
[InlineData("[ ")]
120+
[InlineData("] ")]
121+
[InlineData("? ")]
122+
[InlineData("= ")]
123+
[InlineData("{ ")]
124+
[InlineData("} ")]
125+
[InlineData("get@ ")]
126+
[InlineData("post= ")]
87127
public async Task ServerClosesConnectionAsSoonAsBadRequestLineIsDetected(string request)
88128
{
89129
using (var server = new TestServer(context => TaskUtilities.CompletedTask))

test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ public void Dispose()
4343
_socket.Dispose();
4444
}
4545

46+
public async Task SendAll(params string[] lines)
47+
{
48+
var text = String.Join("\r\n", lines);
49+
var writer = new StreamWriter(_stream, Encoding.ASCII);
50+
await writer.WriteAsync(text);
51+
writer.Flush();
52+
_stream.Flush();
53+
}
54+
55+
public async Task SendAllEnd(params string[] lines)
56+
{
57+
await SendAll(lines);
58+
_socket.Shutdown(SocketShutdown.Send);
59+
}
60+
4661
public async Task Send(params string[] lines)
4762
{
4863
var text = String.Join("\r\n", lines);

0 commit comments

Comments
 (0)