From 1e3f7b8de914eb3fb8d0e1b24ce8453f13736b18 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Wed, 13 Nov 2013 07:45:17 +0800 Subject: [PATCH] introduce uri args If the URI coming from the client contains '?' then - the URI part is terminated with a '\0' - the remainder of the URI goes in a new header WSI_TOKEN_HTTP_URI_ARGS - the remainder of the URI is not subject to path sanitization measures (it still has %xx processing done on it) In the test server, http requests now also dump header information to stderr. The attack.sh script is simplified and can now parse the test server header dumps. Signed-off-by: Andy Green --- lib/libwebsockets.h | 1 + lib/parsers.c | 25 +++++++ lib/private-libwebsockets.h | 1 + test-server/attack.sh | 99 ++++++++++++++-------------- test-server/test-server.c | 126 +++++++++++++++++++----------------- 5 files changed, 139 insertions(+), 113 deletions(-) diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 4c121af3..89e3798d 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -268,6 +268,7 @@ enum lws_token_indexes { WSI_TOKEN_HTTP_DATE, WSI_TOKEN_HTTP_RANGE, WSI_TOKEN_HTTP_REFERER, + WSI_TOKEN_HTTP_URI_ARGS, WSI_TOKEN_MUXURL, diff --git a/lib/parsers.c b/lib/parsers.c index c5142519..fb43fca9 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -334,6 +334,9 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) else /* last we issued was / so SEEN_SLASH */ wsi->u.hdr.ups = URIPS_SEEN_SLASH; break; + case URIPS_ARGUMENTS: + /* leave them alone */ + break; } check_eol: @@ -346,6 +349,28 @@ check_eol: lwsl_parser("*\n"); } + if (c == '?') { /* start of URI arguments */ + /* seal off uri header */ + wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = '\0'; + + /* move to using WSI_TOKEN_HTTP_URI_ARGS */ + wsi->u.hdr.ah->next_frag_index++; + wsi->u.hdr.ah->frags[ + wsi->u.hdr.ah->next_frag_index].offset = + wsi->u.hdr.ah->pos; + wsi->u.hdr.ah->frags[ + wsi->u.hdr.ah->next_frag_index].len = 0; + wsi->u.hdr.ah->frags[ + wsi->u.hdr.ah->next_frag_index].next_frag_index = 0; + + wsi->u.hdr.ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = + wsi->u.hdr.ah->next_frag_index; + + /* defeat normal uri path processing */ + wsi->u.hdr.ups = URIPS_ARGUMENTS; + goto swallow; + } + spill: if (issue_char(wsi, c) < 0) return -1; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 5d2b4704..740066df 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -304,6 +304,7 @@ enum uri_path_states { URIPS_SEEN_SLASH, URIPS_SEEN_SLASH_DOT, URIPS_SEEN_SLASH_DOT_DOT, + URIPS_ARGUMENTS, }; enum uri_esc_states { diff --git a/test-server/attack.sh b/test-server/attack.sh index e6f10be1..0186f486 100755 --- a/test-server/attack.sh +++ b/test-server/attack.sh @@ -16,6 +16,29 @@ function check { exit 1 fi dd if=$LOG bs=1 skip=$LEN 2>/dev/null + + if [ "$1" = "default" ] ; then + 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 + fi + + if [ "$1" = "forbidden" ] ; then + if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then + echo "FAIL: should have told forbidden (test server has no dirs)" + exit 1 + fi + fi + + if [ "$1" == "args" ] ; then + a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep Uri.Args\: | tr -s ' ' | cut -d' ' -f4-`" + if [ "$a" != "$2" ] ; then + echo "Args '$a' not $2" + exit 1 + fi + fi LEN=`stat $LOG -c %s` } @@ -30,6 +53,19 @@ while [ -z "`grep Listening $LOG`" ] ; do done check +echo +echo "---- ? processing (%2f%2e%2e%2f%2e./test.html?arg=1)" +rm -f /tmp/lwscap +echo -e "GET %2f%2e%2e%2f%2e./test.html?arg=1 HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap +check args "arg=1" + +echo +echo "---- ? processing (%2f%2e%2e%2f%2e./test.html?arg=/../.)" +rm -f /tmp/lwscap +echo -e "GET %2f%2e%2e%2f%2e./test.html?arg=/../. HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap +check args "arg=/../." + + echo echo "---- spam enough crap to not be GET" echo "not GET" | nc $SERVER $PORT @@ -124,99 +160,58 @@ echo -e "GET /test.html HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD................. "......................................................................................................................." \ "......................................................................................................................." \ | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap -check -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 +check default echo 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 -if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then - echo "FAIL: should have told forbidden (test server has no dirs)" - exit 1 -fi +check forbidden echo echo "---- directory attack 2 (/../ should be /)" rm -f /tmp/lwscap echo -e "GET /../ HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap -check -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 +check default echo echo "---- directory attack 3 (/./ should be /)" rm -f /tmp/lwscap echo -e "GET /./ HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap -check -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 +check default echo echo "---- directory attack 4 (/blah/.. should be /)" 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 -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 +check default echo echo "---- directory attack 5 (/blah/../ should be /)" 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 -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 +check default echo echo "---- directory attack 6 (/blah/../. should be /)" 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 -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 +check default echo echo "---- directory attack 7 (/%2e%2e%2f../../../etc/passwd should be /etc/passswd)" 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 -if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then - echo "FAIL: should have told forbidden (test server has no dirs)" - exit 1 -fi +check forbidden echo echo "---- directory attack 7 (%2f%2e%2e%2f%2e./.%2e/.%2e%2fetc/passwd should be /etc/passswd)" 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 -if [ -z "`grep '

403 Forbidden

' /tmp/lwscap`" ] ; then - echo "FAIL: should have told forbidden (test server has no dirs)" - exit 1 -fi +check forbidden + echo -echo "--- survived" +echo "--- survived OK ---" kill -2 $CPID diff --git a/test-server/test-server.c b/test-server/test-server.c index 899314c8..d9382cc1 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -103,6 +103,69 @@ struct per_session_data__http { int fd; }; +/* + * this is just an example of parsing handshake headers, you don't need this + * in your code unless you will filter allowing connections by the header + * content + */ + +static void +dump_handshake_info(struct libwebsocket *wsi) +{ + int n; + static const char *token_names[] = { + /*[WSI_TOKEN_GET_URI] =*/ "GET URI", + /*[WSI_TOKEN_HOST] =*/ "Host", + /*[WSI_TOKEN_CONNECTION] =*/ "Connection", + /*[WSI_TOKEN_KEY1] =*/ "key 1", + /*[WSI_TOKEN_KEY2] =*/ "key 2", + /*[WSI_TOKEN_PROTOCOL] =*/ "Protocol", + /*[WSI_TOKEN_UPGRADE] =*/ "Upgrade", + /*[WSI_TOKEN_ORIGIN] =*/ "Origin", + /*[WSI_TOKEN_DRAFT] =*/ "Draft", + /*[WSI_TOKEN_CHALLENGE] =*/ "Challenge", + + /* new for 04 */ + /*[WSI_TOKEN_KEY] =*/ "Key", + /*[WSI_TOKEN_VERSION] =*/ "Version", + /*[WSI_TOKEN_SWORIGIN] =*/ "Sworigin", + + /* new for 05 */ + /*[WSI_TOKEN_EXTENSIONS] =*/ "Extensions", + + /* client receives these */ + /*[WSI_TOKEN_ACCEPT] =*/ "Accept", + /*[WSI_TOKEN_NONCE] =*/ "Nonce", + /*[WSI_TOKEN_HTTP] =*/ "Http", + + "Accept:", + "If-Modified-Since:", + "Accept-Encoding:", + "Accept-Language:", + "Pragma:", + "Cache-Control:", + "Authorization:", + "Cookie:", + "Content-Type:", + "Date:", + "Range:", + "Referer:", + "Uri-Args:", + + /*[WSI_TOKEN_MUXURL] =*/ "MuxURL", + }; + char buf[256]; + + for (n = 0; n < sizeof(token_names) / sizeof(token_names[0]); n++) { + if (!lws_hdr_total_length(wsi, n)) + continue; + + lws_hdr_copy(wsi, buf, sizeof buf, n); + + fprintf(stderr, " %s = %s\n", token_names[n], buf); + } +} + const char * get_mimetype(const char *file) { int n = strlen(file); @@ -152,6 +215,8 @@ static int callback_http(struct libwebsocket_context *context, switch (reason) { case LWS_CALLBACK_HTTP: + dump_handshake_info(wsi); + if (len < 1) { libwebsockets_return_http_status(context, wsi, HTTP_STATUS_BAD_REQUEST, NULL); @@ -371,67 +436,6 @@ bail: return 0; } -/* - * this is just an example of parsing handshake headers, you don't need this - * in your code unless you will filter allowing connections by the header - * content - */ - -static void -dump_handshake_info(struct libwebsocket *wsi) -{ - int n; - static const char *token_names[] = { - /*[WSI_TOKEN_GET_URI] =*/ "GET URI", - /*[WSI_TOKEN_HOST] =*/ "Host", - /*[WSI_TOKEN_CONNECTION] =*/ "Connection", - /*[WSI_TOKEN_KEY1] =*/ "key 1", - /*[WSI_TOKEN_KEY2] =*/ "key 2", - /*[WSI_TOKEN_PROTOCOL] =*/ "Protocol", - /*[WSI_TOKEN_UPGRADE] =*/ "Upgrade", - /*[WSI_TOKEN_ORIGIN] =*/ "Origin", - /*[WSI_TOKEN_DRAFT] =*/ "Draft", - /*[WSI_TOKEN_CHALLENGE] =*/ "Challenge", - - /* new for 04 */ - /*[WSI_TOKEN_KEY] =*/ "Key", - /*[WSI_TOKEN_VERSION] =*/ "Version", - /*[WSI_TOKEN_SWORIGIN] =*/ "Sworigin", - - /* new for 05 */ - /*[WSI_TOKEN_EXTENSIONS] =*/ "Extensions", - - /* client receives these */ - /*[WSI_TOKEN_ACCEPT] =*/ "Accept", - /*[WSI_TOKEN_NONCE] =*/ "Nonce", - /*[WSI_TOKEN_HTTP] =*/ "Http", - - "Accept:", - "If-Modified-Since:", - "Accept-Encoding:", - "Accept-Language:", - "Pragma:", - "Cache-Control:", - "Authorization:", - "Cookie:", - "Content-Type:", - "Date:", - "Range:", - "Referer:" - - /*[WSI_TOKEN_MUXURL] =*/ "MuxURL", - }; - char buf[256]; - - for (n = 0; n < sizeof(token_names) / sizeof(token_names[0]); n++) { - if (!lws_hdr_total_length(wsi, n)) - continue; - - lws_hdr_copy(wsi, buf, sizeof buf, n); - - fprintf(stderr, " %s = %s\n", token_names[n], buf); - } -} /* dumb_increment protocol */