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 */