From 4827d5db5fb2ca10772f70fbb6ad7f7f99285d96 Mon Sep 17 00:00:00 2001 From: Michael Santos Date: Mon, 21 Feb 2011 13:53:56 -0500 Subject: inets: prevent XSS in error pages Prevent user controlled input from being interpreted as HTML in error pages by encoding the reserved HTML characters. The reserved character set should be safe for displaying data within the body of HTML pages as outlined here: http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet Previously, weird URLs were URI encoded in the error page. This worked quite well but the URL would be displayed in the HTML in percent encoded format. There was also a check for URIs that were already escaped (by the browser) that would fail if the browser sent an URI containing a "%", e.g.: w3m "http://localhost:8080/foo?%" Also encode the HTTP method and version, since it's possible they may be manipulated: FOO /index.html HTTP/1.0 GET /index.html foo/1.0 Encode the static messages to prevent characters from being interpreted as HTML such as "heavy load (>~w processes)". --- lib/inets/src/http_lib/http_util.erl | 18 ++++++++++++++- lib/inets/src/http_server/httpd_util.erl | 38 ++++++++++++++++---------------- lib/inets/test/httpd_basic_SUITE.erl | 11 ++++----- 3 files changed, 42 insertions(+), 25 deletions(-) (limited to 'lib/inets') diff --git a/lib/inets/src/http_lib/http_util.erl b/lib/inets/src/http_lib/http_util.erl index 4f1147176c..5e6b69ac5e 100644 --- a/lib/inets/src/http_lib/http_util.erl +++ b/lib/inets/src/http_lib/http_util.erl @@ -25,7 +25,8 @@ hexlist_to_integer/1, integer_to_hexlist/1, convert_month/1, is_hostname/1, - timestamp/0, timeout/2 + timestamp/0, timeout/2, + html_encode/1 ]). @@ -187,6 +188,13 @@ timeout(Timeout, Started) -> end. +html_encode(Chars) -> + Reserved = sets:from_list([$&, $<, $>, $\", $', $/]), + lists:append(lists:map(fun(Char) -> + char_to_html_entity(Char, Reserved) + end, Chars)). + + %%%======================================================================== %%% Internal functions %%%======================================================================== @@ -235,3 +243,11 @@ convert_to_ascii([Num | Reversed], Number) convert_to_ascii([Num | Reversed], Number) when (Num > 9) andalso (Num < 16) -> convert_to_ascii(Reversed, [Num + 55 | Number]). + +char_to_html_entity(Char, Reserved) -> + case sets:is_element(Char, Reserved) of + true -> + "&#" ++ integer_to_list(Char) ++ ";"; + false -> + [Char] + end. diff --git a/lib/inets/src/http_server/httpd_util.erl b/lib/inets/src/http_server/httpd_util.erl index 789f12652b..c1aff65d5e 100644 --- a/lib/inets/src/http_server/httpd_util.erl +++ b/lib/inets/src/http_server/httpd_util.erl @@ -181,7 +181,7 @@ message(304, _URL,_) -> message(400,none,_) -> "Your browser sent a query that this server could not understand."; message(400,Msg,_) -> - "Your browser sent a query that this server could not understand. "++ maybe_encode(Msg); + "Your browser sent a query that this server could not understand. "++ http_util:html_encode(Msg); message(401,none,_) -> "This server could not verify that you are authorized to access the document you @@ -190,48 +190,48 @@ credentials (e.g., bad password), or your browser doesn't understand how to supply the credentials required."; message(403,RequestURI,_) -> - "You don't have permission to access "++ maybe_encode(RequestURI) ++" on this server."; + "You don't have permission to access "++ http_util:html_encode(RequestURI) ++" on this server."; message(404,RequestURI,_) -> - "The requested URL " ++ maybe_encode(RequestURI) ++ " was not found on this server."; + "The requested URL " ++ http_util:html_encode(RequestURI) ++ " was not found on this server."; message(408, Timeout, _) -> Timeout; message(412,none,_) -> - "The requested preconditions where false"; + "The requested preconditions were false"; message(413, Reason,_) -> - "Entity: " ++ Reason; + "Entity: " ++ http_util:html_encode(Reason); message(414,ReasonPhrase,_) -> - "Message "++ ReasonPhrase ++"."; + "Message "++ http_util:html_encode(ReasonPhrase) ++"."; message(416,ReasonPhrase,_) -> - ReasonPhrase; + http_util:html_encode(ReasonPhrase); message(500,_,ConfigDB) -> ServerAdmin=lookup(ConfigDB,server_admin,"unknown@unknown"), "The server encountered an internal error or " "misconfiguration and was unable to complete " "your request.

