@@ -146,6 +146,131 @@ TEST(BenchmarkTest, localhost) { performance_test("localhost"); }
146146
147147TEST (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\n evil: hello1" );
189+ cli.Delete (" /test2" , " A=B" , " text/plain\r\n evil: hello2" );
190+ cli.Put (" /test3" , " text" , " text/plain\r\n evil: hello3" );
191+ cli.Patch (" /test4" , " content" , " text/plain\r\n evil: 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\n Content-Length: 5\r\n\r\n Hello" ;
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\n GET /pwned HTTP/1.1\r\n Host: 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+
149274class UnixSocketTest : public ::testing::Test {
150275protected:
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\n evil: hello1" );
10668- cli.Delete (" /test2" , " A=B" , " text/plain\r\n evil: hello2" );
10669- cli.Put (" /test3" , " text" , " text/plain\r\n evil: hello3" );
10670- cli.Patch (" /test4" , " content" , " text/plain\r\n evil: 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\n Content-Length: 5\r\n\r\n Hello" ;
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\n GET /pwned HTTP/1.1\r\n Host: 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-
1075410753TEST (PathParamsTest, StaticMatch) {
1075510754 const auto pattern = " /users/all" ;
1075610755 detail::PathParamsMatcher matcher (pattern);
0 commit comments