AppSec Blog

Spot the Vuln - Vegetables - SQL Injection


Affected Software: Short URL Plugin

Fixed in Version: Changeset 55280

Issue Type: SQL Injection

Original Code: Found Here


This weeks' vulnerabilities were a couple of SQL injection bugs in the Short URL Plugin for WordPress. The symptoms for the issues indicate classic SQL injection, let's have a quick look at the code. First, looking over the code sample, we see a couple of dynamically built SQL statements. It would probably make sense to spend a bit of time and convert these dynamic SQL statements into prepared statements, that way you won't have to worry about a code change inadvertently re-introducing a SQL injection flaw or an escaping filter bypass. With dynamically built SQL statements we'll also have to trace each variable until we can determine whether the value can be controlled by an attacker. Lucky for us, the variable assignments are very close to the SQL statements. In the vulnerable sample, we see that the author is taking values directly from a POST request and using those tainted values to build SQL statements. Looking at the check-in, we see that the developer chose to use WordPress' built-in escaping function for escaping user/attacker controlled data before passing it to a SQL statement.

Although the checked-in fixes were straightforward, I was surprised to see that the developers missed an obvious SQL injection on line 56. Same classic SQL injection symptoms, the only difference is the dynamic SQL being built is a DELETE SQL statement as opposed to an INSERT or UPDATE. For those that are wondering? YES, this SQL injection is still present in the latest version of the plug-in! If you happen to be using this plug-in on your website, I would recommend you escape $delete_id before passing it to a SQL statement! I notified the plug-in author, hopefully they'll be a patch soon.

Is this the first 0day?

Developers Solution

<?php ...snip... function kd_admin_options_su(){ global $table_prefix, $wpdb, $user_ID; $table_name = $table_prefix . "short_url"; if($wpdb->get_var("show tables like '$table_name'") != $table_name){ $sql = "CREATE TABLE ".$table_name." ( link_id int(11) NOT NULL auto_increment, link_url text NOT NULL, link_desc text NOT NULL, link_count int(11) NOT NULL default '0', PRIMARY KEY (`link_id`) );"; require_once(ABSPATH . 'wp-admin/upgrade-functions.php'); dbDelta($sql); } if(isset($_POST['action'])) { $action = $_POST['action']; if($action == "create"){ - $add_url = $_POST['form_url']; - $add_desc = $_POST['form_desc']; + $add_url = $wpdb->escape($_POST['form_url']); + $add_desc = $wpdb->escape($_POST['form_desc']); if($add_url == "http://" || (!$add_url)){ $ERR = $ERR . "<br>You must enter a URL to redirect to!"; } if(!$ERR){ $wpdb->query("INSERT INTO $table_name (link_url,link_desc) VALUES ('$add_url','$add_desc')"); $new_url = get_option("siteurl") . "/u/" . mysql_insert_id(); $MES = $MES . "<br>The redirect URL has been added. Your new Short URL is: " . $new_url; } } if($action == "edit"){ - $edit_id = $_POST['id']; - $edit_url = $_POST['form_url']; - $edit_desc = $_POST['form_desc']; + $edit_id = $wpdb->escape($_POST['id']); + $edit_url = $wpdb->escape($_POST['form_url']); + $edit_desc = $wpdb->escape($_POST['form_desc']); if($edit_url == "http://" || (!$edit_url)){ $ERR = $ERR . "<br>You must enter a URL to redirect to!"; } if(!$ERR){ $wpdb->query("UPDATE $table_name SET link_url='$edit_url',link_desc='$edit_desc' WHERE link_id = $edit_id"); $MES = $MES . "<br>The redirect URL has been modified."; } } if($action == "delete"){ $delete_id = $_POST['id']; $wpdb->query("DELETE FROM $table_name WHERE link_id = '$delete_id'"); $MES = $MES . "<br>Redirect deleted!"; } if($action == "clearall"){ $wpdb->query("UPDATE $table_name SET link_count='0' WHERE link_count > 0"); $MES = $MES . "<br>Counts have been reset!"; } } ?> <form method="post"> <h2>Short URL Admin</h2> <?php if($ERR){ echo "<p>" . $ERR . "</p>"; } if($MES){ echo "<p>" . $MES . "</p>"; } ?> <p>Short URL allows you to create shorter URL's and keeps track of how many times a link has been clicked. It's useful for managing downloads, keeping track of outbound links and for masking URL's. Clicking the Clear All Clicks button will reset the count for each entry. Visit the <a href="">plugin page</a> for more information about this plugin.</p> <h2>Current Redirects</h2> <table class="widefat"> <thead> <tr> <th scope="col">Short URL (The URL to use)</th> <th scope="col">Real URL (Where it redirects to)</th> <th scope="col">Notes</th> <th scope="col">Amount of Clicks</th> <th scope="col">Manage</th> </tr> </thead> <tbody id="the-list"> <?php $rowdata = $wpdb->get_results("SELECT * FROM $table_name"); foreach ($rowdata as $row) { $is_editing = $_POST['edit_id']; if($is_editing){ if($is_editing == $row->link_id){ $EDIT = 1; $EDIT_ID = $row->link_id; $EDIT_URL = $row->link_url; $EDIT_DESC = $row->link_desc; } } ?> <tr class='<?php echo $class; ?>'> <th scope="row"><a href="<? echo get_option("siteurl") . "/u/" . $row->link_id; ?>" target="_blank"><? echo get_option("siteurl") . "/u/" . $row->link_id; ?></a></th> <td><? echo $row->link_url; ?></td> <td><? echo $row->link_desc; ?></td> <td><? echo $row->link_count; ?></td> <td><form method="post" name="delete"><input type="hidden" name="action" value="delete"><input type="hidden" name="id" value="<? echo $row->link_id; ?>"><input type="submit" value="Delete"></form><form method="post" name="edit"><input type="hidden" name="edit_id" value="<? echo $row->link_id; ?>"><input type="submit" value="Edit"></form></td> ...snip...

Post a Comment


* Indicates a required field.