about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-12-20 22:05:50 +0000
committerEric Wong <normalperson@yhbt.net>2010-12-20 22:05:50 +0000
commitbf64b9aa855cf3590a4d5b4eca853aef33ba90cc (patch)
tree0867d533bd3f79ebf336d0fda2667bb8f787e875
parent8be3668c11cf721960581e325b481c105e8f3c89 (diff)
downloadunicorn-bf64b9aa855cf3590a4d5b4eca853aef33ba90cc.tar.gz
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.
-rw-r--r--ext/unicorn_http/unicorn_http.rl63
-rw-r--r--test/unit/test_http_parser_xftrust.rb38
2 files changed, 88 insertions, 13 deletions
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