Skip to content

Commit 57b850c

Browse files
committed
Land rapid7#6373, joomla mixin
2 parents 951a76f + 2cc54a7 commit 57b850c

File tree

7 files changed

+317
-128
lines changed

7 files changed

+317
-128
lines changed

lib/msf/core/exploit/http/joomla.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# -*- coding: binary -*-
2+
3+
module Msf
4+
class Exploit
5+
class Remote
6+
module HTTP
7+
module Joomla
8+
require 'msf/core/exploit/http/joomla/base'
9+
require 'msf/core/exploit/http/joomla/version'
10+
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Exploit::Remote::HTTP::Joomla::Base
13+
include Msf::Exploit::Remote::HTTP::Joomla::Version
14+
15+
def initialize(info = {})
16+
super
17+
18+
register_options(
19+
[
20+
Msf::OptString.new('TARGETURI', [true, 'The base path to the Joomla application', '/'])
21+
], Msf::Exploit::Remote::HTTP::Joomla
22+
)
23+
end
24+
25+
end
26+
end
27+
end
28+
end
29+
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# -*- coding: binary -*-
2+
3+
module Msf::Exploit::Remote::HTTP::Joomla::Base
4+
5+
# Checks if Joomla is up and running.
6+
#
7+
# @return [TrueClass] Joomla is up and running.
8+
# @return [FalseClass] Joomla is not up.
9+
def joomla_and_online?
10+
# Possible paths that we might see the generator tag.
11+
paths = [ '/', '/administrator/' ]
12+
13+
paths.each do |path|
14+
res = send_request_cgi({
15+
'uri' => normalize_uri(target_uri.path, path)
16+
})
17+
18+
if res
19+
elements = res.get_html_meta_elements
20+
elements.each_entry do |e|
21+
if e.attributes['content'] && /joomla!/i === e.attributes['content'].value
22+
return true
23+
end
24+
end
25+
end
26+
end
27+
28+
false
29+
end
30+
end
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# -*- coding: binary -*-
2+
3+
module Msf::Exploit::Remote::HTTP::Joomla::Version
4+
5+
# Returns the Joomla version.
6+
#
7+
# @return [String] Joomla version (if found)
8+
# @return [NilClass] No Joomla version found.
9+
def joomla_version
10+
files = [
11+
'administrator/manifests/files/joomla.xml',
12+
'language/en-GB/en-GB.xml',
13+
'templates/system/css/system.css',
14+
'media/system/js/mootools-more.js',
15+
'language/en-GB/en-GB.ini',
16+
'htaccess.txt',
17+
'language/en-GB/en-GB.com_media.ini'
18+
]
19+
20+
files.each do |file|
21+
version = check_file_version(file)
22+
return version if version
23+
end
24+
25+
nil
26+
end
27+
28+
private
29+
30+
# Returns the Joomla version based on a response.
31+
#
32+
# @param res [Rex::Proto::Http::Response] Response to fingerprint from.
33+
# @return [String] Joomla version (if found)
34+
# @return [NilClass] No Joomla version found.
35+
def fingerprint(res)
36+
version = nil
37+
38+
case res.body
39+
when /<version.*\/?>[[:print:]]+<\/version\/?>/i
40+
version = res.get_html_document.at('version').text
41+
when /system\.css 20196 2011\-01\-09 02\:40\:25Z ian/,
42+
/MooTools\.More\=\{version\:\"1\.3\.0\.1\"/,
43+
/en-GB\.ini 20196 2011\-01\-09 02\:40\:25Z ian/,
44+
/en-GB\.ini 20990 2011\-03\-18 16\:42\:30Z infograf768/,
45+
/20196 2011\-01\-09 02\:40\:25Z ian/
46+
version = '1.6'
47+
when /system\.css 21322 2011\-05\-11 01\:10\:29Z dextercowley /,
48+
/MooTools\.More\=\{version\:\"1\.3\.2\.1\"/,
49+
/22183 2011\-09\-30 09\:04\:32Z infograf768/,
50+
/21660 2011\-06\-23 13\:25\:32Z infograf768/
51+
version = '1.7'
52+
when /Joomla! 1\.5/,
53+
/MooTools\=\{version\:\'1\.12\'\}/,
54+
/11391 2009\-01\-04 13\:35\:50Z ian/
55+
version = '1.5'
56+
when /Copyright \(C\) 2005 \- 2012 Open Source Matters/,
57+
/MooTools.More\=\{version\:\"1\.4\.0\.1\"/
58+
version = '2.5'
59+
when /<meta name=\"Keywords\" content=\"(.*)\">\s+<meta name/
60+
version = $1.split(/,/)[0]
61+
when /(Copyright \(C\) 2005 \- 200(6|7))/,
62+
/47 2005\-09\-15 02\:55\:27Z rhuk/,
63+
/423 2005\-10\-09 18\:23\:50Z stingrey/,
64+
/1005 2005\-11\-13 17\:33\:59Z stingrey/,
65+
/1570 2005\-12\-29 05\:53\:33Z eddieajau/,
66+
/2368 2006\-02\-14 17\:40\:02Z stingrey/,
67+
/4085 2006\-06\-21 16\:03\:54Z stingrey/,
68+
/4756 2006\-08\-25 16\:07\:11Z stingrey/,
69+
/5973 2006\-12\-11 01\:26\:33Z robs/,
70+
/5975 2006\-12\-11 01\:26\:33Z robs/
71+
version = '1.0'
72+
end
73+
74+
version
75+
end
76+
77+
# Returns the Joomla version based on a file path.
78+
#
79+
# @param file [String] File path to check.
80+
# @return [String] Joomla version (if found)
81+
# @return [NilClass] No Joomla version found.
82+
def check_file_version(file)
83+
version = nil
84+
85+
res = send_request_cgi({
86+
'uri' => normalize_uri(target_uri.path, file)
87+
})
88+
89+
if res && res.code == 200
90+
version = fingerprint(res)
91+
end
92+
93+
version
94+
end
95+
96+
end

lib/msf/core/exploit/mixins.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110

111111
# Custom HTTP Modules
112112
require 'msf/core/exploit/http/wordpress'
113+
require 'msf/core/exploit/http/joomla'
113114
require 'msf/core/exploit/http/typo3'
114115
require 'msf/core/exploit/http/jboss'
115116

modules/auxiliary/scanner/http/joomla_version.rb

Lines changed: 32 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
# This module requires Metasploit: http://metasploit.com/download
33
# Current source: https://github.com/rapid7/metasploit-framework
44
##
5+
56
require 'msf/core'
67

78
class Metasploit3 < Msf::Auxiliary
89

9-
include Msf::Exploit::Remote::HttpClient
10+
include Msf::Exploit::Remote::HTTP::Joomla
1011
include Msf::Auxiliary::Scanner
1112
include Msf::Auxiliary::Report
1213

@@ -22,146 +23,49 @@ def initialize
2223
'Author' => [ 'newpid0' ],
2324
'License' => MSF_LICENSE
2425
)
25-
register_options(
26-
[
27-
OptString.new('TARGETURI', [ true, "The path to the Joomla install", '/'])
28-
], self.class)
29-
end
30-
31-
def os_fingerprint(response)
32-
if not response.headers.has_key?('Server')
33-
return "Unknown OS (No Server Header)"
34-
end
35-
36-
case response.headers['Server']
37-
when /Win32/, /\(Windows/, /IIS/
38-
os = "Windows"
39-
when /Apache\//
40-
os = "*Nix"
41-
else
42-
os = "Unknown Server Header Reporting: "+response.headers['Server']
43-
end
44-
return os
4526
end
4627

47-
def fingerprint(response)
48-
case response.body
49-
when /<version.*\/?>(.+)<\/version\/?>/i
50-
v = $1
51-
out = (v =~ /^6/) ? "Joomla #{v}" : " #{v}"
52-
when /system\.css 20196 2011\-01\-09 02\:40\:25Z ian/,
53-
/MooTools\.More\=\{version\:\"1\.3\.0\.1\"/,
54-
/en-GB\.ini 20196 2011\-01\-09 02\:40\:25Z ian/,
55-
/en-GB\.ini 20990 2011\-03\-18 16\:42\:30Z infograf768/,
56-
/20196 2011\-01\-09 02\:40\:25Z ian/
57-
out = "1.6"
58-
when /system\.css 21322 2011\-05\-11 01\:10\:29Z dextercowley /,
59-
/MooTools\.More\=\{version\:\"1\.3\.2\.1\"/,
60-
/22183 2011\-09\-30 09\:04\:32Z infograf768/,
61-
/21660 2011\-06\-23 13\:25\:32Z infograf768/
62-
out = "1.7"
63-
when /Joomla! 1.5/,
64-
/MooTools\=\{version\:\'1\.12\'\}/,
65-
/11391 2009\-01\-04 13\:35\:50Z ian/
66-
out = "1.5"
67-
when /Copyright \(C\) 2005 \- 2012 Open Source Matters/,
68-
/MooTools.More\=\{version\:\"1\.4\.0\.1\"/
69-
out = "2.5"
70-
when /<meta name=\"Keywords\" content=\"(.*)\">\s+<meta name/
71-
out = $1.split(/,/)[0]
72-
when /(Copyright \(C\) 2005 - 200(6|7))/,
73-
/47 2005\-09\-15 02\:55\:27Z rhuk/,
74-
/423 2005\-10\-09 18\:23\:50Z stingrey/,
75-
/1005 2005\-11\-13 17\:33\:59Z stingrey/,
76-
/1570 2005\-12\-29 05\:53\:33Z eddieajau/,
77-
/2368 2006\-02\-14 17\:40\:02Z stingrey/,
78-
/4085 2006\-06\-21 16\:03\:54Z stingrey/,
79-
/4756 2006\-08\-25 16\:07\:11Z stingrey/,
80-
/5973 2006\-12\-11 01\:26\:33Z robs/,
81-
/5975 2006\-12\-11 01\:26\:33Z robs/
82-
out = "1.0"
83-
else
84-
out = 'Unknown Joomla'
85-
end
86-
return out
87-
end
88-
89-
def check_file(tpath, file, ip)
28+
def get_server_header
29+
# This module used to determine the operating system by the server header. But this is
30+
# not an accurate way to do OS detection, so we have toned it down to just returning the
31+
# header, and let the user decide.
9032
res = send_request_cgi({
91-
'uri' => "#{tpath}#{file}",
92-
'method' => 'GET'
33+
'uri' => normalize_uri(target_uri.path)
9334
})
9435

95-
return :abort if res.nil?
96-
97-
res.body.gsub!(/[\r|\n]/, ' ')
98-
99-
if (res.code == 200)
100-
os = os_fingerprint(res)
101-
out = fingerprint(res)
102-
return false if not out
103-
104-
if(out =~ /Unknown Joomla/)
105-
print_error("#{peer} - Unable to identify Joomla Version with #{file}")
106-
return false
107-
else
108-
print_good("#{peer} - Joomla Version:#{out} from: #{file} ")
109-
print_good("#{peer} - OS: #{os}")
110-
report_note(
111-
:host => ip,
112-
:port => datastore['RPORT'],
113-
:proto => 'http',
114-
:ntype => 'joomla_version',
115-
:data => out
116-
)
117-
return true
118-
end
119-
elsif (res.code == 403)
120-
if(res.body =~ /secured with Secure Sockets Layer/ or res.body =~ /Secure Channel Required/ or res.body =~ /requires a secure connection/)
121-
vprint_status("#{ip} denied access to #{ip} (SSL Required)")
122-
elsif(res.body =~ /has a list of IP addresses that are not allowed/)
123-
vprint_status("#{ip} restricted access by IP")
124-
elsif(res.body =~ /SSL client certificate is required/)
125-
vprint_status("#{ip} requires a SSL client certificate")
126-
else
127-
vprint_status("#{ip} denied access to #{ip} #{res.code} #{res.message}")
128-
end
129-
return :abort
36+
if res && res.headers['Server']
37+
return res.headers['Server']
13038
end
13139

132-
return false
133-
134-
rescue OpenSSL::SSL::SSLError
135-
vprint_error("#{peer} - SSL error")
136-
return :abort
137-
rescue Errno::ENOPROTOOPT, Errno::ECONNRESET, ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::ArgumentError
138-
vprint_error("#{peer} - Unable to Connect")
139-
return :abort
140-
rescue ::Timeout::Error, ::Errno::EPIPE
141-
vprint_error("#{peer} - Timeout error")
142-
return :abort
40+
nil
14341
end
14442

14543
def run_host(ip)
146-
tpath = normalize_uri(target_uri.path)
147-
if tpath[-1,1] != '/'
148-
tpath += '/'
44+
unless joomla_and_online?
45+
print_error("It doesn't look like Joomla is up and running at #{target_uri.to_s}")
46+
return
14947
end
15048

151-
files = [
152-
'language/en-GB/en-GB.xml',
153-
'templates/system/css/system.css',
154-
'media/system/js/mootools-more.js',
155-
'language/en-GB/en-GB.ini',
156-
'htaccess.txt',
157-
'language/en-GB/en-GB.com_media.ini'
158-
]
49+
server = get_server_header
50+
version = joomla_version
15951

160-
vprint_status("#{peer} - Checking Joomla version")
161-
files.each do |file|
162-
joomla_found = check_file(tpath, file, ip)
163-
return if joomla_found == :abort
164-
break if joomla_found
52+
if server
53+
print_status("Server: #{server}")
54+
else
55+
print_error("Unable to determine server.")
56+
end
57+
58+
if version
59+
print_status("Joomla version: #{version}")
60+
report_note(
61+
host: ip,
62+
port: datastore['RPORT'],
63+
proto: ssl ? 'https' : 'http',
64+
ntype: 'joomla.version',
65+
data: version
66+
)
67+
else
68+
print_error("Unable to find Joomla version.")
16569
end
16670
end
16771

0 commit comments

Comments
 (0)