Tencent Security Center · 2015/08/18 11:18

This is a bug that has been on my mind for a few days. WordPress released version 4.2.4 last week, which fixes possible SQL vulnerabilities and several XSS. Check Point also sent out an analysis soon, and I will also analyze and reproduce the latest vulnerability.

0x01 Unauthorized Vulnerability caused by GP mixing


First, a little background. WordPress users have subscriber, contributor, author, editor, and administrator rights.

The lowest permission is the subscriber, who can only subscribe to articles. The default registered user is the subscriber after registration is enabled on wordpress. Many well-known Chinese websites, such as Freebuf, allow users to register as “subscribers”.

Let’s start with a suggestion loophole that allows us, as a subscriber, to insert an article into the database beyond our rights.

WordPress checks user permissions by calling the current_user_CAN function, which we see here:

Call the has_cap method, follow up

Follow up map_meta_cap again:

As you can see, this function really checks permissions. The error code is checking ‘edit_POST’ and ‘edit_page’ sections:

$POST = $post = $post = $POST = $POST = $POST = $POST

$post is the ID of the article to edit, that is, if I want to edit an article that doesn’t exist, I return it without checking permissions.

Normally there is no problem, because there is no editing for non-existent articles.

/wp-admin/post.php

$_POST[‘ post_ID ‘] $_POST[‘ post_ID ‘] $_POST[‘ post_ID ‘] $_POST[‘ post_ID ‘]

But the post_ID passed in when we later call current_user_CAN comes from POST:

There is a logical problem here. If we pass the correct POstid in the GET argument (so that get_POST does not generate an error) and a nonexistent POstid in the POST argument, we can bypass the edit_POST permission check step.

But this logic error can’t do serious harm right now, because the code that actually edits the article is in the edit_POST function, and the post_ID is from $_POST.

0x02 Obtain _wpNOnce Bypasses CSRF defense


WordPress defends against CSRF vulnerability by using _wpnonce (also known as token), and its token is very strict. Different operations have different tokens.

For example, if we want to call the edit_POST function, we need the following logic:

When $post_type== ‘postaJAXPOST’, the name of _wpnonce is’ add-postaJAXPOST ‘.

How do I get _wpnonce with the name “add-postaJAXPOST”?

See the position of the point above:

There is a post-Quickdraft-save operation. This operation is used to temporarily store drafts, and whenever the user accesses this operation, a new article with status auto-Draft is inserted into the database POST table.

Since we don’t know the _wpnonce whose name is “add-post”, we go to the wp_dashboard_quick_press function and follow up:

See above. Luckily for wordpress, in this function the _wpnonce is printed in the form itself.

So, as long as we visit post-Quickdraft-save once, we can get add-post’s _wpNonce, bypassing the check_admin_referer function.

0x03 Logic Vulnerability caused by Contention Vulnerability


This section is actually the real heart of the weight lift hole, after we get _wpNonce, we go into the edit_POST function.

Our intention is to update an article, but as stated in 0x01, to bypass the permission check function, we need to pass in a “non-existent” article ID. So even if we could perform an update, it would be impossible to modify an existing article?

What is involved here is a logical hole created by competition. See the edit_POST function code:

The above two diagrams should be intuitive. After the current_USER_CAN mentioned in 0x01 is bypassed, there is a vacuum of execution time between the final execution of the UPDATE statement.

For example, we pass tax_input=1,2,3,4… 10000, so that query will be executed 10000 times, which is a long time to execute. (I tested this statement 10,000 times on my own virtual machine, which took about 5-10 seconds)

So if there are new articles inserted during this period, then our previous “non-existent” ID can exist (just set the ID to the latest article ID +1). But the question is, how do we insert a new article in that time? Because post-Quickdraft-save has already been done in 0x02 to get _wpNonce. Post-quickdraft-save inserts an article with status auto-draft into the database, but each user will insert at most one article.

