From 4e7a13314ddeb5db80babb329f648bdc99b0f181 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Mon, 11 Nov 2013 07:30:33 +0800 Subject: [PATCH] real http status codes update attack.sh Signed-off-by: Andy Green --- lib/handshake.c | 1 + lib/libwebsockets.c | 4 +- lib/libwebsockets.h | 35 ++++++++++++ lib/output.c | 75 ------------------------- lib/server.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++ test-server/attack.sh | 40 ++++++------- test-server/test-server.c | 17 ++++++ 7 files changed, 210 insertions(+), 101 deletions(-) diff --git a/lib/handshake.c b/lib/handshake.c index f1e7dad..1935c25 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -139,6 +139,7 @@ libwebsocket_read(struct libwebsocket_context *context, memset(&wsi->u, 0, sizeof(wsi->u)); wsi->mode = LWS_CONNMODE_HTTP_SERVING_ACCEPTED; wsi->state = WSI_STATE_HTTP; + wsi->u.http.fd = -1; /* expose it at the same offset as u.hdr */ wsi->u.http.ah = ah; diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 475162b..2cb3ab0 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -217,10 +217,10 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context, } - if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED && wsi->u.http.fd) { + if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED && wsi->u.http.fd >= 0) { lwsl_debug("closing http fd %d\n", wsi->u.http.fd); close(wsi->u.http.fd); - wsi->u.http.fd = 0; + wsi->u.http.fd = -1; context->protocols[0].callback(context, wsi, LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0); } diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 54d485a..4c121af 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -127,6 +127,8 @@ LWS_VISIBLE LWS_EXTERN void lwsl_hexdump(void *buf, size_t len); #endif +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + enum libwebsocket_context_options { LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT = 2, LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME = 4, @@ -394,6 +396,34 @@ enum lws_close_status { LWS_CLOSE_STATUS_TLS_FAILURE = 1015, }; +enum http_status { + HTTP_STATUS_BAD_REQUEST = 400, + HTTP_STATUS_UNAUTHORIZED, + HTTP_STATUS_PAYMENT_REQUIRED, + HTTP_STATUS_FORBIDDEN, + HTTP_STATUS_NOT_FOUND, + HTTP_STATUS_METHOD_NOT_ALLOWED, + HTTP_STATUS_NOT_ACCEPTABLE, + HTTP_STATUS_PROXY_AUTH_REQUIRED, + HTTP_STATUS_REQUEST_TIMEOUT, + HTTP_STATUS_CONFLICT, + HTTP_STATUS_GONE, + HTTP_STATUS_LENGTH_REQUIRED, + HTTP_STATUS_PRECONDITION_FAILED, + HTTP_STATUS_REQ_ENTITY_TOO_LARGE, + HTTP_STATUS_REQ_URI_TOO_LONG, + HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, + HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE, + HTTP_STATUS_EXPECTATION_FAILED, + + HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, + HTTP_STATUS_NOT_IMPLEMENTED, + HTTP_STATUS_BAD_GATEWAY, + HTTP_STATUS_SERVICE_UNAVAILABLE, + HTTP_STATUS_GATEWAY_TIMEOUT, + HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, +}; + struct libwebsocket; struct libwebsocket_context; /* needed even with extensions disabled for create context */ @@ -940,6 +970,11 @@ LWS_VISIBLE LWS_EXTERN int libwebsockets_serve_http_file_fragment(struct libwebsocket_context *context, struct libwebsocket *wsi); +LWS_VISIBLE int libwebsockets_return_http_status( + struct libwebsocket_context *context, + struct libwebsocket *wsi, unsigned int code, + const char *html_body); + LWS_VISIBLE LWS_EXTERN const struct libwebsocket_protocols * libwebsockets_get_protocol(struct libwebsocket *wsi); diff --git a/lib/output.c b/lib/output.c index 9abe087..5a661b0 100644 --- a/lib/output.c +++ b/lib/output.c @@ -641,78 +641,3 @@ LWS_VISIBLE int libwebsockets_serve_http_file_fragment( return 0; /* indicates further processing must be done */ } - -/** - * libwebsockets_serve_http_file() - Send a file back to the client using http - * @context: libwebsockets context - * @wsi: Websocket instance (available from user callback) - * @file: The file to issue over http - * @content_type: The http content type, eg, text/html - * @other_headers: NULL or pointer to \0-terminated other header string - * - * This function is intended to be called from the callback in response - * to http requests from the client. It allows the callback to issue - * local files down the http link in a single step. - * - * Returning <0 indicates error and the wsi should be closed. Returning - * >0 indicates the file was completely sent and the wsi should be closed. - * ==0 indicates the file transfer is started and needs more service later, - * the wsi should be left alone. - */ - -LWS_VISIBLE int libwebsockets_serve_http_file( - struct libwebsocket_context *context, - struct libwebsocket *wsi, const char *file, - const char *content_type, const char *other_headers) -{ - struct stat stat_buf; - unsigned char *p = context->service_buffer; - int ret = 0; - int n; - - wsi->u.http.fd = open(file, O_RDONLY -#ifdef WIN32 - | _O_BINARY -#endif - ); - - if (wsi->u.http.fd < 1) { - lwsl_err("Unable to open '%s'\n", file); - p += sprintf((char *)p, - "HTTP/1.0 400 Bad\x0d\x0aServer: libwebsockets\x0d\x0a\x0d\x0a" - ); - wsi->u.http.fd = 0; - /* too small to care about partial, closing anyway */ - libwebsocket_write(wsi, context->service_buffer, - p - context->service_buffer, LWS_WRITE_HTTP); - - return -1; - } - - fstat(wsi->u.http.fd, &stat_buf); - wsi->u.http.filelen = stat_buf.st_size; - p += sprintf((char *)p, -"HTTP/1.0 200 OK\x0d\x0aServer: libwebsockets\x0d\x0a""Content-Type: %s\x0d\x0a", - content_type); - if (other_headers) { - n = strlen(other_headers); - memcpy(p, other_headers, n); - p += n; - } - p += sprintf((char *)p, - "Content-Length: %u\x0d\x0a\x0d\x0a", - (unsigned int)stat_buf.st_size); - - ret = libwebsocket_write(wsi, context->service_buffer, - p - context->service_buffer, LWS_WRITE_HTTP); - if (ret != (p - context->service_buffer)) { - lwsl_err("_write returned %d from %d\n", ret, (p - context->service_buffer)); - return -1; - } - - wsi->u.http.filepos = 0; - wsi->state = WSI_STATE_HTTP_ISSUING_FILE; - - return libwebsockets_serve_http_file_fragment(context, wsi); -} - diff --git a/lib/server.c b/lib/server.c index bc8b497..7c0421a 100644 --- a/lib/server.c +++ b/lib/server.c @@ -389,3 +389,142 @@ int lws_server_socket_service(struct libwebsocket_context *context, return 0; } + +static const char *err400[] = { + "Bad Request", + "Unauthorized", + "Payment Required", + "Forbidden", + "Not Found", + "Method Not Allowed", + "Not Acceptable", + "Proxy Auth Required", + "Request Timeout", + "Conflict", + "Gone", + "Length Required", + "Precondition Failed", + "Request Entity Too Large", + "Request URI too Long", + "Unsupported Media Type", + "Requested Range Not Satisfiable", + "Expectation Failed" +}; + +static const char *err500[] = { + "Internal Server Error", + "Not Implemented", + "Bad Gateway", + "Service Unavailable", + "Gateway Timeout", + "HTTP Version Not Supported" +}; + +/** + * libwebsockets_return_http_status() - Return simple http status + * @context: libwebsockets context + * @wsi: Websocket instance (available from user callback) + * @code: Status index, eg, 404 + * @html_body: User-readable HTML description, or NULL + * + * Helper to report HTTP errors back to the client cleanly and + * consistently + */ +LWS_VISIBLE int libwebsockets_return_http_status( + struct libwebsocket_context *context, struct libwebsocket *wsi, + unsigned int code, const char *html_body) +{ + int n, m; + const char *description = ""; + + if (!html_body) + html_body = ""; + + if (code >= 400 && code < (400 + ARRAY_SIZE(err400))) + description = err400[code - 400]; + if (code >= 500 && code < (500 + ARRAY_SIZE(err500))) + description = err500[code - 500]; + + n = sprintf((char *)context->service_buffer, + "HTTP/1.0 %u %s\x0d\x0a" + "Server: libwebsockets\x0d\x0a" + "Mime-Type: text/html\x0d\x0a\x0d\x0a" + "

