{"id":18265871,"url":"https://github.com/mpgn/rails-doubletap-rce","last_synced_at":"2025-04-04T21:32:07.597Z","repository":{"id":34353459,"uuid":"177236589","full_name":"mpgn/Rails-doubletap-RCE","owner":"mpgn","description":"RCE on Rails 5.2.2 using a path traversal (CVE-2019-5418) and a deserialization of Ruby objects (CVE-2019-5420)","archived":false,"fork":false,"pushed_at":"2023-01-19T12:13:40.000Z","size":71,"stargazers_count":134,"open_issues_count":14,"forks_count":27,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-20T19:34:54.369Z","etag":null,"topics":["rails"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mpgn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-23T02:52:31.000Z","updated_at":"2024-09-17T07:15:40.000Z","dependencies_parsed_at":"2023-02-10T23:45:43.110Z","dependency_job_id":null,"html_url":"https://github.com/mpgn/Rails-doubletap-RCE","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpgn%2FRails-doubletap-RCE","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpgn%2FRails-doubletap-RCE/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpgn%2FRails-doubletap-RCE/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mpgn%2FRails-doubletap-RCE/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mpgn","download_url":"https://codeload.github.com/mpgn/Rails-doubletap-RCE/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247252367,"owners_count":20908677,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rails"],"created_at":"2024-11-05T11:20:21.740Z","updated_at":"2025-04-04T21:32:07.115Z","avatar_url":"https://github.com/mpgn.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rails-doubletap-exploit\n\nRCE on Rails 5.2.2 using a path traversal (CVE-2019-5418) and a deserialization of Ruby objects (CVE-2019-5420)\n\n![capture d'écran](https://user-images.githubusercontent.com/5891788/54860812-dc7cc480-4d1f-11e9-8886-6d9c6a05d648.png)\n\n**Technical Analysis**:\n- CVE-2019-5418 - https://github.com/mpgn/CVE-2019-5418\n- CVE-2019-5420 - https://hackerone.com/reports/473888\n\n\n**Security Adivsory**:\n- CVE-2019-5418 - https://groups.google.com/forum/#!topic/rubyonrails-security/pFRKI96Sm8Q\n- CVE-2019-5420 - https://groups.google.com/forum/#!searchin/rubyonrails-security/CVE-2019-5420\n\n---\n\n### Exploit\n\n1. The exploit check if the Rails application is vulnerable to the **CVE-2019-5418**\n2. Then gets the content of the files: `credentials.yml.enc` and `master.key`\n3. Decrypt the *credentials.yml.enc* and get the **secret_key_base** value\n4. Craft a request to the ressource `/rails/active_storage/disk/:encoded_key/*filename(.:format)` =\u003e **CVE-2019-5420**\n5. Send the request to the vulnerable server\n6. The code is executed on the server\n\n![capture d'écran_1](https://user-images.githubusercontent.com/5891788/54864755-f2a87600-4d5b-11e9-9eab-8402ea52c978.png)\n\n**Mitigations**\n\n- You may notice the cache level is disabled on the exploit, but you can use a race condition to retrieve the two files: https://gist.github.com/snyff/04c3463845480632a1fe192308c31439#file-race_condition-sh\n\n---\nFix of **CVE-2019-5420**\n\n```diff\nFrom 7f5ccda38bfecbe0bf00f15e5b8f5e40d52ab3f1 Mon Sep 17 00:00:00 2001\nFrom: Aaron Patterson \u003caaron.patterson@gmail.com\u003e\nDate: Sun, 10 Mar 2019 16:37:46 -0700\nSubject: [PATCH] Fix possible dev mode RCE\n\nIf the secret_key_base is nil in dev or test generate a key from random\nbytes and store it in a tmp file. This prevents the app developers from\nhaving to share / checkin the secret key for dev / test but also\nmaintains a key between app restarts in dev/test.\n\n[CVE-2019-5420]\n\nCo-Authored-By: eileencodes \u003ceileencodes@gmail.com\u003e\nCo-Authored-By: John Hawthorn \u003cjohn@hawthorn.email\u003e\n---\n .../middleware/session/cookie_store.rb        |  7 +++---\n railties/lib/rails/application.rb             | 19 ++++++++++++++--\n .../test/application/configuration_test.rb    | 22 ++++++++++++++++++-\n railties/test/isolation/abstract_unit.rb      |  1 +\n 4 files changed, 43 insertions(+), 6 deletions(-)\n\ndiff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb\nindex 4ea96196d3..b7475d3682 100644\n--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb\n+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb\n@@ -29,9 +29,10 @@\n     #\n     #   Rails.application.config.session_store :cookie_store, key: '_your_app_session'\n     #\n-    # By default, your secret key base is derived from your application name in\n-    # the test and development environments. In all other environments, it is stored\n-    # encrypted in the \u003ctt\u003econfig/credentials.yml.enc\u003c/tt\u003e file.\n+    # In the development and test environments your application's secret key base is\n+    # generated by Rails and stored in a temporary file in \u003ctt\u003etmp/development_secret.txt\u003c/tt\u003e.\n+    # In all other environments, it is stored encrypted in the\n+    # \u003ctt\u003econfig/credentials.yml.enc\u003c/tt\u003e file.\n     #\n     # If your application was not updated to Rails 5.2 defaults, the secret_key_base\n     # will be found in the old \u003ctt\u003econfig/secrets.yml\u003c/tt\u003e file.\ndiff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb\nindex e346d5cc3a..6a30e8cfa0 100644\n--- a/railties/lib/rails/application.rb\n+++ b/railties/lib/rails/application.rb\n@@ -426,8 +426,8 @@ def secrets=(secrets) #:nodoc:\n     # then credentials.secret_key_base, and finally secrets.secret_key_base. For most applications,\n     # the correct place to store it is in the encrypted credentials file.\n     def secret_key_base\n-      if Rails.env.test? || Rails.env.development?\n-        secrets.secret_key_base || Digest::MD5.hexdigest(self.class.name)\n+      if Rails.env.development? || Rails.env.test?\n+        secrets.secret_key_base ||= generate_development_secret\n       else\n         validate_secret_key_base(\n           ENV[\"SECRET_KEY_BASE\"] || credentials.secret_key_base || secrets.secret_key_base\n@@ -588,6 +588,21 @@ def validate_secret_key_base(secret_key_base)\n \n     private\n \n+      def generate_development_secret\n+        if secrets.secret_key_base.nil?\n+          key_file = Rails.root.join(\"tmp/development_secret.txt\")\n+\n+          if !File.exist?(key_file)\n+            random_key = SecureRandom.hex(64)\n+            File.binwrite(key_file, random_key)\n+          end\n+\n+          secrets.secret_key_base = File.binread(key_file)\n+        end\n+\n+        secrets.secret_key_base\n+      end\n+\n       def build_request(env)\n         req = super\n         env[\"ORIGINAL_FULLPATH\"] = req.fullpath\ndiff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb\nindex 293a1a7dbd..68c2199aba 100644\n--- a/railties/test/application/configuration_test.rb\n+++ b/railties/test/application/configuration_test.rb\n@@ -513,6 +513,27 @@ def index\n     end\n \n \n+    test \"application will generate secret_key_base in tmp file if blank in development\" do\n+      app_file \"config/initializers/secret_token.rb\", \u003c\u003c-RUBY\n+        Rails.application.credentials.secret_key_base = nil\n+      RUBY\n+\n+      app \"development\"\n+\n+      assert_not_nil app.secrets.secret_key_base\n+      assert File.exist?(app_path(\"tmp/development_secret.txt\"))\n+    end\n+\n+    test \"application will not generate secret_key_base in tmp file if blank in production\" do\n+      app_file \"config/initializers/secret_token.rb\", \u003c\u003c-RUBY\n+        Rails.application.credentials.secret_key_base = nil\n+      RUBY\n+\n+      assert_raises ArgumentError do\n+        app \"production\"\n+      end\n+    end\n+\n     test \"raises when secret_key_base is blank\" do\n       app_file \"config/initializers/secret_token.rb\", \u003c\u003c-RUBY\n         Rails.application.credentials.secret_key_base = nil\n@@ -550,7 +571,6 @@ def index\n \n     test \"application verifier can build different verifiers\" do\n       make_basic_app do |application|\n-        application.credentials.secret_key_base = \"b3c631c314c0bbca50c1b2843150fe33\"\n         application.config.session_store :disabled\n       end\n \ndiff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb\nindex 6568a356d6..fe850d45ec 100644\n--- a/railties/test/isolation/abstract_unit.rb\n+++ b/railties/test/isolation/abstract_unit.rb\n@@ -155,6 +155,7 @@ def self.name; \"RailtiesTestApp\"; end\n       @app.config.active_support.deprecation = :log\n       @app.config.active_support.test_order = :random\n       @app.config.log_level = :info\n+      @app.secrets.secret_key_base = \"b3c631c314c0bbca50c1b2843150fe33\"\n \n       yield @app if block_given?\n       @app.initialize!\n-- \n2.21.0\n```\n\nFix of **CVE-2019-5418**\n```diff\nFrom d7fac9c09a535ec7f11bb9aa8addb4af37b7d4b5 Mon Sep 17 00:00:00 2001\nFrom: John Hawthorn \u003cjohn@hawthorn.email\u003e\nDate: Mon, 4 Mar 2019 18:24:51 -0800\nSubject: [PATCH] Only accept formats from registered mime types\n\n[CVE-2019-5418]\n[CVE-2019-5419]\n---\n .../lib/action_dispatch/http/mime_negotiation.rb   |  5 +++++\n actionpack/test/controller/mime/respond_to_test.rb | 10 ++++++----\n .../new_base/content_negotiation_test.rb           | 14 ++++++++++++--\n 3 files changed, 23 insertions(+), 6 deletions(-)\n\ndiff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb\nindex d7435fa8df..ada52adfeb 100644\n--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb\n+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb\n@@ -74,6 +74,11 @@ def formats\n           else\n             [Mime[:html]]\n           end\n+\n+          v = v.select do |format|\n+            format.symbol || format.ref == \"*/*\"\n+          end\n+\n           set_header k, v\n         end\n       end\ndiff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb\nindex f9ffd5f54c..a80cef83b7 100644\n--- a/actionpack/test/controller/mime/respond_to_test.rb\n+++ b/actionpack/test/controller/mime/respond_to_test.rb\n@@ -105,7 +105,7 @@ def made_for_content_type\n   def custom_type_handling\n     respond_to do |type|\n       type.html { render body: \"HTML\"    }\n-      type.custom(\"application/crazy-xml\")  { render body: \"Crazy XML\"  }\n+      type.custom(\"application/fancy-xml\")  { render body: \"Fancy XML\"  }\n       type.all  { render body: \"Nothing\" }\n     end\n   end\n@@ -294,12 +294,14 @@ def setup\n     @request.host = \"www.example.com\"\n     Mime::Type.register_alias(\"text/html\", :iphone)\n     Mime::Type.register(\"text/x-mobile\", :mobile)\n+    Mime::Type.register(\"application/fancy-xml\", :fancy_xml)\n   end\n \n   def teardown\n     super\n     Mime::Type.unregister(:iphone)\n     Mime::Type.unregister(:mobile)\n+    Mime::Type.unregister(:fancy_xml)\n   end\n \n   def test_html\n@@ -455,10 +457,10 @@ def test_synonyms\n   end\n \n   def test_custom_types\n-    @request.accept = \"application/crazy-xml\"\n+    @request.accept = \"application/fancy-xml\"\n     get :custom_type_handling\n-    assert_equal \"application/crazy-xml\", @response.content_type\n-    assert_equal \"Crazy XML\", @response.body\n+    assert_equal \"application/fancy-xml\", @response.content_type\n+    assert_equal \"Fancy XML\", @response.body\n \n     @request.accept = \"text/html\"\n     get :custom_type_handling\ndiff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb\nindex 7205e90176..6de91c57b7 100644\n--- a/actionpack/test/controller/new_base/content_negotiation_test.rb\n+++ b/actionpack/test/controller/new_base/content_negotiation_test.rb\n@@ -20,9 +20,19 @@ def all\n       assert_body \"Hello world */*!\"\n     end\n \n-    test \"Not all mimes are converted to symbol\" do\n+    test \"A js or */* Accept header will return HTML\" do\n+      get \"/content_negotiation/basic/hello\", headers: { \"HTTP_ACCEPT\" =\u003e \"text/javascript, */*\" }\n+      assert_body \"Hello world text/html!\"\n+    end\n+\n+    test \"A js or */* Accept header on xhr will return HTML\" do\n+      get \"/content_negotiation/basic/hello\", headers: { \"HTTP_ACCEPT\" =\u003e \"text/javascript, */*\" }, xhr: true\n+      assert_body \"Hello world text/javascript!\"\n+    end\n+\n+    test \"Unregistered mimes are ignored\" do\n       get \"/content_negotiation/basic/all\", headers: { \"HTTP_ACCEPT\" =\u003e \"text/plain, mime/another\" }\n-      assert_body '[:text, \"mime/another\"]'\n+      assert_body '[:text]'\n     end\n   end\n end\n-- \n2.21.0\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpgn%2Frails-doubletap-rce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmpgn%2Frails-doubletap-rce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmpgn%2Frails-doubletap-rce/lists"}