Skip to content

Commit b27edbd

Browse files
committed
Fix port reuse problem
1 parent 2ba0e7a commit b27edbd

File tree

1 file changed

+125
-126
lines changed

1 file changed

+125
-126
lines changed

test/test.cc

Lines changed: 125 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,131 @@ TEST(BenchmarkTest, localhost) { performance_test("localhost"); }
146146

147147
TEST(BenchmarkTest, v6) { performance_test("::1"); }
148148

149+
TEST(VulnerabilityTest, CRLFInjection) {
150+
Server svr;
151+
152+
svr.Post("/test1", [](const Request & /*req*/, Response &res) {
153+
res.set_content("Hello 1", "text/plain");
154+
});
155+
156+
svr.Delete("/test2", [](const Request & /*req*/, Response &res) {
157+
res.set_content("Hello 2", "text/plain");
158+
});
159+
160+
svr.Put("/test3", [](const Request & /*req*/, Response &res) {
161+
res.set_content("Hello 3", "text/plain");
162+
});
163+
164+
svr.Patch("/test4", [](const Request & /*req*/, Response &res) {
165+
res.set_content("Hello 4", "text/plain");
166+
});
167+
168+
svr.set_logger([](const Request &req, const Response & /*res*/) {
169+
for (const auto &x : req.headers) {
170+
auto key = x.first;
171+
EXPECT_STRNE("evil", key.c_str());
172+
}
173+
});
174+
175+
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
176+
auto se = detail::scope_exit([&] {
177+
svr.stop();
178+
thread.join();
179+
ASSERT_FALSE(svr.is_running());
180+
});
181+
182+
svr.wait_until_ready();
183+
184+
{
185+
Client cli(HOST, PORT);
186+
187+
cli.Post("/test1", "A=B",
188+
"application/x-www-form-urlencoded\r\nevil: hello1");
189+
cli.Delete("/test2", "A=B", "text/plain\r\nevil: hello2");
190+
cli.Put("/test3", "text", "text/plain\r\nevil: hello3");
191+
cli.Patch("/test4", "content", "text/plain\r\nevil: hello4");
192+
}
193+
}
194+
195+
TEST(VulnerabilityTest, CRLFInjectionInHeaders) {
196+
auto server_thread = std::thread([] {
197+
auto srv = ::socket(AF_INET, SOCK_STREAM, 0);
198+
#ifndef _WIN32
199+
httplib::default_socket_options(srv);
200+
#endif
201+
202+
sockaddr_in addr{};
203+
addr.sin_family = AF_INET;
204+
addr.sin_port = htons(PORT);
205+
::inet_pton(AF_INET, HOST, &addr.sin_addr);
206+
::bind(srv, reinterpret_cast<sockaddr *>(&addr), sizeof(addr));
207+
::listen(srv, 1);
208+
209+
sockaddr_in cli_addr{};
210+
socklen_t cli_len = sizeof(cli_addr);
211+
auto cli = ::accept(srv, reinterpret_cast<sockaddr *>(&cli_addr), &cli_len);
212+
213+
struct timeval tv;
214+
tv.tv_sec = 1;
215+
tv.tv_usec = 0;
216+
::setsockopt(cli, SOL_SOCKET, SO_RCVTIMEO,
217+
#ifdef _WIN32
218+
reinterpret_cast<const char *>(&tv),
219+
#else
220+
&tv,
221+
#endif
222+
sizeof(tv));
223+
224+
std::string buf_all;
225+
char buf[2048];
226+
ssize_t n;
227+
228+
while ((n = ::recv(cli, buf, sizeof(buf), 0)) > 0) {
229+
buf_all.append(buf, static_cast<size_t>(n));
230+
231+
size_t pos;
232+
while ((pos = buf_all.find("\r\n\r\n")) != std::string::npos) {
233+
auto request_block = buf_all.substr(0, pos + 4); // include separator
234+
235+
auto e = request_block.find("\r\n");
236+
if (e != std::string::npos) {
237+
auto request_line = request_block.substr(0, e);
238+
std::string msg =
239+
"CRLF injection detected in request line: '" + request_line + "'";
240+
EXPECT_FALSE(true) << msg;
241+
}
242+
243+
std::string resp = "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello";
244+
::send(cli, resp.c_str(), resp.size(), 0);
245+
246+
buf_all.erase(0, pos + 4);
247+
}
248+
}
249+
250+
#ifdef _WIN32
251+
::closesocket(cli);
252+
::closesocket(srv);
253+
#else
254+
::close(cli);
255+
::close(srv);
256+
#endif
257+
});
258+
259+
std::this_thread::sleep_for(std::chrono::milliseconds(200));
260+
261+
auto cli = httplib::Client(HOST, PORT);
262+
263+
auto headers = httplib::Headers{
264+
{"A", "B\r\n\r\nGET /pwned HTTP/1.1\r\nHost: 127.0.0.1:1234\r\n\r\n"},
265+
{"Connection", "keep-alive"}};
266+
267+
auto res = cli.Get("/hi", headers);
268+
EXPECT_FALSE(res);
269+
EXPECT_EQ(httplib::Error::InvalidHeaders, res.error());
270+
271+
server_thread.join();
272+
}
273+
149274
class UnixSocketTest : public ::testing::Test {
150275
protected:
151276
void TearDown() override { std::remove(pathname_.c_str()); }
@@ -10625,132 +10750,6 @@ TEST(RedirectTest, Issue2185_Online) {
1062510750
}
1062610751
#endif
1062710752

10628-
TEST(VulnerabilityTest, CRLFInjection) {
10629-
Server svr;
10630-
10631-
svr.Post("/test1", [](const Request & /*req*/, Response &res) {
10632-
res.set_content("Hello 1", "text/plain");
10633-
});
10634-
10635-
svr.Delete("/test2", [](const Request & /*req*/, Response &res) {
10636-
res.set_content("Hello 2", "text/plain");
10637-
});
10638-
10639-
svr.Put("/test3", [](const Request & /*req*/, Response &res) {
10640-
res.set_content("Hello 3", "text/plain");
10641-
});
10642-
10643-
svr.Patch("/test4", [](const Request & /*req*/, Response &res) {
10644-
res.set_content("Hello 4", "text/plain");
10645-
});
10646-
10647-
svr.set_logger([](const Request &req, const Response & /*res*/) {
10648-
for (const auto &x : req.headers) {
10649-
auto key = x.first;
10650-
EXPECT_STRNE("evil", key.c_str());
10651-
}
10652-
});
10653-
10654-
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
10655-
auto se = detail::scope_exit([&] {
10656-
svr.stop();
10657-
thread.join();
10658-
ASSERT_FALSE(svr.is_running());
10659-
});
10660-
10661-
svr.wait_until_ready();
10662-
10663-
{
10664-
Client cli(HOST, PORT);
10665-
10666-
cli.Post("/test1", "A=B",
10667-
"application/x-www-form-urlencoded\r\nevil: hello1");
10668-
cli.Delete("/test2", "A=B", "text/plain\r\nevil: hello2");
10669-
cli.Put("/test3", "text", "text/plain\r\nevil: hello3");
10670-
cli.Patch("/test4", "content", "text/plain\r\nevil: hello4");
10671-
}
10672-
}
10673-
10674-
TEST(VulnerabilityTest, CRLFInjectionInHeaders) {
10675-
auto server_thread = std::thread([] {
10676-
auto srv = ::socket(AF_INET, SOCK_STREAM, 0);
10677-
int on = 1;
10678-
::setsockopt(srv, SOL_SOCKET, SO_REUSEADDR,
10679-
#ifdef _WIN32
10680-
reinterpret_cast<const char *>(&on),
10681-
#else
10682-
&on,
10683-
#endif
10684-
sizeof(on));
10685-
10686-
sockaddr_in addr{};
10687-
addr.sin_family = AF_INET;
10688-
addr.sin_port = htons(PORT);
10689-
::inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
10690-
::bind(srv, reinterpret_cast<sockaddr *>(&addr), sizeof(addr));
10691-
::listen(srv, 1);
10692-
10693-
sockaddr_in cli_addr{};
10694-
socklen_t cli_len = sizeof(cli_addr);
10695-
auto cli = ::accept(srv, reinterpret_cast<sockaddr *>(&cli_addr), &cli_len);
10696-
10697-
struct timeval tv;
10698-
tv.tv_sec = 1;
10699-
tv.tv_usec = 0;
10700-
::setsockopt(cli, SOL_SOCKET, SO_RCVTIMEO,
10701-
#ifdef _WIN32
10702-
reinterpret_cast<const char *>(&tv),
10703-
#else
10704-
&tv,
10705-
#endif
10706-
sizeof(tv));
10707-
10708-
std::string buf_all;
10709-
char buf[2048];
10710-
ssize_t n;
10711-
10712-
while ((n = ::recv(cli, buf, sizeof(buf), 0)) > 0) {
10713-
buf_all.append(buf, static_cast<size_t>(n));
10714-
10715-
size_t pos;
10716-
while ((pos = buf_all.find("\r\n\r\n")) != std::string::npos) {
10717-
auto request_block = buf_all.substr(0, pos + 4); // include separator
10718-
10719-
auto e = request_block.find("\r\n");
10720-
if (e != std::string::npos) {
10721-
auto request_line = request_block.substr(0, e);
10722-
std::string msg =
10723-
"CRLF injection detected in request line: '" + request_line + "'";
10724-
EXPECT_FALSE(true) << msg;
10725-
}
10726-
10727-
std::string resp = "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello";
10728-
::send(cli, resp.c_str(), resp.size(), 0);
10729-
10730-
buf_all.erase(0, pos + 4);
10731-
}
10732-
}
10733-
10734-
::close(cli);
10735-
::close(srv);
10736-
});
10737-
10738-
std::this_thread::sleep_for(std::chrono::milliseconds(200));
10739-
10740-
auto cli = httplib::Client("127.0.0.1", PORT);
10741-
10742-
auto headers = httplib::Headers{
10743-
{"A", "B\r\n\r\nGET /pwned HTTP/1.1\r\nHost: 127.0.0.1:1234\r\n\r\n"},
10744-
{"Connection", "keep-alive"}};
10745-
10746-
auto res = cli.Get("/hi", headers);
10747-
EXPECT_FALSE(res);
10748-
10749-
if (res) { EXPECT_EQ(httplib::Error::InvalidHeaders, res.error()); }
10750-
10751-
server_thread.join();
10752-
}
10753-
1075410753
TEST(PathParamsTest, StaticMatch) {
1075510754
const auto pattern = "/users/all";
1075610755
detail::PathParamsMatcher matcher(pattern);

0 commit comments

Comments
 (0)