Allow lcal http servers to perform cross site scripting of restful APIs

This commit is contained in:
Jeremy Lakeman 2014-07-28 12:54:57 +09:30
parent d436705e64
commit 31cf3a67b5
6 changed files with 139 additions and 20 deletions

View File

@ -997,6 +997,27 @@ static int http_request_parse_header(struct http_request *r)
goto malformed;
}
_rewind(r);
if (_skip_literal_nocase(r, "Origin:")) {
if (r->request_header.origin) {
if (r->debug_flag && *r->debug_flag)
DEBUGF("Skipping duplicate HTTP header Origin: %s", alloca_toprint(50, sol, r->end - sol));
r->cursor = nextline;
_commit(r);
return 0;
}
_skip_optional_space(r);
struct substring origin;
if (_skip_word_printable(r, &origin)
&& _skip_optional_space(r)
&& r->cursor == eol) {
r->cursor = nextline;
_commit(r);
_reserve(r, &r->request_header.origin, origin);
return 0;
}
goto malformed;
}
_rewind(r);
if (r->debug_flag && *r->debug_flag)
DEBUGF("Skipped HTTP request header: %s", alloca_toprint(-1, sol, eol - sol));
r->cursor = nextline;
@ -1979,6 +2000,14 @@ static int _render_response(struct http_request *r)
}
if (hr.header.content_length != CONTENT_LENGTH_UNKNOWN)
strbuf_sprintf(sb, "Content-Length: %"PRIhttp_size_t"\r\n", hr.header.content_length);
if (hr.header.allow_origin)
strbuf_sprintf(sb, "Access-Control-Allow-Origin: %s\r\n", hr.header.allow_origin);
if (hr.header.allow_methods)
strbuf_sprintf(sb, "Access-Control-Allow-Methods: %s\r\n", hr.header.allow_methods);
if (hr.header.allow_headers)
strbuf_sprintf(sb, "Access-Control-Allow-Headers: %s\r\n", hr.header.allow_headers);
const char *scheme = NULL;
switch (hr.header.www_authenticate.scheme) {
case NOAUTH: break;

View File

@ -90,6 +90,7 @@ struct http_request_headers {
http_size_t content_length;
struct mime_content_type content_type;
unsigned short content_range_count;
const char *origin; // points into buffer; nul terminated
struct http_range content_ranges[5];
struct http_client_authorization authorization;
};
@ -100,6 +101,9 @@ struct http_response_headers {
http_size_t resource_length; // size of entire resource
const char *content_type; // "type/subtype"
const char *boundary;
char allow_origin[23]; // max supported str (for now) "http://localhost:65537"
const char *allow_methods;
const char *allow_headers;
struct http_www_authenticate www_authenticate;
};

18
httpd.c
View File

@ -348,6 +348,24 @@ int authorize_restful(struct http_request *r)
{
if (!is_from_loopback(r))
return 403;
if (r->request_header.origin){
const char *remainder;
if (strcasecmp(r->request_header.origin, "null")==0
|| (strcase_startswith(r->request_header.origin, "http://localhost", &remainder)
&& (*remainder==':' || *remainder=='\0'))
|| (strcase_startswith(r->request_header.origin, "http://127.0.0.1", &remainder)
&& (*remainder==':' || *remainder=='\0'))
){
strncpy(r->response.header.allow_origin,r->request_header.origin, sizeof r->response.header.allow_origin);
r->response.header.allow_methods="GET, POST, OPTIONS";
r->response.header.allow_headers="Authorization";
}else
return 403;
}
if (r->verb == HTTP_VERB_OPTIONS){
http_request_simple_response(r, 200, NULL);
return 200;
}
if (!is_authorized_restful(&r->request_header.authorization)) {
r->response.header.www_authenticate.scheme = BASIC;
r->response.header.www_authenticate.realm = "Serval RESTful API";

View File

@ -117,6 +117,9 @@ int restful_meshms_(httpd_request *r, const char *remainder)
r->http.response.header.content_type = CONTENT_TYPE_JSON;
if (!is_rhizome_http_enabled())
return 403;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
const char *verb = HTTP_VERB_GET;
http_size_t content_length = CONTENT_LENGTH_UNKNOWN;
HTTP_HANDLER *handler = NULL;
@ -173,19 +176,15 @@ int restful_meshms_(httpd_request *r, const char *remainder)
}
if (handler == NULL)
return 404;
if (r->http.verb != verb)
return 405;
if ( content_length != CONTENT_LENGTH_UNKNOWN
&& r->http.request_header.content_length != CONTENT_LENGTH_UNKNOWN
&& r->http.request_header.content_length != content_length) {
http_request_simple_response(&r->http, 400, "Bad content length");
return 400;
}
int ret = authorize_restful(&r->http);
if (ret)
return ret;
ret = handler(r, remainder);
return ret;
if (r->http.verb != verb)
return 405;
return handler(r, remainder);
}
static HTTP_CONTENT_GENERATOR restful_meshms_conversationlist_json_content;

View File

@ -152,13 +152,13 @@ int restful_rhizome_bundlelist_json(httpd_request *r, const char *remainder)
r->http.render_extra_headers = render_manifest_headers;
if (!is_rhizome_http_enabled())
return 403;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
if (*remainder)
return 404;
if (r->http.verb != HTTP_VERB_GET)
return 405;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
r->u.rhlist.phase = LIST_HEADER;
r->u.rhlist.rowcount = 0;
bzero(&r->u.rhlist.cursor, sizeof r->u.rhlist.cursor);
@ -184,15 +184,15 @@ int restful_rhizome_newsince(httpd_request *r, const char *remainder)
r->http.response.header.content_type = CONTENT_TYPE_JSON;
if (!is_rhizome_http_enabled())
return 403;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
uint64_t rowid;
const char *end = NULL;
if (!strn_to_list_token(remainder, &rowid, &end) || strcmp(end, "/bundlelist.json") != 0)
return 404;
if (r->http.verb != HTTP_VERB_GET)
return 405;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
r->u.rhlist.phase = LIST_HEADER;
r->u.rhlist.rowcount = 0;
bzero(&r->u.rhlist.cursor, sizeof r->u.rhlist.cursor);
@ -325,15 +325,15 @@ int restful_rhizome_insert(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = CONTENT_TYPE_JSON;
r->http.render_extra_headers = render_manifest_headers;
if (*remainder)
return 404;
if (!is_rhizome_http_enabled())
return 403;
if (r->http.verb != HTTP_VERB_POST)
return 405;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
if (*remainder)
return 404;
if (r->http.verb != HTTP_VERB_POST)
return 405;
// Parse the request body as multipart/form-data.
assert(r->u.insert.current_part == NULL);
assert(!r->u.insert.received_author);
@ -686,6 +686,9 @@ int restful_rhizome_(httpd_request *r, const char *remainder)
r->http.render_extra_headers = render_manifest_headers;
if (!is_rhizome_http_enabled())
return 403;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
HTTP_HANDLER *handler = NULL;
rhizome_bid_t bid;
const char *end;
@ -705,9 +708,6 @@ int restful_rhizome_(httpd_request *r, const char *remainder)
return 404;
if (r->http.verb != HTTP_VERB_GET)
return 405;
int ret = authorize_restful(&r->http);
if (ret)
return ret;
if ((r->manifest = rhizome_new_manifest()) == NULL)
return 500;
ret = rhizome_retrieve_manifest(&bid, r->manifest);

View File

@ -115,6 +115,75 @@ teardown_AuthBasicWrong() {
teardown
}
doc_CORS_Request="Allow local cross site requests, and deny remote ones"
test_CORS_Request(){
executeOk curl \
--silent --fail --show-error --write-out '%{http_code}' \
--output http.output \
--dump-header http.headers \
--header "Origin: http://localhost" \
--request "OPTIONS" \
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '200'
assertGrep http.headers "^Access-Control-Allow-Origin: http://localhost$CR\$"
assertGrep http.headers "^Access-Control-Allow-Methods: GET, POST, OPTIONS$CR\$"
assertGrep http.headers "^Access-Control-Allow-Headers: Authorization$CR\$"
executeOk curl \
--silent --fail --show-error --write-out '%{http_code}' \
--output http.output \
--dump-header http.headers \
--header "Origin: http://localhost:1234" \
--request "OPTIONS" \
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '200'
assertGrep http.headers "^Access-Control-Allow-Origin: http://localhost:1234$CR\$"
executeOk curl \
--silent --fail --show-error --write-out '%{http_code}' \
--output http.output \
--dump-header http.headers \
--header "Origin: null" \
--request "OPTIONS" \
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '200'
assertGrep http.headers "^Access-Control-Allow-Origin: null$CR\$"
executeOk curl \
--silent --show-error --write-out '%{http_code}' \
--output http.output \
--dump-header http.headers \
--header "Origin: http://malevolent.site.com" \
--request "OPTIONS" \
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '403'
executeOk curl \
--silent --show-error --write-out '%{http_code}' \
--output http.output \
--dump-header http.headers \
--header "Origin: http://localhost.malevolent.site.com" \
--request "OPTIONS" \
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '403'
executeOk curl \
--silent --fail --show-error --write-out '%{http_code}' \
--output http.output \
--dump-header http.headers \
--header "Origin: http://localhost" \
--basic --user ron:weasley \
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '200'
executeOk curl \
--silent --show-error --write-out '%{http_code}' \
--output http.output \
--dump-header http.headers \
--header "Origin: http://malevolent.site.com" \
--basic --user ron:weasley \
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '403'
}
teardown_CORS_Request() {
tfw_cat http.headers http.output
teardown
}
doc_RhizomeList="HTTP RESTful list 100 Rhizome bundles as JSON"
setup_RhizomeList() {
setup