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 <andy.green@linaro.org>
This commit is contained in:
Andy Green 2013-11-13 07:45:17 +08:00
parent d3f6873942
commit 1e3f7b8de9
5 changed files with 139 additions and 113 deletions

View file

@ -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,

View file

@ -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;

View file

@ -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 {

View file

@ -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 '<h1>403 Forbidden</h1>' /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 '<h1>403 Forbidden</h1>' /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 '<h1>403 Forbidden</h1>' /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 '<h1>403 Forbidden</h1>' /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

View file

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