Please contact the server administrator " - ++ ServerAdmin ++ ", and inform them of the time the error occurred " + ++ http_util:html_encode(ServerAdmin) ++ ", and inform them of the time the error occurred " "and anything you might have done that may have caused the error."; message(501,{Method, RequestURI, HTTPVersion}, _ConfigDB) -> if is_atom(Method) -> - atom_to_list(Method)++ - " to "++ maybe_encode(RequestURI)++" ("++HTTPVersion++") not supported."; + http_util:html_encode(atom_to_list(Method))++ + " to "++ http_util:html_encode(RequestURI)++" ("++ http_util:html_encode(HTTPVersion)++") not supported."; is_list(Method) -> - Method++ - " to "++ maybe_encode(RequestURI)++" ("++HTTPVersion++") not supported." + http_util:html_encode(Method)++ + " to "++ http_util:html_encode(RequestURI)++" ("++ http_util:html_encode(HTTPVersion)++") not supported." end; message(503, String, _ConfigDB) -> - "This service in unavailable due to: "++String. + "This service in unavailable due to: "++ http_util:html_encode(String). maybe_encode(URI) -> - case lists:member($%, URI) of - true -> - URI; - false -> - http_uri:encode(URI) - end. + Decoded = try http_uri:decode(URI) of + N -> N + catch + error:_ -> URI + end, + http_uri:encode(Decoded). %%convert_rfc_date(Date)->{{YYYY,MM,DD},{HH,MIN,SEC}} diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index dcea200a1a..1cb07c2f5b 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -151,12 +151,13 @@ escaped_url_in_error_body(Config) when is_list(Config) -> URL = ?URL_START ++ integer_to_list(Port) ++ Path, EscapedPath = http_uri:encode(Path), {ok, {404, Body}} = httpc:request(get, {URL, []}, - [{url_encode, true}], - [{version, "HTTP/1.0"}, {full_result, false}]), + [{url_encode, true}, {version, "HTTP/1.0"}], + [{full_result, false}]), EscapedPath = find_URL_path(string:tokens(Body, " ")), - {ok, {404, Body1}} = httpc:request(get, {URL, []}, [], - [{version, "HTTP/1.0"}, {full_result, false}]), - EscapedPath = find_URL_path(string:tokens(Body1, " ")), + {ok, {404, Body1}} = httpc:request(get, {URL, []}, + [{version, "HTTP/1.0"}], [{full_result, false}]), + HTMLEncodedPath = http_util:html_encode(Path), + HTMLEncodedPath = find_URL_path(string:tokens(Body1, " ")), inets:stop(httpd, Pid). find_URL_path([]) -> -- cgit v1.2.3 From 0422eb9016dd7bea2dff4004895ed45815ef0f48 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 10 Mar 2011 16:15:35 +0100 Subject: Added proper release notes, appup and version. --- lib/inets/doc/src/notes.xml | 41 +++++++++++++++++++++++++++++++++ lib/inets/src/inets_app/inets.appup.src | 13 ++++++++++- lib/inets/vsn.mk | 2 +- 3 files changed, 54 insertions(+), 2 deletions(-) (limited to 'lib/inets') diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index 5da9d98002..5dbe761988 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -32,6 +32,47 @@ notes.xml +

Inets 5.6 + +
Improvements and New Features + + + + +

[httpd] Prevent XSS in error pages. + Prevent user controlled input from being interpreted + as HTML in error pages by encoding the reserved HTML + characters.

+

Michael Santos

+

Own Id: OTP-9124

+
+
+ +
+ +
Fixed Bugs and Malfunctions +

-

