diff --git a/lib/parsers.c b/lib/parsers.c
index 963804ed..0721a4b5 100644
--- a/lib/parsers.c
+++ b/lib/parsers.c
@@ -546,6 +546,23 @@ lws_parse(struct lws *wsi, unsigned char c)
if (issue_char(wsi, '/') < 0)
return -1;
+ if (wsi->u.hdr.ups == URIPS_SEEN_SLASH_DOT_DOT) {
+ /*
+ * back up one dir level if possible
+ * safe against header fragmentation because
+ * the method URI can only be in 1 fragment
+ */
+ if (ah->frags[ah->nfrag].len > 2) {
+ ah->pos--;
+ ah->frags[ah->nfrag].len--;
+ do {
+ ah->pos--;
+ ah->frags[ah->nfrag].len--;
+ } while (ah->frags[ah->nfrag].len > 1 &&
+ ah->data[ah->pos] != '/');
+ }
+ }
+
/* begin parsing HTTP version: */
if (issue_char(wsi, '\0') < 0)
return -1;
@@ -553,7 +570,10 @@ lws_parse(struct lws *wsi, unsigned char c)
goto start_fragment;
}
- /* special URI processing... convert %xx */
+ /*
+ * PRIORITY 1
+ * special URI processing... convert %xx
+ */
switch (wsi->u.hdr.ues) {
case URIES_IDLE:
@@ -563,30 +583,19 @@ lws_parse(struct lws *wsi, unsigned char c)
}
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;
- }
+ if (char_to_hex(c) < 0)
+ /* illegal post-% char */
+ goto forbid;
+
wsi->u.hdr.esc_stash = c;
wsi->u.hdr.ues = URIES_SEEN_PERCENT_H1;
goto swallow;
case URIES_SEEN_PERCENT_H1:
- if (char_to_hex(c) < 0) {
- /* regurgitate */
- if (issue_char(wsi, '%') < 0)
- return -1;
- wsi->u.hdr.ues = URIES_IDLE;
- /* regurgitate + assess */
- if (lws_parse(wsi, wsi->u.hdr.esc_stash) < 0)
- return -1;
- /* continue on to assess c */
- break;
- }
+ if (char_to_hex(c) < 0)
+ /* illegal post-% char */
+ goto forbid;
+
c = (char_to_hex(wsi->u.hdr.esc_stash) << 4) |
char_to_hex(c);
enc = 1;
@@ -595,6 +604,7 @@ lws_parse(struct lws *wsi, unsigned char c)
}
/*
+ * PRIORITY 2
* special URI processing...
* convert /.. or /... or /../ etc to /
* convert /./ to /
@@ -653,20 +663,6 @@ lws_parse(struct lws *wsi, unsigned char c)
case URIPS_SEEN_SLASH_DOT:
/* swallow second . */
if (c == '.') {
- /*
- * back up one dir level if possible
- * safe against header fragmentation because
- * the method URI can only be in 1 fragment
- */
- if (ah->frags[ah->nfrag].len > 2) {
- ah->pos--;
- ah->frags[ah->nfrag].len--;
- do {
- ah->pos--;
- ah->frags[ah->nfrag].len--;
- } while (ah->frags[ah->nfrag].len > 1 &&
- ah->data[ah->pos] != '/');
- }
wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT_DOT;
goto swallow;
}
@@ -682,19 +678,44 @@ lws_parse(struct lws *wsi, unsigned char c)
break;
case URIPS_SEEN_SLASH_DOT_DOT:
- /* swallow prior .. chars and any subsequent . */
- if (c == '.')
+
+ /* /../ or /..[End of URI] --> backup to last / */
+ if (c == '/' || c == '?') {
+ /*
+ * back up one dir level if possible
+ * safe against header fragmentation because
+ * the method URI can only be in 1 fragment
+ */
+ if (ah->frags[ah->nfrag].len > 2) {
+ ah->pos--;
+ ah->frags[ah->nfrag].len--;
+ do {
+ ah->pos--;
+ ah->frags[ah->nfrag].len--;
+ } while (ah->frags[ah->nfrag].len > 1 &&
+ ah->data[ah->pos] != '/');
+ }
+ wsi->u.hdr.ups = URIPS_SEEN_SLASH;
+ if (ah->frags[ah->nfrag].len > 1)
+ break;
goto swallow;
- /* last issued was /, so another / == // */
- if (c == '/')
- goto swallow;
- /* last we issued was / so SEEN_SLASH */
- wsi->u.hdr.ups = URIPS_SEEN_SLASH;
+ }
+
+ /* /..[^/] ... regurgitate and allow */
+
+ if (issue_char(wsi, '.') < 0)
+ return -1;
+ if (issue_char(wsi, '.') < 0)
+ return -1;
+ wsi->u.hdr.ups = URIPS_IDLE;
break;
}
if (c == '?' && !enc &&
!ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI arguments */
+ if (wsi->u.hdr.ues != URIES_IDLE)
+ goto forbid;
+
/* seal off uri header */
if (issue_char(wsi, '\0') < 0)
return -1;
@@ -714,10 +735,12 @@ lws_parse(struct lws *wsi, unsigned char c)
}
check_eol:
-
/* bail at EOL */
if (wsi->u.hdr.parser_state != WSI_TOKEN_CHALLENGE &&
c == '\x0d') {
+ if (wsi->u.hdr.ues != URIES_IDLE)
+ goto forbid;
+
c = '\0';
wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING_SAW_CR;
lwsl_parser("*\n");
@@ -763,7 +786,7 @@ swallow:
*/
if (m == ARRAY_SIZE(methods)) {
lwsl_info("Unknown method - dropping\n");
- return -1;
+ goto forbid;
}
break;
}
@@ -851,6 +874,8 @@ excessive:
case WSI_TOKEN_SKIPPING_SAW_CR:
lwsl_parser("WSI_TOKEN_SKIPPING_SAW_CR '%c'\n", c);
+ if (wsi->u.hdr.ues != URIES_IDLE)
+ goto forbid;
if (c == '\x0a') {
wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
wsi->u.hdr.lextable_pos = 0;
@@ -867,7 +892,8 @@ excessive:
return 0;
set_parsing_complete:
-
+ if (wsi->u.hdr.ues != URIES_IDLE)
+ goto forbid;
if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) {
if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION))
wsi->ietf_spec_revision =
@@ -879,6 +905,11 @@ set_parsing_complete:
wsi->hdr_parsing_completed = 1;
return 0;
+
+forbid:
+ lwsl_notice(" forbidding on uri sanitation\n");
+ lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
+ return -1;
}
diff --git a/test-server/attack.sh b/test-server/attack.sh
index bd160006..3bf4d675 100755
--- a/test-server/attack.sh
+++ b/test-server/attack.sh
@@ -35,6 +35,14 @@ function check {
fi
fi
+ if [ "$1" = "rejected" ] ; then
+ if [ -z "`grep '
406
' /tmp/lwscap`" ] ; then
+ echo "FAIL: should have told forbidden (test server has no dirs)"
+ exit 1
+ fi
+ fi
+
+
if [ "$1" = "media" ] ; then
if [ -z "`grep '415
' /tmp/lwscap`" ] ; then
echo "FAIL: should have told unknown media type"
@@ -73,7 +81,7 @@ function check {
rm -rf $LOG
killall libwebsockets-test-server 2>/dev/null
-libwebsockets-test-server -d31 2>> $LOG &
+libwebsockets-test-server -d15 2>> $LOG &
CPID=$!
while [ -z "`grep Listening $LOG`" ] ; do
@@ -218,7 +226,7 @@ check
echo
echo "---- nonexistant file"
rm -f /tmp/lwscap
-echo -e "GET nope HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
+echo -e "GET /nope HTTP/1.1\x0d\x0a\x0d\x0a" | nc $SERVER $PORT | sed '1,/^\r$/d'> /tmp/lwscap
check media
check
@@ -226,7 +234,7 @@ 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 rejected
check
echo
@@ -268,14 +276,14 @@ 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 rejected
check
echo
echo "---- directory attack 8 (%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 rejected
check
echo
@@ -288,6 +296,449 @@ if [ "$good" != "`md5sum /tmp/lwsdump | cut -d' ' -f 1`" ] ; then
exit 1
fi
+echo
+echo "---- mass testing uri variations"
+
+rm -f /tmp/results
+
+for i in \
+/..../ \
+/.../. \
+/...// \
+/.../a \
+/.../w \
+/.../? \
+/.../% \
+/../.. \
+/.././ \
+/../.a \
+/../.w \
+/../.. \
+/../.% \
+/..//. \
+/../// \
+/..//a \
+/..//w \
+/..//? \
+/..//% \
+/../a. \
+/../a/ \
+/../aa \
+/../aw \
+/../a? \
+/../a% \
+/../w. \
+/../w/ \
+/../wa \
+/../ww \
+/../w? \
+/../w% \
+/../?. \
+/../?/ \
+/../?a \
+/../?w \
+/../?? \
+/../?% \
+/../%. \
+/../%/ \
+/../%a \
+/../%w \
+/../%? \
+/../%% \
+/./... \
+/./../ \
+/./..a \
+/./..w \
+/./..? \
+/./..% \
+/.//.. \
+/.a../ \
+/.a/.. \
+/.w../ \
+/.w/.. \
+/.?../ \
+/../.. \
+/.%../ \
+/.%/.. \
+//.... \
+//.../ \
+//...a \
+//...w \
+//...? \
+//...% \
+//../. \
+//..// \
+//../a \
+//../w \
+//../? \
+//../% \
+//..a. \
+//..a/ \
+//..aa \
+//..aw \
+//..a? \
+//..a% \
+//..w. \
+//..w/ \
+//..wa \
+//..ww \
+//..w? \
+//..w% \
+//..?. \
+//..?/ \
+//..?a \
+//..?w \
+//..?? \
+//..?% \
+//..%. \
+//..%/ \
+//..%a \
+//..%w \
+//..%? \
+//..%% \
+//./.. \
+///... \
+///../ \
+///..a \
+///..w \
+///..? \
+///..% \
+////.. \
+//a../ \
+//a/.. \
+//w../ \
+//w/.. \
+//?../ \
+//?/.. \
+//%../ \
+//%/.. \
+/a.../ \
+/a../. \
+/a..// \
+/a../a \
+/a../w \
+/a../? \
+/a../% \
+/a./.. \
+/a/... \
+/a/../ \
+/a/..a \
+/a/..w \
+/a/..? \
+/a/..% \
+/a//.. \
+/aa../ \
+/aa/.. \
+/aw../ \
+/aw/.. \
+/a?../ \
+/a?/.. \
+/a%../ \
+/a%/.. \
+/w.../ \
+/w../. \
+/w..// \
+/w../a \
+/w../w \
+/w../? \
+/w../% \
+/w./.. \
+/w/... \
+/w/../ \
+/w/..a \
+/w/..w \
+/w/..? \
+/w/..% \
+/w//.. \
+/wa../ \
+/wa/.. \
+/ww../ \
+/ww/.. \
+/w?../ \
+/w?/.. \
+/w%../ \
+/w%/.. \
+/?.../ \
+/?../. \
+/?..// \
+/?../a \
+/?../w \
+/?../? \
+/?../% \
+/?./.. \
+/?/... \
+/?/../ \
+/?/..a \
+/?/..w \
+/?/..? \
+/?/..% \
+/?//.. \
+/?a../ \
+/?a/.. \
+/?w../ \
+/?w/.. \
+/??../ \
+/??/.. \
+/?%../ \
+/?%/.. \
+/%.../ \
+/%../. \
+/%..// \
+/%../a \
+/%../w \
+/%../? \
+/%../% \
+/%./.. \
+/%/... \
+/%/../ \
+/%/..a \
+/%/..w \
+/%/..? \
+/%/..% \
+/%//.. \
+/%a../ \
+/%a/.. \
+/%w../ \
+/%w/.. \
+/%?../ \
+/%?/.. \
+/%%../ \
+/%%/.. \
+/a/w/../a \
+/path/to/dir/../other/dir \
+; do
+
+R=`rm -f /tmp/lwscap ; echo -n -e "GET $i HTTP/1.0\r\n\r\n" | nc localhost 7681 2>/dev/null >/tmp/lwscap; head -n1 /tmp/lwscap| cut -d' ' -f2`
+
+cat /tmp/lwscap | head -n1
+echo ==== $R
+
+
+if [ "$R" != "403" ]; then
+ U=`cat $LOG | grep lws_http_serve | tail -n 1 | cut -d':' -f3 | cut -d' ' -f2`
+ echo $U
+ echo "- \"$i\" -> $R \"$U\"" >>/tmp/results
+else
+ echo "- \"$i\" -> $R" >>/tmp/results
+fi
+done
+
+cat </tmp/lwsresult1
+- "/..../" -> 406 "/..../"
+- "/.../." -> 406 "/.../"
+- "/...//" -> 406 "/.../"
+- "/.../a" -> 406 "/.../a"
+- "/.../w" -> 406 "/.../w"
+- "/.../?" -> 406 "/.../"
+- "/.../%" -> 403
+- "/../.." -> 200 "/"
+- "/.././" -> 200 "/"
+- "/../.a" -> 415 "/.a"
+- "/../.w" -> 415 "/.w"
+- "/../.." -> 200 "/"
+- "/../.%" -> 403
+- "/..//." -> 200 "/"
+- "/..///" -> 200 "/"
+- "/..//a" -> 415 "/a"
+- "/..//w" -> 415 "/w"
+- "/..//?" -> 200 "/"
+- "/..//%" -> 403
+- "/../a." -> 415 "/a."
+- "/../a/" -> 406 "/a/"
+- "/../aa" -> 415 "/aa"
+- "/../aw" -> 415 "/aw"
+- "/../a?" -> 415 "/a"
+- "/../a%" -> 403
+- "/../w." -> 415 "/w."
+- "/../w/" -> 406 "/w/"
+- "/../wa" -> 415 "/wa"
+- "/../ww" -> 415 "/ww"
+- "/../w?" -> 415 "/w"
+- "/../w%" -> 403
+- "/../?." -> 200 "/"
+- "/../?/" -> 200 "/"
+- "/../?a" -> 200 "/"
+- "/../?w" -> 200 "/"
+- "/../??" -> 200 "/"
+- "/../?%" -> 403
+- "/../%." -> 403
+- "/../%/" -> 403
+- "/../%a" -> 403
+- "/../%w" -> 403
+- "/../%?" -> 403
+- "/../%%" -> 403
+- "/./..." -> 415 "/..."
+- "/./../" -> 200 "/"
+- "/./..a" -> 415 "/..a"
+- "/./..w" -> 415 "/..w"
+- "/./..?" -> 200 "/"
+- "/./..%" -> 403
+- "/.//.." -> 200 "/"
+- "/.a../" -> 406 "/.a../"
+- "/.a/.." -> 200 "/"
+- "/.w../" -> 406 "/.w../"
+- "/.w/.." -> 200 "/"
+- "/.?../" -> 415 "/."
+- "/../.." -> 200 "/"
+- "/.%../" -> 403
+- "/.%/.." -> 403
+- "//...." -> 415 "/...."
+- "//.../" -> 406 "/.../"
+- "//...a" -> 415 "/...a"
+- "//...w" -> 415 "/...w"
+- "//...?" -> 415 "/..."
+- "//...%" -> 403
+- "//../." -> 200 "/"
+- "//..//" -> 200 "/"
+- "//../a" -> 415 "/a"
+- "//../w" -> 415 "/w"
+- "//../?" -> 200 "/"
+- "//../%" -> 403
+- "//..a." -> 415 "/..a."
+- "//..a/" -> 406 "/..a/"
+- "//..aa" -> 415 "/..aa"
+- "//..aw" -> 415 "/..aw"
+- "//..a?" -> 415 "/..a"
+- "//..a%" -> 403
+- "//..w." -> 415 "/..w."
+- "//..w/" -> 406 "/..w/"
+- "//..wa" -> 415 "/..wa"
+- "//..ww" -> 415 "/..ww"
+- "//..w?" -> 415 "/..w"
+- "//..w%" -> 403
+- "//..?." -> 200 "/"
+- "//..?/" -> 200 "/"
+- "//..?a" -> 415 "/a"
+- "//..?w" -> 415 "/w"
+- "//..??" -> 200 "/"
+- "//..?%" -> 403
+- "//..%." -> 403
+- "//..%/" -> 403
+- "//..%a" -> 403
+- "//..%w" -> 403
+- "//..%?" -> 403
+- "//..%%" -> 403
+- "//./.." -> 200 "/"
+- "///..." -> 415 "/..."
+- "///../" -> 200 "/"
+- "///..a" -> 415 "/..a"
+- "///..w" -> 415 "/..w"
+- "///..?" -> 200 "/"
+- "///..%" -> 403
+- "////.." -> 200 "/"
+- "//a../" -> 406 "/a../"
+- "//a/.." -> 200 "/"
+- "//w../" -> 406 "/w../"
+- "//w/.." -> 200 "/"
+- "//?../" -> 200 "/"
+- "//?/.." -> 200 "/"
+- "//%../" -> 403
+- "//%/.." -> 403
+- "/a.../" -> 406 "/a.../"
+- "/a../." -> 406 "/a../"
+- "/a..//" -> 406 "/a../"
+- "/a../a" -> 406 "/a../a"
+- "/a../w" -> 406 "/a../w"
+- "/a../?" -> 406 "/a../"
+- "/a../%" -> 403
+- "/a./.." -> 200 "/"
+- "/a/..." -> 406 "/a/..."
+- "/a/../" -> 200 "/"
+- "/a/..a" -> 406 "/a/..a"
+- "/a/..w" -> 406 "/a/..w"
+- "/a/..?" -> 200 "/"
+- "/a/..%" -> 403
+- "/a//.." -> 200 "/"
+- "/aa../" -> 406 "/aa../"
+- "/aa/.." -> 200 "/"
+- "/aw../" -> 406 "/aw../"
+- "/aw/.." -> 200 "/"
+- "/a?../" -> 415 "/a"
+- "/a?/.." -> 415 "/a"
+- "/a%../" -> 403
+- "/a%/.." -> 403
+- "/w.../" -> 406 "/w.../"
+- "/w../." -> 406 "/w../"
+- "/w..//" -> 406 "/w../"
+- "/w../a" -> 406 "/w../a"
+- "/w../w" -> 406 "/w../w"
+- "/w../?" -> 406 "/w../"
+- "/w../%" -> 403
+- "/w./.." -> 200 "/"
+- "/w/..." -> 406 "/w/..."
+- "/w/../" -> 200 "/"
+- "/w/..a" -> 406 "/w/..a"
+- "/w/..w" -> 406 "/w/..w"
+- "/w/..?" -> 200 "/"
+- "/w/..%" -> 403
+- "/w//.." -> 200 "/"
+- "/wa../" -> 406 "/wa../"
+- "/wa/.." -> 200 "/"
+- "/ww../" -> 406 "/ww../"
+- "/ww/.." -> 200 "/"
+- "/w?../" -> 415 "/w"
+- "/w?/.." -> 415 "/w"
+- "/w%../" -> 403
+- "/w%/.." -> 403
+- "/?.../" -> 200 "/"
+- "/?../." -> 200 "/"
+- "/?..//" -> 200 "/"
+- "/?../a" -> 200 "/"
+- "/?../w" -> 200 "/"
+- "/?../?" -> 200 "/"
+- "/?../%" -> 403
+- "/?./.." -> 200 "/"
+- "/?/..." -> 200 "/"
+- "/?/../" -> 200 "/"
+- "/?/..a" -> 200 "/"
+- "/?/..w" -> 200 "/"
+- "/?/..?" -> 200 "/"
+- "/?/..%" -> 403
+- "/?//.." -> 200 "/"
+- "/?a../" -> 200 "/"
+- "/?a/.." -> 200 "/"
+- "/?w../" -> 200 "/"
+- "/?w/.." -> 200 "/"
+- "/??../" -> 200 "/"
+- "/??/.." -> 200 "/"
+- "/?%../" -> 403
+- "/?%/.." -> 403
+- "/%.../" -> 403
+- "/%../." -> 403
+- "/%..//" -> 403
+- "/%../a" -> 403
+- "/%../w" -> 403
+- "/%../?" -> 403
+- "/%../%" -> 403
+- "/%./.." -> 403
+- "/%/..." -> 403
+- "/%/../" -> 403
+- "/%/..a" -> 403
+- "/%/..w" -> 403
+- "/%/..?" -> 403
+- "/%/..%" -> 403
+- "/%//.." -> 403
+- "/%a../" -> 403
+- "/%a/.." -> 403
+- "/%w../" -> 403
+- "/%w/.." -> 403
+- "/%?../" -> 403
+- "/%?/.." -> 403
+- "/%%../" -> 403
+- "/%%/.." -> 403
+- "/a/w/../a" -> 406 "/a/a"
+- "/path/to/dir/../other/dir" -> 406 "/path/to/other/dir"
+EOF
+
+if [ "`md5sum /tmp/results | cut -d' ' -f 1`" != "`md5sum /tmp/lwsresult1 | cut -d' ' -f1`" ] ; then
+ echo "Differences..."
+ diff -urN /tmp/results /tmp/lwsresult1
+ exit 1
+else
+ echo "OK"
+fi
+
+
echo
echo "--- survived OK ---"
kill -2 $CPID
diff --git a/test-server/test-server-http.c b/test-server/test-server-http.c
index 046f390e..1c7b898e 100644
--- a/test-server/test-server-http.c
+++ b/test-server/test-server-http.c
@@ -134,9 +134,12 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
struct lws_pollargs *pa = (struct lws_pollargs *)in;
#endif
+
switch (reason) {
case LWS_CALLBACK_HTTP:
+ lwsl_notice("lws_http_serve: %s\n",in);
+
if (debug_level & LLL_INFO) {
dump_handshake_info(wsi);
@@ -164,7 +167,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
/* this example server has no concept of directories */
if (strchr((const char *)in + 1, '/')) {
- lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
+ lws_return_http_status(wsi, HTTP_STATUS_NOT_ACCEPTABLE, NULL);
goto try_to_reuse;
}