In the original check-point article, it says that wordpress will delete the post automatically after a week, and _wpnonce will keep it for another day, so that we can insert another post once again after post-Quickdraft-save.

I thought about it on my own, and there’s no need to go to all this trouble. If we can register another account as a subscriber, we can insert another article, so my POC doesn’t need to wait for a week.

The combination of these three loopholes creates an entitlement loophole. To address the entitlement vulnerability described in the first article, I wrote an EXP, which allows the subscriber to insert an article into the trash:

Visit the article editing page to see this article:

0x04 untrash article caused when SQL injection vulnerability


So just a loophole like this doesn’t really make much sense. Check-point’s second article mentioned an SQL injection that was caused by this entitlement vulnerability.

So let’s talk about how injection works.

/wp-includes/post.php
Copy the code

As shown above. Many places in WordPress perform the precompilation used by SQL statements, but only where user input is directly accepted. In the figure above, the meta is retrieved from the database using the get_post_meta function, and then the SQL statement is inserted as a string concatenation.

This place creates a secondary injection.

Let’s take a look at how it was stored the first time. Wp_trash_post removes comments from the post and then calls wp_trash_post_comments:

Wp_trash_post_comments:

As you can see above, the “comment_approved” was actually pulled from the database as well. So this is what I call a triple injection.

So LET me follow up and see where the earliest comment_approved came from.

In fact, as you can see from the SQL statement in the graph, this comment_approved is a field in the comments table. I looked at the new comment function and the edit_comment function, and found that this field is included in the edit_comment function:

So, the end result of this chain of operations is an SQL injection vulnerability.

To summarize 1234, the whole process is as follows:

Insert article with quick draft -> Edit article without authority -> Insert comment -> Modify comment (malicious data into library) -> Delete article (malicious data into another library) -> Undelete article (malicious data was taken out, directly insert SQL statement causing injection)

0x05 The hidden part of the original text and the study of the real utilization process


There is no mention of the _wpNonce token in the second full text of the check-Point article, but almost everything in the wordpress backend requires a specific _wpNonce. In the first step we get the _wpNonce for “Add-PostaJAXPOST” via a leak point, but in fact each of the following steps (add, edit comments, Trash articles, untrash articles) requires a different _wpNonce. How do we get _wpNonce?

After my analysis, I could not find the _wpNonce for “add comment” and “undelete article” under the subscriber permission, while the _wpNonce for “modify comment” and “delete article” could be found in the background.

In addition, although the foreground can also add comments, adding comments to the foreground checks whether the article is a draft and whether the status is public or private, so we cannot add comments to this article and its derived preview articles.

Therefore, I upgraded the authority of the basic account to the author authority that can publish and edit articles, and then reproduced the injection vulnerability.

First post an article and respond to it with a comment:

Let’s revise this comment again:

In the article edit page, find trash’s _wpnonce and trash the article to which the comment belongs:

Find the undeleted _wpnonce and undelete this article from the trash:

SQL > insert into SQL

Finally, LET me summarize this loophole.

This loophole has two core points: one is an overreach loophole caused by a logic error of competition conditions; The second is to exploit a three – time operation that leads to the final SQL injection vulnerability.

These two core technology points are very representative, after the whole study, I have to admire the idea of Cave master and the depth of wordpress research.

But I also regret that I failed to analyze how to inject under the lowest permissions, mainly because the _wpNonce acquisition caused some problems in exploit.

In addition, the SQL injection is a small point, has to be said for the first time in pollution data warehousing, save the data in the database field only 20 characters in length, so resulting in our injection point is a “have the length limit” injection point, for the use of the length limit, I also had not come up with a better way.

Although there is a length limit, because the injection point is in the comment table of the UPDATE statement, this vulnerability can set the comments of an entire site to zero, which can be very harmful for social sites such as Freebuf.

So, my assessment of this SQL injection is that the use is (probably) a bit weak, but the thinking is absolutely first-class and worth learning.