+ +
+ +
+ +
Inets 5.5.2
Improvements and New Features diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 07da8ca961..7e3785e240 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -18,6 +18,12 @@ {"%VSN%", [ + {"5.5.2", + [ + {load_module, http_util, soft_purge, soft_purge, []}, + {load_module, httpd_util, soft_purge, soft_purge, [http_util]} + ] + }, {"5.5.1", [ {load_module, http_chunk, soft_purge, soft_purge, []} @@ -34,7 +40,12 @@ ] } ], - [ + [ + {"5.5.2", + [ + {load_module, http_util, soft_purge, soft_purge, []}, + {load_module, httpd_util, soft_purge, soft_purge, [http_util]} + ] {"5.5.1", [ {load_module, http_chunk, soft_purge, soft_purge, []} diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index b1de3fef43..c0e25a30e3 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -18,7 +18,7 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 5.5.2 +INETS_VSN = 5.6 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" -- cgit v1.2.3 From bf6a8c53023a284b9f2de8d13538db1ab3ff8dee Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 11 Mar 2011 12:27:47 +0100 Subject: [httpd] Prevent XSS in error pages. Prevent user controlled input from being interpreted as HTML in error pages by encoding the reserved HTML characters. --- lib/inets/src/inets_app/inets.appup.src | 1 + lib/inets/test/httpd_basic_SUITE.erl | 55 +++++++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 13 deletions(-) (limited to 'lib/inets') diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 7e3785e240..b89ce0fbb2 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -46,6 +46,7 @@ {load_module, http_util, soft_purge, soft_purge, []}, {load_module, httpd_util, soft_purge, soft_purge, [http_util]} ] + }, {"5.5.1", [ {load_module, http_chunk, soft_purge, soft_purge, []} diff --git a/lib/inets/test/httpd_basic_SUITE.erl b/lib/inets/test/httpd_basic_SUITE.erl index 1cb07c2f5b..ced0d3d6d0 100644 --- a/lib/inets/test/httpd_basic_SUITE.erl +++ b/lib/inets/test/httpd_basic_SUITE.erl @@ -29,7 +29,11 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [uri_too_long_414, header_too_long_413, escaped_url_in_error_body]. + [ + uri_too_long_414, + header_too_long_413, + escaped_url_in_error_body + ]. groups() -> []. @@ -40,6 +44,7 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. + %%-------------------------------------------------------------------- %% Function: init_per_suite(Config) -> Config %% Config - [tuple()] @@ -50,6 +55,8 @@ end_per_group(_GroupName, Config) -> %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- init_per_suite(Config) -> + tsp("init_per_suite -> entry with" + "~n Config: ~p", [Config]), ok = inets:start(), PrivDir = ?config(priv_dir, Config), HttpdConf = [{port, 0}, {ipfamily, inet}, @@ -64,6 +71,8 @@ init_per_suite(Config) -> %% Description: Cleanup after the whole suite %%-------------------------------------------------------------------- end_per_suite(_Config) -> + tsp("end_per_suite -> entry with" + "~n Config: ~p", [_Config]), inets:stop(), ok. @@ -79,9 +88,12 @@ end_per_suite(_Config) -> %% Note: This function is free to add any key/value pairs to the Config %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- -init_per_testcase(_Case, Config) -> +init_per_testcase(Case, Config) -> + tsp("init_per_testcase(~w) -> entry with" + "~n Config: ~p", [Case, Config]), Config. + %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ %% Case - atom() @@ -90,9 +102,12 @@ init_per_testcase(_Case, Config) -> %% A list of key/value pairs, holding the test case configuration. %% Description: Cleanup after each test case %%-------------------------------------------------------------------- -end_per_testcase(_, Config) -> +end_per_testcase(Case, Config) -> + tsp("end_per_testcase(~w) -> entry with" + "~n Config: ~p", [Case, Config]), Config. + %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- @@ -142,23 +157,30 @@ escaped_url_in_error_body(doc) -> escaped_url_in_error_body(suite) -> []; escaped_url_in_error_body(Config) when is_list(Config) -> - HttpdConf = ?config(httpd_conf, Config), + tsp("escaped_url_in_error_body -> entry with" + "~n Config: ~p", [Config]), + HttpdConf = ?config(httpd_conf, Config), {ok, Pid} = inets:start(httpd, [{port, 0} | HttpdConf]), Info = httpd:info(Pid), Port = proplists:get_value(port, Info), - Address = proplists:get_value(bind_address, Info), - Path = "/this_is_bold", + _Address = proplists:get_value(bind_address, Info), + Path = "/this_is_bold", URL = ?URL_START ++ integer_to_list(Port) ++ Path, EscapedPath = http_uri:encode(Path), - {ok, {404, Body}} = httpc:request(get, {URL, []}, - [{url_encode, true}, {version, "HTTP/1.0"}], - [{full_result, false}]), - EscapedPath = find_URL_path(string:tokens(Body, " ")), {ok, {404, Body1}} = httpc:request(get, {URL, []}, - [{version, "HTTP/1.0"}], [{full_result, false}]), + [{url_encode, true}, + {version, "HTTP/1.0"}], + [{full_result, false}]), + EscapedPath = find_URL_path(string:tokens(Body1, " ")), + {ok, {404, Body2}} = httpc:request(get, {URL, []}, + [{url_encode, false}, + {version, "HTTP/1.0"}], + [{full_result, false}]), HTMLEncodedPath = http_util:html_encode(Path), - HTMLEncodedPath = find_URL_path(string:tokens(Body1, " ")), - inets:stop(httpd, Pid). + HTMLEncodedPath = find_URL_path(string:tokens(Body2, " ")), + inets:stop(httpd, Pid), + tsp("escaped_url_in_error_body -> done"), + ok. find_URL_path([]) -> ""; @@ -166,3 +188,10 @@ find_URL_path(["URL", URL | _]) -> URL; find_URL_path([_ | Rest]) -> find_URL_path(Rest). + + +tsp(F) -> + tsp(F, []). +tsp(F, A) -> + test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]). + -- cgit v1.2.3