Skip to content

Commit 70c8296

Browse files
committed
Merge branch 'master' of github.com:rapid7/metasploit-framework into rapid7
2 parents d485460 + 6a7f875 commit 70c8296

File tree

2 files changed

+239
-3
lines changed

2 files changed

+239
-3
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# web site for more information on licensing and terms of use.
5+
# http://metasploit.com/
6+
##
7+
8+
require 'msf/core'
9+
10+
class Metasploit3 < Msf::Auxiliary
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Auxiliary::Scanner
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'Wordpress Pingback Locator',
17+
'Description' => %q{
18+
This module will scan for wordpress sites with the Pingback
19+
API enabled. By interfacing with the API an attacker can cause
20+
the wordpress site to port scan an external target and return
21+
results. Refer to the wordpress_pingback_portscanner module.
22+
},
23+
'Author' =>
24+
[
25+
'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>',
26+
'Brandon McCann "zeknox" <bmccann[at]accuvant.com>' ,
27+
'FireFart' # Original PoC
28+
],
29+
'License' => MSF_LICENSE,
30+
'References' =>
31+
[
32+
[ 'URL', 'http://www.securityfocus.com/archive/1/525045/30/30/threaded'],
33+
[ 'URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'],
34+
[ 'URL', 'https://github.com/FireFart/WordpressPingbackPortScanner']
35+
]
36+
))
37+
38+
register_options(
39+
[
40+
OptString.new('TARGETURI', [ true, 'The path to wordpress installation (e.g. /wordpress/)', '/'])
41+
], self.class)
42+
43+
register_advanced_options(
44+
[
45+
OptInt.new('NUM_REDIRECTS', [ true, "Number of HTTP redirects to follow", 10])
46+
], self.class)
47+
end
48+
49+
def setup()
50+
# Check if database is active
51+
if db()
52+
@db_active = true
53+
else
54+
@db_active = false
55+
end
56+
end
57+
58+
def get_xml_rpc_url(ip)
59+
# code to find the xmlrpc url when passed in IP
60+
vprint_status("#{ip} - Enumerating XML-RPC URI...")
61+
62+
begin
63+
64+
uri = target_uri.path
65+
uri << '/' if uri[-1,1] != '/'
66+
67+
res = send_request_cgi(
68+
{
69+
'method' => 'HEAD',
70+
'uri' => "#{uri}"
71+
})
72+
# Check if X-Pingback exists and return value
73+
if res
74+
if res['X-Pingback']
75+
return res['X-Pingback']
76+
else
77+
vprint_status("#{ip} - X-Pingback header not found")
78+
return nil
79+
end
80+
else
81+
return nil
82+
end
83+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
84+
vprint_error("#{ip} - Unable to connect")
85+
return nil
86+
rescue ::Timeout::Error, ::Errno::EPIPE
87+
vprint_error("#{ip} - Unable to connect")
88+
return nil
89+
end
90+
end
91+
92+
# Creates the XML data to be sent
93+
def generate_pingback_xml(target, valid_blog_post)
94+
xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
95+
xml << "<methodCall>"
96+
xml << "<methodName>pingback.ping</methodName>"
97+
xml << "<params>"
98+
xml << "<param><value><string>#{target}</string></value></param>"
99+
xml << "<param><value><string>#{valid_blog_post}</string></value></param>"
100+
xml << "</params>"
101+
xml << "</methodCall>"
102+
return xml
103+
end
104+
105+
def get_blog_posts(xml_rpc, ip)
106+
# find all blog posts within IP and determine if pingback is enabled
107+
vprint_status("#{ip} - Enumerating Blog posts on...")
108+
blog_posts = nil
109+
110+
uri = target_uri.path
111+
uri << '/' if uri[-1,1] != '/'
112+
113+
# make http request to feed url
114+
begin
115+
vprint_status("#{ip} - Resolving #{uri}?feed=rss2 to locate wordpress feed...")
116+
res = send_request_cgi({
117+
'uri' => "#{uri}?feed=rss2",
118+
'method' => 'GET'
119+
})
120+
121+
count = datastore['NUM_REDIRECTS']
122+
123+
# Follow redirects
124+
while (res.code == 301 || res.code == 302) and res.headers['Location'] and count != 0
125+
vprint_status("#{ip} - Web server returned a #{res.code}...following to #{res.headers['Location']}")
126+
127+
uri = res.headers['Location'].sub(/(http|https):\/\/.*?\//, "/")
128+
res = send_request_cgi({
129+
'uri' => "#{uri}",
130+
'method' => 'GET'
131+
})
132+
133+
if res.code == 200
134+
vprint_status("#{ip} - Feed located at #{uri}")
135+
else
136+
vprint_status("#{ip} - Returned a #{res.code}...")
137+
end
138+
count = count - 1
139+
end
140+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
141+
vprint_error("#{ip} - Unable to connect")
142+
return nil
143+
rescue ::Timeout::Error, ::Errno::EPIPE
144+
vprint_error("#{ip} - Unable to connect")
145+
return nil
146+
end
147+
148+
if res.nil? or res.code != 200
149+
vprint_status("#{ip} - Did not recieve HTTP response from #{ip}")
150+
return blog_posts
151+
end
152+
153+
# parse out links and place in array
154+
links = res.body.scan(/<link>([^<]+)<\/link>/i)
155+
156+
if links.nil? or links.empty?
157+
vprint_status("#{ip} - Feed at #{ip} did not have any links present")
158+
return blog_posts
159+
end
160+
161+
links.each do |link|
162+
blog_post = link[0]
163+
pingback_response = get_pingback_request(xml_rpc, 'http://127.0.0.1', blog_post)
164+
if pingback_response
165+
pingback_disabled_match = pingback_response.body.match(/<value><int>33<\/int><\/value>/i)
166+
if pingback_response.code == 200 and pingback_disabled_match.nil?
167+
print_good("#{ip} - Pingback enabled: #{link.join}")
168+
blog_posts = link.join
169+
return blog_posts
170+
else
171+
vprint_status("#{ip} - Pingback disabled: #{link.join}")
172+
end
173+
end
174+
end
175+
return blog_posts
176+
end
177+
178+
# method to send xml-rpc requests
179+
def get_pingback_request(xml_rpc, target, blog_post)
180+
uri = xml_rpc.sub(/.*?#{target}/,"")
181+
# create xml pingback request
182+
pingback_xml = generate_pingback_xml(target, blog_post)
183+
184+
# Send post request with crafted XML as data
185+
begin
186+
res = send_request_cgi({
187+
'uri' => "#{uri}",
188+
'method' => 'POST',
189+
'data' => "#{pingback_xml}"
190+
})
191+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
192+
vprint_error("Unable to connect to #{uri}")
193+
return nil
194+
rescue ::Timeout::Error, ::Errno::EPIPE
195+
vprint_error("Unable to connect to #{uri}")
196+
return nil
197+
end
198+
return res
199+
end
200+
201+
# Save data to vuln table
202+
def store_vuln(ip, blog)
203+
report_vuln(
204+
:host => ip,
205+
:proto => 'tcp',
206+
:port => datastore['RPORT'],
207+
:name => self.name,
208+
:info => "Module #{self.fullname} found pingback at #{blog}",
209+
:sname => datastore['SSL'] ? "https" : "http"
210+
)
211+
end
212+
213+
# main control method
214+
def run_host(ip)
215+
# call method to get xmlrpc url
216+
xmlrpc = get_xml_rpc_url(ip)
217+
218+
# once xmlrpc url is found, get_blog_posts
219+
if xmlrpc.nil?
220+
vprint_error("#{ip} - It doesn't appear to be vulnerable")
221+
else
222+
hash = get_blog_posts(xmlrpc, ip)
223+
224+
if hash
225+
store_vuln(ip, hash) if @db_active
226+
else
227+
vprint_status("#{ip} - X-Pingback enabled but no vulnerable blogs found")
228+
end
229+
end
230+
end
231+
end

modules/exploits/multi/http/rails_xml_yaml_code_exec.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,18 @@ def initialize(info = {})
2323
any ruby code remotely in the context of the application.
2424
2525
This module has been tested across multiple versions of RoR 3.x and RoR 2.x
26+
27+
The technique used by this module requires the target to be running a fairly version
28+
of Ruby 1.9 (since 2011 or so). Applications using Ruby 1.8 may still be
29+
exploitable using the init_with() method, but this has not been demonstrated.
30+
2631
},
2732
'Author' =>
2833
[
2934
'charliesome', # PoC
30-
'espes', # PoC and Metasploit module
31-
'lian', # Identified the RouteSet::NamedRouteCollection vector
32-
'hdm' # Module merge/conversion/payload work
35+
'espes', # PoC and Metasploit module
36+
'lian', # Identified the RouteSet::NamedRouteCollection vector
37+
'hdm' # Module merge/conversion/payload work
3338
],
3439
'License' => MSF_LICENSE,
3540
'References' =>

0 commit comments

Comments
 (0)