%u %s

%s", + code, description, code, description, html_body); + + lwsl_info((const char *)context->service_buffer); + + m = libwebsocket_write(wsi, context->service_buffer, n, LWS_WRITE_HTTP); + + return m; +} + +/** + * libwebsockets_serve_http_file() - Send a file back to the client using http + * @context: libwebsockets context + * @wsi: Websocket instance (available from user callback) + * @file: The file to issue over http + * @content_type: The http content type, eg, text/html + * @other_headers: NULL or pointer to \0-terminated other header string + * + * This function is intended to be called from the callback in response + * to http requests from the client. It allows the callback to issue + * local files down the http link in a single step. + * + * Returning <0 indicates error and the wsi should be closed. Returning + * >0 indicates the file was completely sent and the wsi should be closed. + * ==0 indicates the file transfer is started and needs more service later, + * the wsi should be left alone. + */ + +LWS_VISIBLE int libwebsockets_serve_http_file( + struct libwebsocket_context *context, + struct libwebsocket *wsi, const char *file, + const char *content_type, const char *other_headers) +{ + struct stat stat_buf; + unsigned char *p = context->service_buffer; + int ret = 0; + int n; + + wsi->u.http.fd = open(file, O_RDONLY +#ifdef WIN32 + | _O_BINARY +#endif + ); + + if (wsi->u.http.fd < 1) { + lwsl_err("Unable to open '%s'\n", file); + libwebsockets_return_http_status(context, wsi, + HTTP_STATUS_NOT_FOUND, NULL); + wsi->u.http.fd = -1; + return -1; + } + + fstat(wsi->u.http.fd, &stat_buf); + wsi->u.http.filelen = stat_buf.st_size; + p += sprintf((char *)p, +"HTTP/1.0 200 OK\x0d\x0aServer: libwebsockets\x0d\x0a""Content-Type: %s\x0d\x0a", + content_type); + if (other_headers) { + n = strlen(other_headers); + memcpy(p, other_headers, n); + p += n; + } + p += sprintf((char *)p, + "Content-Length: %u\x0d\x0a\x0d\x0a", + (unsigned int)stat_buf.st_size); + + ret = libwebsocket_write(wsi, context->service_buffer, + p - context->service_buffer, LWS_WRITE_HTTP); + if (ret != (p - context->service_buffer)) { + lwsl_err("_write returned %d from %d\n", ret, (p - context->service_buffer)); + return -1; + } + + wsi->u.http.filepos = 0; + wsi->state = WSI_STATE_HTTP_ISSUING_FILE; + + return libwebsockets_serve_http_file_fragment(context, wsi); +} + diff --git a/test-server/attack.sh b/test-server/attack.sh index 84328ac..c127907 100755 --- a/test-server/attack.sh +++ b/test-server/attack.sh @@ -107,7 +107,7 @@ check echo echo "---- good request but http payload coming too (should be ignored and test.html served)" -echo -e "GET blah HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD........................................" \ +echo -e "GET /test.html HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD........................................" \ "......................................................................................................................." \ "......................................................................................................................." \ "......................................................................................................................." \ @@ -125,10 +125,9 @@ echo -e "GET blah HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD....................... "......................................................................................................................." \ | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap check -size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3` -if [ $size -ne 0 ] ; then - echo "FAIL: got something back when should have hung up" - cat /tmp/lwscap +diff /tmp/lwscap /usr/share/libwebsockets-test-server/test.html > /dev/null +if [ $? -ne 0 ] ; then + echo "FAIL: got something other than test.html back" exit 1 fi @@ -137,9 +136,8 @@ echo "---- directory attack 1 (/../../../../etc/passwd should be /etc/passswd)" rm -f /tmp/lwscap echo -e "GET /../../../../etc/passwd HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap check -size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3` -if [ $size -ne 0 ] ; then - echo "FAIL: got something back when should have hung up" +if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then + echo "FAIL: should have told forbidden (test server has no dirs)" exit 1 fi @@ -170,9 +168,8 @@ echo "---- directory attack 4 (/blah/.. should be /blah/)" rm -f /tmp/lwscap echo -e "GET /blah/.. HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap check -size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3` -if [ $size -ne 0 ] ; then - echo "FAIL: got something back when should have hung up" +if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then + echo "FAIL: should have told forbidden (test server has no dirs)" exit 1 fi @@ -181,9 +178,8 @@ echo "---- directory attack 5 (/blah/../ should be /blah/)" rm -f /tmp/lwscap echo -e "GET /blah/../ HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap check -size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3` -if [ $size -ne 0 ] ; then - echo "FAIL: got something back when should have hung up" +if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then + echo "FAIL: should have told forbidden (test server has no dirs)" exit 1 fi @@ -192,9 +188,8 @@ echo "---- directory attack 6 (/blah/../. should be /blah/)" rm -f /tmp/lwscap echo -e "GET /blah/../. HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap check -size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3` -if [ $size -ne 0 ] ; then - echo "FAIL: got something back when should have hung up" +if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then + echo "FAIL: should have told forbidden (test server has no dirs)" exit 1 fi @@ -203,9 +198,8 @@ echo "---- directory attack 7 (/%2e%2e%2f../../../etc/passwd should be /etc/pass rm -f /tmp/lwscap echo -e "GET /%2e%2e%2f../../../etc/passwd HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap check -size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3` -if [ $size -ne 0 ] ; then - echo "FAIL: got something back when should have hung up" +if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then + echo "FAIL: should have told forbidden (test server has no dirs)" exit 1 fi @@ -214,13 +208,11 @@ echo "---- directory attack 7 (%2f%2e%2e%2f%2e./.%2e/.%2e%2fetc/passwd should be rm -f /tmp/lwscap echo -e "GET %2f%2e%2e%2f%2e./.%2e/.%2e%2fetc/passwd HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap check -size=`stat /tmp/lwscap | grep Size: | tr -s ' ' | cut -d' ' -f3` -if [ $size -ne 0 ] ; then - echo "FAIL: got something back when should have hung up" +if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then + echo "FAIL: should have told forbidden (test server has no dirs)" exit 1 fi - echo echo "--- survived" kill -2 $CPID diff --git a/test-server/test-server.c b/test-server/test-server.c index 35150de..899314c 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -152,6 +152,19 @@ static int callback_http(struct libwebsocket_context *context, switch (reason) { case LWS_CALLBACK_HTTP: + if (len < 1) { + libwebsockets_return_http_status(context, wsi, + HTTP_STATUS_BAD_REQUEST, NULL); + return -1; + } + + /* this server has no concept of directories */ + if (strchr((const char *)in + 1, '/')) { + libwebsockets_return_http_status(context, wsi, + HTTP_STATUS_FORBIDDEN, NULL); + return -1; + } + /* check for the "send a big file by hand" example case */ if (!strcmp((const char *)in, "/leaf.jpg")) { @@ -217,9 +230,13 @@ static int callback_http(struct libwebsocket_context *context, } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; + + /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); + libwebsockets_return_http_status(context, wsi, + HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } -- 2.7.4