From b1a9e508cd1626f1b1fa411711d9ddb64a86d8e3 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sun, 10 Nov 2013 15:15:21 +0800 Subject: [PATCH] translate and protect uri test sever use uri path This translates %xx in the GET uri and removes /.. and /... type sequences along with translating // or /// etc to /. Since the result is hopefully secure, it also changes the test server to actually use the uri path pasted on a resource directory without whitelisting. Signed-off-by: Andy Green --- lib/parsers.c | 135 ++++++++++++++++++++++++++++++++++++++++---- lib/private-libwebsockets.h | 15 +++++ test-server/test-server.c | 47 ++++++++++----- 3 files changed, 172 insertions(+), 25 deletions(-) diff --git a/lib/parsers.c b/lib/parsers.c index ba10899..f6d7120 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -144,6 +144,33 @@ int lws_hdr_simple_create(struct libwebsocket *wsi, return 0; } +static char char_to_hex(const char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -1; +} + +static int issue_char(struct libwebsocket *wsi, unsigned char c) +{ + if (wsi->u.hdr.ah->pos == sizeof(wsi->u.hdr.ah->data)) { + lwsl_warn("excessive header content\n"); + return -1; + } + wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = c; + if (c) + wsi->u.hdr.ah->frags[wsi->u.hdr.ah->next_frag_index].len++; + + return 0; +} + int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) { int n; @@ -188,12 +215,105 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) wsi->u.hdr.parser_state]].len && c == ' ') break; - /* special case space terminator for get-uri */ - if (wsi->u.hdr.parser_state == WSI_TOKEN_GET_URI && c == ' ') { + if (wsi->u.hdr.parser_state != WSI_TOKEN_GET_URI) + goto check_eol; + + /* special URI processing... end at space */ + + if (c == ' ') { + /* enforce starting with / */ + if (!wsi->u.hdr.ah->frags[wsi->u.hdr.ah->next_frag_index].len) + if (issue_char(wsi, '/') < 0) + return -1; c = '\0'; wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; + goto spill; } + /* special URI processing... convert %xx */ + + switch (wsi->u.hdr.ues) { + case URIES_IDLE: + if (c == '%') { + wsi->u.hdr.ues = URIES_SEEN_PERCENT; + goto swallow; + } + break; + case URIES_SEEN_PERCENT: + if (char_to_hex(c) < 0) { + /* regurgitate */ + if (issue_char(wsi, '%') < 0) + return -1; + wsi->u.hdr.ues = URIES_IDLE; + /* continue on to assess c */ + break; + } + wsi->u.hdr.esc_stash = c; + wsi->u.hdr.ues = URIES_SEEN_PERCENT_H1; + break; + + case URIES_SEEN_PERCENT_H1: + if (char_to_hex(c) < 0) { + /* regurgitate */ + issue_char(wsi, '%'); + wsi->u.hdr.ues = URIES_IDLE; + /* regurgitate + assess */ + if (libwebsocket_parse(wsi, wsi->u.hdr.esc_stash) < 0) + return -1; + /* continue on to assess c */ + break; + } + c = (char_to_hex(wsi->u.hdr.esc_stash) << 4) | + char_to_hex(c); + break; + } + + /* + * special URI processing... + * convert /.. or /... etc to / + * convert // or /// etc to / + * leave /.dir or whatever alone + */ + + switch (wsi->u.hdr.ups) { + case URIPS_IDLE: + /* issue the first / always */ + if (c == '/') + wsi->u.hdr.ups = URIPS_SEEN_SLASH; + break; + case URIPS_SEEN_SLASH: + /* swallow subsequent slashes */ + if (c == '/') + goto swallow; + /* track and swallow the first . after / */ + if (c == '.') { + wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT; + goto swallow; + } else + wsi->u.hdr.ups = URIPS_IDLE; + break; + case URIPS_SEEN_SLASH_DOT: + /* swallow second . */ + if (c == '.') { + wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT_DOT; + goto swallow; + } + /* it was like /.dir ... regurgitate the . */ + wsi->u.hdr.ups = URIPS_IDLE; + issue_char(wsi, '.'); + break; + + case URIPS_SEEN_SLASH_DOT_DOT: + /* swallow prior .. chars and any subsequent . */ + if (c == '.') + goto swallow; + else /* last we issued was / so SEEN_SLASH */ + wsi->u.hdr.ups = URIPS_SEEN_SLASH; + break; + } + +check_eol: + /* bail at EOL */ if (wsi->u.hdr.parser_state != WSI_TOKEN_CHALLENGE && c == '\x0d') { @@ -202,15 +322,10 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) lwsl_parser("*\n"); } - if (wsi->u.hdr.ah->pos == sizeof(wsi->u.hdr.ah->data)) { - lwsl_warn("excessive header content\n"); +spill: + if (issue_char(wsi, c) < 0) return -1; - } - wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = c; - if (c) - wsi->u.hdr.ah->frags[ - wsi->u.hdr.ah->next_frag_index].len++; - +swallow: /* per-protocol end of headers management */ if (wsi->u.hdr.parser_state == WSI_TOKEN_CHALLENGE) diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 7cf8899..5d2b470 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -299,6 +299,18 @@ struct libwebsocket_context { void *user_space; }; +enum uri_path_states { + URIPS_IDLE, + URIPS_SEEN_SLASH, + URIPS_SEEN_SLASH_DOT, + URIPS_SEEN_SLASH_DOT_DOT, +}; + +enum uri_esc_states { + URIES_IDLE, + URIES_SEEN_PERCENT, + URIES_SEEN_PERCENT_H1, +}; /* * This is totally opaque to code using the library. It's exported as a @@ -335,6 +347,9 @@ struct _lws_header_related { struct allocated_headers *ah; short lextable_pos; unsigned char parser_state; /* enum lws_token_indexes */ + enum uri_path_states ups; + enum uri_esc_states ues; + char esc_stash; }; struct _lws_websocket_related { diff --git a/test-server/test-server.c b/test-server/test-server.c index dacc22f..2c01f53 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -99,18 +99,29 @@ struct serveable { const char *mimetype; }; -static const struct serveable whitelist[] = { - { "/favicon.ico", "image/x-icon" }, - { "/libwebsockets.org-logo.png", "image/png" }, - - /* last one is the default served if no match */ - { "/test.html", "text/html" }, -}; - struct per_session_data__http { int fd; }; +const char * get_mimetype(const char *file) +{ + int n = strlen(file); + + if (n < 5) + return NULL; + + if (!strcmp(&file[n - 4], ".ico")) + return "image/x-icon"; + + if (!strcmp(&file[n - 4], ".png")) + return "image/png"; + + if (!strcmp(&file[n - 5], ".html")) + return "text/html"; + + return NULL; +} + /* this protocol server (always the first one) just knows how to do HTTP */ static int callback_http(struct libwebsocket_context *context, @@ -133,6 +144,7 @@ static int callback_http(struct libwebsocket_context *context, struct stat stat_buf; struct per_session_data__http *pss = (struct per_session_data__http *)user; + const char *mimetype; #ifdef EXTERNAL_POLL int fd = (int)(long)in; #endif @@ -197,12 +209,17 @@ static int callback_http(struct libwebsocket_context *context, } /* if not, send a file the easy way */ - - for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++) - if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0) - break; - - sprintf(buf, "%s%s", resource_path, whitelist[n].urlpath); + strcpy(buf, resource_path); + if (strcmp(in, "/")) + strncat(buf, in, sizeof(buf) - strlen(resource_path)); + else /* default file to serve */ + strcat(buf, "/test.html"); + buf[sizeof(buf) - 1] = '\0'; + mimetype = get_mimetype(buf); + if (!mimetype) { + lwsl_err("Unknown mimetype for %s\n", buf); + return -1; + } /* demostrates how to set a cookie on / */ @@ -223,7 +240,7 @@ static int callback_http(struct libwebsocket_context *context, } if (libwebsockets_serve_http_file(context, wsi, buf, - whitelist[n].mimetype, other_headers)) + mimetype, other_headers)) return -1; /* through completion or error, close the socket */ /* -- 2.7.4