From 4eb36373d70b3fda099d1896733c6ddd3046bdf1 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Tue, 15 Dec 2015 22:57:19 +0800 Subject: [PATCH] http uri arguments process in fragments This makes the URI argument processing split each parameter into a "fragment". Processing header content as fragments already exists in lws, because it's legal to deliver header content by repeating the header. Now there's an api to access individual fragments, also add the code to the test server to print each URI argument separately. Adapt attack.sh to parse the fragments. Signed-off-by: Andy Green --- lib/header.c | 3 -- lib/parsers.c | 29 ++++++++++++++++---- test-server/attack.sh | 50 ++++++++++++++++++++++++++-------- test-server/test-server-http.c | 12 ++++++++ 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/lib/header.c b/lib/header.c index 5daf0d84b..b2d782889 100644 --- a/lib/header.c +++ b/lib/header.c @@ -24,9 +24,6 @@ const unsigned char *lws_token_to_string(enum lws_token_indexes token) { - if (token == WSI_TOKEN_HTTP_URI_ARGS) - return (unsigned char *)"Uri-Args:"; - if ((unsigned int)token >= ARRAY_SIZE(set)) return NULL; diff --git a/lib/parsers.c b/lib/parsers.c index d40eeb68f..736aeed92 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -323,8 +323,24 @@ int lws_parse(struct lws *wsi, unsigned char c) switch (wsi->u.hdr.ups) { case URIPS_IDLE: + /* genuine delimiter */ + if (c == '&' && !enc) { + issue_char(wsi, c); + /* swallow the terminator */ + ah->frags[ah->nfrag].len--; + /* link to next fragment */ + ah->frags[ah->nfrag].nfrag = ah->nfrag + 1; + ah->nfrag++; + if (ah->nfrag >= ARRAY_SIZE(ah->frags)) + goto excessive; + /* start next fragment after the & */ + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + goto swallow; + } /* issue the first / always */ - if (c == '/') + if (c == '/' && !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) wsi->u.hdr.ups = URIPS_SEEN_SLASH; break; case URIPS_SEEN_SLASH: @@ -365,7 +381,8 @@ int lws_parse(struct lws *wsi, unsigned char c) } /* it was like /.dir ... regurgitate the . */ wsi->u.hdr.ups = URIPS_IDLE; - issue_char(wsi, '.'); + if (issue_char(wsi, '.') < 0) + return -1; break; case URIPS_SEEN_SLASH_DOT_DOT: @@ -380,19 +397,20 @@ int lws_parse(struct lws *wsi, unsigned char c) break; } - if (c == '?' && !enc) { /* start of URI arguments */ + if (c == '?' && !enc && + !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI arguments */ /* seal off uri header */ ah->data[ah->pos++] = '\0'; /* move to using WSI_TOKEN_HTTP_URI_ARGS */ ah->nfrag++; + if (ah->nfrag >= ARRAY_SIZE(ah->frags)) + goto excessive; ah->frags[ah->nfrag].offset = ah->pos; ah->frags[ah->nfrag].len = 0; ah->frags[ah->nfrag].nfrag = 0; ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = ah->nfrag; - - /* defeat normal uri path processing */ wsi->u.hdr.ups = URIPS_IDLE; goto swallow; } @@ -501,6 +519,7 @@ swallow: start_fragment: ah->nfrag++; +excessive: if (ah->nfrag == ARRAY_SIZE(ah->frags)) { lwsl_warn("More hdr frags than we can deal with\n"); return -1; diff --git a/test-server/attack.sh b/test-server/attack.sh index d9defec26..dab8be77d 100755 --- a/test-server/attack.sh +++ b/test-server/attack.sh @@ -35,14 +35,32 @@ function check { 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 [ "$1" == "1" ] ; then + a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep URI\ Arg\ 1\: | tr -s ' ' | cut -d' ' -f5-`" if [ "$a" != "$2" ] ; then - echo "Args '$a' not $2" + echo "Arg 1 '$a' not $2" exit 1 fi fi - LEN=`stat $LOG -c %s` + + if [ "$1" == "2" ] ; then + a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep URI\ Arg\ 2\: | tr -s ' ' | cut -d' ' -f5-`" + if [ "$a" != "$2" ] ; then + echo "Arg 2 '$a' not $2" + exit 1 + fi + fi + if [ "$1" == "3" ] ; then + a="`dd if=$LOG bs=1 skip=$LEN 2>/dev/null |grep URI\ Arg\ 3\: | tr -s ' ' | cut -d' ' -f5-`" + if [ "$a" != "$2" ] ; then + echo "Arg 3 '$a' not $2" + exit 1 + fi + fi + + if [ -z "$1" ] ; then + LEN=`stat $LOG -c %s` + fi } @@ -60,22 +78,24 @@ echo echo "---- /cgi-bin/settingsjs?UPDATE_SETTINGS=1&Root_Channels_1_Channel_name_http_post=%3F&Root_Channels_1_Channel_location_http_post=%3F" rm -f /tmp/lwscap echo -e "GET /cgi-bin/settingsjs?UPDATE_SETTINGS=1&Root_Channels_1_Channel_name_http_post=%3F&Root_Channels_1_Channel_location_http_post=%3F HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap -check args "UPDATE_SETTINGS=1&Root_Channels_1_Channel_name_http_post=?&Root_Channels_1_Channel_location_http_post=?" - - +check 1 "UPDATE_SETTINGS=1" +check 2 "Root_Channels_1_Channel_name_http_post=?" +check 3 "Root_Channels_1_Channel_location_http_post=?" +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" +check 1 "arg=1" +check 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=/../." - +check 1 "arg=/../." +check echo echo "---- spam enough crap to not be GET" @@ -172,55 +192,63 @@ echo -e "GET /test.html HTTP/1.1\x0d\x0a\x0d\x0aILLEGAL-PAYLOAD................. "......................................................................................................................." \ | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap check default +check 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 forbidden +check 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 default +check 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 default +check 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 default +check 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 default +check 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 default +check 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 forbidden +check 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 forbidden - +check echo echo "--- survived OK ---" diff --git a/test-server/test-server-http.c b/test-server/test-server-http.c index 1100d0718..52b0b0f2d 100644 --- a/test-server/test-server-http.c +++ b/test-server/test-server-http.c @@ -137,6 +137,18 @@ int callback_http(struct lws_context *context, struct lws *wsi, dump_handshake_info(wsi); + /* dump the individual URI Arg parameters */ + m = 1; + n = 0; + while (m > 0) { + m = lws_hdr_copy_fragment(wsi, buf, sizeof(buf), + WSI_TOKEN_HTTP_URI_ARGS, n); + if (m < 0) + continue; + n++; + lwsl_info("URI Arg %d: %s\n", n, buf); + } + if (len < 1) { lws_return_http_status(context, wsi, HTTP_STATUS_BAD_REQUEST, NULL);