From bf64b9aa855cf3590a4d5b4eca853aef33ba90cc Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 20 Dec 2010 22:05:50 +0000 Subject: http: allow ignoring X-Forwarded-* for url_scheme Evil clients may be exposed to the Unicorn parser via Rainbows!, so we'll allow people to turn off blindly trusting certain X-Forwarded* headers for "rack.url_scheme" and rely on middleware to handle it. --- ext/unicorn_http/unicorn_http.rl | 63 +++++++++++++++++++++++++++-------- test/unit/test_http_parser_xftrust.rb | 38 +++++++++++++++++++++ 2 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 test/unit/test_http_parser_xftrust.rb diff --git a/ext/unicorn_http/unicorn_http.rl b/ext/unicorn_http/unicorn_http.rl index 510f456..cee0e0b 100644 --- a/ext/unicorn_http/unicorn_http.rl +++ b/ext/unicorn_http/unicorn_http.rl @@ -26,6 +26,12 @@ /* all of these flags need to be set for keepalive to be supported */ #define UH_FL_KEEPALIVE (UH_FL_KAVERSION | UH_FL_REQEOF | UH_FL_HASHEADER) +/* + * whether or not to trust X-Forwarded-Proto and X-Forwarded-SSL when + * setting rack.url_scheme + */ +static VALUE x_forwarded_trust = Qtrue; + static unsigned long keepalive_requests = 100; /* same as nginx */ /* @@ -49,6 +55,31 @@ static VALUE set_ka_req(VALUE self, VALUE val) return ka_req(self); } +/* + * Sets whether or not the parser will trust X-Forwarded-Proto and + * X-Forwarded-SSL headers and set "rack.url_scheme" to "https" accordingly. + * Rainbows!/Zbatery installations facing untrusted clients directly + * should set this to +false+ + */ +static VALUE set_xftrust(VALUE self, VALUE val) +{ + if (Qtrue == val || Qfalse == val) + x_forwarded_trust = val; + else + rb_raise(rb_eTypeError, "must be true or false"); + + return val; +} + +/* + * returns whether or not the parser will trust X-Forwarded-Proto and + * X-Forwarded-SSL headers and set "rack.url_scheme" to "https" accordingly + */ +static VALUE xftrust(VALUE self) +{ + return x_forwarded_trust; +} + /* keep this small for Rainbows! since every client has one */ struct http_parser { int cs; /* Ragel internal state */ @@ -426,22 +457,26 @@ static void set_url_scheme(VALUE env, VALUE *server_port) VALUE scheme = rb_hash_aref(env, g_rack_url_scheme); if (NIL_P(scheme)) { - scheme = rb_hash_aref(env, g_http_x_forwarded_ssl); - if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) { - *server_port = g_port_443; - scheme = g_https; + if (x_forwarded_trust == Qfalse) { + scheme = g_http; } else { - scheme = rb_hash_aref(env, g_http_x_forwarded_proto); - if (NIL_P(scheme)) { - scheme = g_http; + scheme = rb_hash_aref(env, g_http_x_forwarded_ssl); + if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) { + *server_port = g_port_443; + scheme = g_https; } else { - long len = RSTRING_LEN(scheme); - if (len >= 5 && !memcmp(RSTRING_PTR(scheme), "https", 5)) { - if (len != 5) - scheme = g_https; - *server_port = g_port_443; - } else { + scheme = rb_hash_aref(env, g_http_x_forwarded_proto); + if (NIL_P(scheme)) { scheme = g_http; + } else { + long len = RSTRING_LEN(scheme); + if (len >= 5 && !memcmp(RSTRING_PTR(scheme), "https", 5)) { + if (len != 5) + scheme = g_https; + *server_port = g_port_443; + } else { + scheme = g_http; + } } } } @@ -853,6 +888,8 @@ void Init_unicorn_http(void) rb_define_singleton_method(cHttpParser, "keepalive_requests", ka_req, 0); rb_define_singleton_method(cHttpParser, "keepalive_requests=", set_ka_req, 1); + rb_define_singleton_method(cHttpParser, "x_forwarded_trust=", set_xftrust, 1); + rb_define_singleton_method(cHttpParser, "x_forwarded_trust?", xftrust, 0); init_common_fields(); SET_GLOBAL(g_http_host, "HOST"); diff --git a/test/unit/test_http_parser_xftrust.rb b/test/unit/test_http_parser_xftrust.rb new file mode 100644 index 0000000..8c3db40 --- /dev/null +++ b/test/unit/test_http_parser_xftrust.rb @@ -0,0 +1,38 @@ +# -*- encoding: binary -*- +require 'test/test_helper' + +include Unicorn + +class HttpParserXFTrustTest < Test::Unit::TestCase + def setup + assert HttpParser.x_forwarded_trust? + end + + def test_xf_trust_false_xfp + HttpParser.x_forwarded_trust = false + parser = HttpParser.new + parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \ + "X-Forwarded-Proto: https\r\n\r\n" + env = parser.parse + assert_kind_of Hash, env + assert_equal 'foo', env['SERVER_NAME'] + assert_equal '80', env['SERVER_PORT'] + assert_equal 'http', env['rack.url_scheme'] + end + + def test_xf_trust_false_xfs + HttpParser.x_forwarded_trust = false + parser = HttpParser.new + parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \ + "X-Forwarded-SSL: on\r\n\r\n" + env = parser.parse + assert_kind_of Hash, env + assert_equal 'foo', env['SERVER_NAME'] + assert_equal '80', env['SERVER_PORT'] + assert_equal 'http', env['rack.url_scheme'] + end + + def teardown + HttpParser.x_forwarded_trust = true + end +end -- cgit v1.2.3-24-ge0c7