| DetailsAffected Software:PunBB Fixed in Version:1.3 Issue Type:SQL Injection (SQLi) Original Code:Found Here DescriptionThis week’s bug was an old SQL injection bug that affected PunBB versions <1.3. In short,a value is taken from an attacker/user controlled POST request and is used to build a SQL statement. This bug actually requires a small amount of tracing,so here we go! First,we see that PunBB takes the attacker/user supplied content here (line 11) $form = array_map('trim',$_POST['form']);The line above uses the value passed via $_POST[‘form’] to populate the $form variable. The value goes through a trim() function,but is (for the most part) un-sanitized. It’s interesting that PHP allows for the submission of arrays through POST parameters. This behavior is mentioned in the comments on this page Next,the $form variable (which contains our attacker supplied values from $_POST[‘form’]) is used in a foreach statement and each index of the $form variable value is used in some application logic. You can see this in the following line (line 19) foreach ($form as $key =>$input) The foreach extracts the various values from the $form variable,does a quick comparison to a configuration value of some sort. If the comparison returns the correct value,the application uses the tainted value to populate a $query array variable. The tainted value is used here (line 26) 'SET'=>'conf_value='.$input, The name of the variable ($query),along with the names of the indexes in the array (UPDATE,SET,WHERE),and finally the names of variables/functions close-by ($forum_db,query_build) are dead giveaways that the untainted value will eventually be used in a SQL query. Use of a tainted value in this manner leads to SQL injection. The developers addressed this issue by casting the tainted $input value to an int before using it to build a SQL statement. Developers Solution...<snip>...// Load the admin.php language filerequire FORUM_ROOT.'lang/'.$forum_user['language'].'/admin_common.php';require FORUM_ROOT.'lang/'.$forum_user['language'].'/admin_settings.php';$section = isset($_GET['section']) ? $_GET['section']:null;if (isset($_POST['form_sent'])){$form = array_map('trim',$_POST['form']);($hook = get_hook('aop_form_submitted')) ? eval($hook):null;...<snip>...($hook = get_hook('aop_pre_update_configuration')) ? eval($hook):null;foreach ($form as $key =>$input){// Only update permission values that have changedif (array_key_exists('p_'.$key,$forum_config) &&$forum_config['p_'.$key] != $input){$query = array('UPDATE'=>'config',-'SET'=>'conf_value='.$input,+'SET' =>'conf_value='.intval($input),'WHERE'=>'conf_name=\'p_'.$forum_db->escape($key).'\'');($hook = get_hook('aop_qr_update_permission_conf')) ? eval($hook):null;$forum_db->query_build($query) or error(__FILE__,__LINE__)}// Only update option values that have changedif (array_key_exists('o_'.$key,$forum_config) &&$forum_config['o_'.$key] != $input){if ($input != '' || is_int($input))$value = '\''.$forum_db->escape($input).'\'';else$value = 'NULL';$query = array('UPDATE'=>'config','SET'=>'conf_value='.$value,'WHERE'=>'conf_name=\'o_'.$forum_db->escape($key).'\'');($hook = get_hook('aop_qr_update_permission_option')) ? eval($hook):null;$forum_db->query_build($query) or error(__FILE__,__LINE__)}}// Regenerate the config cacheif (!defined('FORUM_CACHE_FUNCTIONS_LOADED'))require FORUM_ROOT.'include/cache.php';generate_config_cache();($hook = get_hook('aop_pre_redirect')) ? eval($hook):null;redirect(forum_link($forum_url['admin_settings_'.$section]),$lang_admin_settings['Settings updated'].' '.$lang_admin_common['Redirect'])}if (!$section || $section == 'setup'){// Setup the form$forum_page['group_count'] = $forum_page['item_count'] = $forum_page['fld_count'] = 0;// Setup breadcrumbs$forum_page['crumbs'] = array(array($forum_config['o_board_title'],forum_link($forum_url['index'])),array($lang_admin_common['Forum administration'],forum_link($forum_url['admin_index'])),array($lang_admin_common['Settings'],forum_link($forum_url['admin_settings_setup'])),array($lang_admin_common['Setup'],forum_link($forum_url['admin_settings_setup'])));($hook = get_hook('aop_setup_pre_header_load')) ? eval($hook):null;define('FORUM_PAGE_SECTION','settings');define('FORUM_PAGE','admin-settings-setup');require FORUM_ROOT.'header.php';// START SUBST - <!-- forum_main -->ob_start();($hook = get_hook('aop_setup_output_start')) ? eval($hook):null;?><div class="main-content main-frm"><form class="frm-form"method="post"accept-charset="utf-8"action="<?php echo forum_link($forum_url['admin_settings_setup']) ?>"><div class="hidden"><input type="hidden"name="csrf_token"value="<?php echo generate_form_token(forum_link($forum_url['admin_settings_setup'])) ?>"/><input type="hidden"name="form_sent"value="1"/></div><div class="content-head"><h2 class="hn"><span><?php echo $lang_admin_settings['Setup personal'] ?></span></h2></div>...<snip>...DetailsAffected Software:Members-only WordPress plugin Fixed in Version:0.6.7 Issue Type:SQL Injection Original Code: Found Here DescriptionThis week’s vuln was an easy one The vulnerability affected the “members-only” plug-in for WordPress and was patched in version 0.6.7. There are a couple changes in the diff,but the most important change (from a security standpoint) is the transition from a string-built,dynamic SQL statement to a prepared statement. The following lines show the SQL injection bug: $feedkey = $_GET['feedkey']; … <snip>… $find_feedkey = $wpdb->get_results(“SELECT umeta_id FROM $wpdb->usermeta WHERE meta_value = ‘$feedkey’”);
The members-only developers assigned a value for the $feedkey variable directly from the querystring. They then used the attacker controlled value to build a SQL statement,using the $feedkey value to complete the SQL WHERE clause. The symptoms presented above are classic SQL injection. I’m happy to see that the members-only developers chose to use prepared statements to fix the SQL injection. There are a few other fixes here,but the SQL injection was the most important security fix. Developers Solution1 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
| -if (empty($userdata->ID) &&$members_only_opt['feed_access'] != 'feednone') //Check if user is logged in or Feed Keys is required +if (empty($userdata->ID) || $members_only_opt['feed_access'] != 'feednone') //Check if user is logged in or Feed Keys is required { $feedkey = $_GET['feedkey']; if (!empty($feedkey)) { // Check if Feed Key is in the Database -$find_feedkey = $wpdb->get_results("SELECT umeta_id FROM $wpdb->usermeta WHERE meta_value = '$feedkey'"); +$find_feedkey = $wpdb->get_results( $wpdb->prepare( + "SELECT umeta_id FROM $wpdb->usermeta WHERE meta_value = %s", + $feedkey +) );
if (!empty($find_feedkey) &&$members_only_opt['feed_access'] == 'feedkeys') //If Feed Key is found and using Feed Keys { $feedkey_valid = TRUE; if (empty($feedkey) &&$members_only_opt['feed_access'] == 'feedkeys') { $feed = members_only_create_feed('No Feed Key Found',$errormsg['feedkey_missing']); -header("Content-Type:application/xml;charset=ISO-8859-1"); +header("Content-Type:application/xml;charset=". get_bloginfo( 'charset' ),true); echo $feed; exit; } elseif ($feedkey_valid == FALSE &&$members_only_opt['feed_access'] == 'feedkeys') { $feed = members_only_create_feed('Feed Key is Invalid',$errormsg['feedkey_invalid']); -header("Content-Type:application/xml;charset=ISO-8859-1"); +header("Content-Type:application/xml;charset=". get_bloginfo( 'charset' ),true); echo $feed; exit; }
if (empty($feedkey) &&$members_only_opt['feed_access'] == 'feedkeys') { $feed = members_only_create_feed('No Feed Key Found',$errormsg['feedkey_missing']); -header("Content-Type:application/xml;charset=ISO-8859-1"); +header("Content-Type:application/xml;charset=". get_bloginfo( 'charset' ),true); echo $feed; exit; } |
DetailsAffected Software:TimesToCome Stop Bot Registration (WordPress Plugin) Fixed in Version:1.9 Issue Type:SQL Injection Original Code: Found Here DescriptionStraight up SQL Injection vulnerability. The following attacker controlled values are taken via the following lines: $request_time = $_SERVER['REQUEST_TIME']; $http_accept = $_SERVER['HTTP_ACCEPT']; $http_user_agent = $_SERVER['HTTP_USER_AGENT']; $http_remote_addr = $_SERVER['REMOTE_ADDR'];
Then,a few lines down the attacker controlled values are used as part of a dynamic INSERT SQL statement. $sql = “INSERT INTO ”. $registration_log_table_name . ”( ip,email,problem,accept,agent,day ) VALUES ( ‘$http_remote_addr’,‘$user’,‘$error’,‘$http_accept’,‘$http_user_agent’,NOW() )”; $result = $wpdb->query( $sql );
Understanding that taking attacker controlled data and using it as part of a dynamic SQL statement is a bad idea,the plug-in developers checked in a patch to sanitize the data before using in the SQL statement. The path is shown below: $http_accept = htmlentities($http_accept); $http_user_agent = htmlentities($http_user_agent); $http_remote_addr = htmlentities($http_remote_addr); $http_request_uri = htmlentities($html_request_uri);
There are a couple curious things about this patch. First,instead of transitioning away from dynamic SQL building (which can be difficult to get just right),the authors decided to sanitize the input before passing it to the dynamic SQL. Second (and more interesting),is the authors used htmlentities() to escape the data. Htmlentities() is typically used to escape data to prevent XSS attacks,not SQL Injection. Htmlentities() takes a few parameters,one of which is the optional $quote_style parameter. The $quote_style parameter defines how strings with double and single quotes will be escaped. According to the PHP documentation the three options are: ENT_COMPAT - Will convert double-quotes and leave single-quotes alone. ENT_QUOTES - Will convert both double and single quotes. ENT_NOQUOTES –Will leave both double and single quotes unconverted
If the $quote_style is not specified,PHP will default to ENT_COMPAT. Do you think this patch will hold up to the test of time? Developers Solution1 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
| // log all requests to register on our blog function ttc_add_to_log( $user,$error) {
global $wpdb; $registration_log_table_name = $wpdb->prefix . "ttc_user_registration_log"; $request_time = $_SERVER['REQUEST_TIME']; $http_accept = $_SERVER['HTTP_ACCEPT']; $http_user_agent = $_SERVER['HTTP_USER_AGENT']; $http_remote_addr = $_SERVER['REMOTE_ADDR'];
if($wpdb->get_var("show tables like '$registration_log_table_name'") != $registration_log_table_name) {ttc_wp_user_registration_install(); }
// wtf? accept statements coming in at over 255 chars? Prevent sql errors and any funny business // by shortening anything from user to 200 chars if over 255 if ( strlen($email) >200 ){ $email = substr ($email,0,200 );} if ( strlen($http_accept ) >200 ) { $http_accept = substr ( $http_accept,0,200 );} if ( strlen($http_user_agent ) >200 ) { $http_user_agent = substr ( $http_user_agent,0,200 );}
+// clean input for database +$http_accept = htmlentities($http_accept); +$http_user_agent = htmlentities($http_user_agent); +$http_remote_addr = htmlentities($http_remote_addr); +$http_request_uri = htmlentities($html_request_uri);
$sql = "INSERT INTO ". $registration_log_table_name . "( ip,email,problem,accept,agent,day ) VALUES ( '$http_remote_addr','$user','$error','$http_accept','$http_user_agent',NOW() )"; $result = $wpdb->query( $sql ); }
// add an email to our email blacklist if we decide it is an bot function ttc_add_to_blacklist( $email ) { global $wpdb; $blacklist_table_name = $wpdb->prefix . "ttc_user_registration_blacklist";
if($wpdb->get_var("show tables like '$blacklist_table_name'") != $blacklist_table_name) { ttc_wp_user_registration_install(); }
if ( strlen($email) >200 ){ $email = substr ($email,0,200 );}
$sql = "INSERT INTO ". $blacklist_table_name . "( blacklisted ) VALUES ( '$email' )"; $result = $wpdb->query( $sql );
}
// add an ip to our ip blacklist if we decide it is a bot function ttc_add_to_ip_blacklist( $ip ) { global $wpdb; $ip_table_name = $wpdb->prefix . "ttc_ip_blacklist";
if($wpdb->get_var("show tables like '$ip_table_name'") != $ip_table_name) { ttc_wp_user_registration_install(); }
$sql = "INSERT INTO ". $ip_table_name . "( ip ) VALUES ( '$ip' )"; $result = $wpdb->query( $sql ); } |
DetailsAffected Software:The Hacker’s Diet (WordPress Plugin) Fixed in Version:0.9.7b Issue Type:SQL Injection Original Code: Found Here DescriptionThis week’s vulnerability was a SQL injection vulnerability affecting the Hacker’s Diet WordPress plugin. In the vulnerable version,the plugin assigns several variables using values obtained directly from the querystring. The variable assignments are shown below: $weeks = $_GET["weeks"]; $start_date = $_GET["start_date"]; $end_date = $_GET["end_date"]; $goal = $_GET["goal"]; $user_id = $_GET["user"]; $maint_mode = $_GET["maint_mode"];
No sanitization or validation is done before assigning the values. Once the assignments are made,the attacker controlled values are then passed to a dynamic SQL string here resulting in SQL Injection: $query = “select date,weight,trend from “.$table_prefix.”hackdiet_weightlog where wp_id = $user_id and date >\”".date(“Y-m-d”,strtotime(“$weeks weeks ago”)).”\”order by date asc”; $query = “select date,weight,trend from “.$table_prefix.”hackdiet_weightlog where wp_id = $user_id and date >= \”$start_date\”and date <= \”$end_date\”order by date asc”;
The plugin authors patched this vulnerability by validating that the $_GET[“user”] and $_GET[“weeks”] parameters contains only numeric characters. An interesting exercise would be to trace through the code and find where the following variables are being used: $start_date = $_GET["start_date"]; $end_date = $_GET["end_date"]; $goal = $_GET["goal"]; $maint_mode = $_GET["maint_mode"];
Developers Solution1 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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| <?php include (dirname(__FILE__)."/jpgraph/jpgraph.php"); include (dirname(__FILE__)."/jpgraph/jpgraph_line.php"); include (dirname(__FILE__)."/jpgraph/jpgraph_scatter.php");
// get our db settings without loading all of wordpress every save $html = implode('',file("../../../wp-config.php")); $html = str_replace ("require_once","// ",$html); $html = str_replace ("<?php","",$html); $html = str_replace ("?>","",$html); eval($html);
mysql_connect(DB_HOST,DB_USER,DB_PASSWORD); mysql_select_db(DB_NAME);
+if (!is_numeric($_GET["user"]) || !is_numeric($_GET["weeks"])) { + exit; +}
$weeks = $_GET["weeks"]; $start_date = $_GET["start_date"]; $end_date = $_GET["end_date"]; $goal = $_GET["goal"]; $user_id = $_GET["user"]; $maint_mode = $_GET["maint_mode"];
if ($weeks) { - $query = "select date,weight,trend from ".$table_prefix."hackdiet_weightlog where wp_id = $user_id and date >\"".date("Y-m-d",strtotime("$weeks weeks ago"))."\"order by date asc"; + $query = "select date,weight,trend from ".$table_prefix."hackdiet_weightlog where wp_id = \"". $user_id . "\"and date >\"".date("Y-m-d",strtotime("$weeks weeks ago"))."\"order by date asc"; } else if ($start_date and $end_date) { - $query = "select date,weight,trend from ".$table_prefix."hackdiet_weightlog where wp_id = $user_id and date >= \"$start_date\"and date <= \"$end_date\"order by date asc"; + $query = "select date,weight,trend from ".$table_prefix."hackdiet_weightlog where wp_id = \"". $user_id . "\"and date >= \"$start_date\"and date <= \"$end_date\"order by date asc"; }
result = mysql_query($query); if (mysql_num_rows($result)) { if (mysql_num_rows($result) == 1) { // only one day,gotta finagle the display
$row = mysql_fetch_assoc($result);
// fake day before $weight_data[] = 0; if ($goal >0) { $goal_data[] = $goal; } $x_data[] = date("n/j",strtotime("yesterday",strtotime($row["date"])));
// data $weight_data[] = $row["weight"]; if ($goal >0) { $goal_data[] = $goal; } $x_data[] = date("n/j",strtotime($row["date"]));
// fake day after $weight_data[] = 0; if ($goal >0) { $goal_data[] = $goal; } $x_data[] = date("n/j",strtotime("tomorrow",strtotime($row["date"]))); } else { $num_rows = mysql_num_rows($result); if ($num_rows <= 7 * 2) { // 0-2 weeks $ticks = "daily"; } else if ($num_rows <= 31 * 4) { // 2 weeks - 4 months $ticks = "weekly"; } else { // 4 months + $ticks = "monthly"; }
$count = 1; while ($row = mysql_fetch_assoc($result)) { $weight_data[] = $row["weight"]; $trend_data[] = $row["trend"]; if ($goal >0) { $goal_data[] = $goal; } switch ($ticks) { case "weekly": if ($count == 1) { $x_data[] = date("n/j",strtotime($row["date"])); } else { $x_data[] = ""; if ($count == 7) { $count = 0; } } break; case "monthly": if (date("j",strtotime($row["date"])) == "1") { $x_data[] = date("n/j",strtotime($row["date"])); } else { $x_data[] = ""; } break; case "daily": default: $x_data[] = date("n/j",strtotime($row["date"])); break; }
$count++; } } |
DetailsAffected Software:WP Category Manager plugin Fixed in Version:1.3.1.0 Issue Type:Sql Injection Original Code: Found Here DescriptionThis week’s vulnerability affected the WP Category Manager plugin. There were two interesting characteristics I noticed with this code fix. First,while there were a number of SQL injection vulnerabilities fixed with this change list,there were also a large number of non security fixes included in this change list as well. It’s generally a good idea to keep security change lists separate from other change lists. The number of non security fixes included in this particular list was so distracting,I removed them from the post. The SQL injection fixes are pretty straight forward,changing dynamically built SQL statements into WordPress’ built-in $wpdb->prepare() function. The second characteristic that caught my attention was usage of numeric IDs at the end of SQL statements. For example: where object_id = $postId and term_taxonomy_id= $categoryId”;
This syntax creates a condition in which the typical addslashes() used to protect against SQL injection can be bypassed. For example,an attacker could craft a SQL injection string like: Sqli.php?categoryId=-1 union select 1,2,3,4,5–
As you can see,the injection string above contains no special characters that would be escaped by addslashes(). Fortunately,the Category Manager plugin developers chose to utilze $wpdb->prepare() instead of addslashes(). Developers Solution1 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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
| <?php
include_once('wpcm-options.php');
if( ! class_exists('wpcm_functions')):
class wpcm_functions { public static function remove_category($postId,$categoryId) { - global $wpdb; - $wpdb->show_errors(); - $queryStr = "DELETE FROM $wpdb->term_relationships - where object_id = $postId and term_taxonomy_id= $categoryId"; + echo $postId;
- $wpdb->query($queryStr); + if(is_int(intval($postId))) + { + global $wpdb; + + $wpdb->show_errors(); + + $queryStr = $wpdb->prepare("DELETE FROM $wpdb->term_relationships where object_id = %d and term_taxonomy_id= %s",$postId,$categoryId); + $wpdb->query($queryStr); + } }
public static function get_posts($category,$page) { global $wpdb; $wpdb->show_errors();
// First figure out how many posts to show per page. This will be your limit $pageSize = wpcm_options::get_option('postsperpage');
$finalQueryLine = '';
if($pageSize != -1) { // Next figure out how many posts to skip. This will be your offset $offset = $pageSize * $page;
$finalQueryLine = "limit ". $pageSize . "offset ". $offset;
+ } + + $querystr = $wpdb->prepare("select wposts.*,wp_term_taxonomy.term_taxonomy_id + from $wpdb->posts wposts + LEFT JOIN $wpdb->term_relationships wp_term_relationships ON wposts.ID = wp_term_relationships.object_id + LEFT JOIN $wpdb->term_taxonomy wp_term_taxonomy ON wp_term_relationships.term_taxonomy_id = wp_term_taxonomy.term_taxonomy_id + LEFT JOIN $wpdb->terms wp_terms ON wp_terms.term_id = wp_term_taxonomy.term_id + WHERE wp_term_taxonomy.taxonomy = 'category' + and wp_terms.name = '%s' + and wposts.post_status='publish' + ORDER BY wposts.ID ". $finalQueryLine ,$category); + + $postlist = $wpdb->get_results($querystr); + return $postlist; } - $querystr = "select wposts.*,wp_term_taxonomy.term_taxonomy_id - from $wpdb->posts wposts - LEFT JOIN $wpdb->term_relationships wp_term_relationships ON wposts.ID = wp_term_relationships.object_id - LEFT JOIN $wpdb->term_taxonomy wp_term_taxonomy ON wp_term_relationships.term_taxonomy_id = wp_term_taxonomy.term_taxonomy_id - LEFT JOIN $wpdb->terms wp_terms ON wp_terms.term_id = wp_term_taxonomy.term_id - WHERE wp_term_taxonomy.taxonomy = 'category' - and wp_terms.name = '". $category . "' - and wposts.post_status='publish' - ORDER BY wposts.ID ". $finalQueryLine; - - $postlist = $wpdb->get_results($querystr); - return $postlist; }
public static function get_postCount($category) { global $wpdb; $wpdb->show_errors();
- $querystr = "select count(*) - from $wpdb->posts wposts - LEFT JOIN $wpdb->term_relationships wp_term_relationships ON wposts.ID = wp_term_relationships.object_id - LEFT JOIN $wpdb->term_taxonomy wp_term_taxonomy ON wp_term_relationships.term_taxonomy_id = wp_term_taxonomy.term_taxonomy_id - LEFT JOIN $wpdb->terms wp_terms ON wp_terms.term_id = wp_term_taxonomy.term_id - WHERE wp_term_taxonomy.taxonomy = 'category' - and wp_terms.name = '". $category . "' - and wposts.post_status='publish'"; + $querystr = $wpdb->prepare("select count(*) + from $wpdb->posts wposts + LEFT JOIN $wpdb->term_relationships wp_term_relationships ON wposts.ID = wp_term_relationships.object_id + LEFT JOIN $wpdb->term_taxonomy wp_term_taxonomy ON wp_term_relationships.term_taxonomy_id = wp_term_taxonomy.term_taxonomy_id + LEFT JOIN $wpdb->terms wp_terms ON wp_terms.term_id = wp_term_taxonomy.term_id + WHERE wp_term_taxonomy.taxonomy = 'category' + and wp_terms.name = '%s' + and wposts.post_status='publish'",$category);
$result = $wpdb->get_var($querystr,0,0); return $result;
}
public static function render_posts($postlist) { if($postlist) { foreach($postlist as $post) { echo '<div id="catmanagerpost'. $post->ID .'">'; echo '<span ><a href="'. get_permalink($post->ID) .'"title="'.$post->post_title . '">' . $post->post_title . '</a></span><span >' . date_format(date_create($post->post_date),"F j,Y") . '</span>'; echo '<p ><a href="javascript:void(0);"postID="'.$post->ID.'"catID="'. $post->term_taxonomy_id .'"id="catmanremovepost'. $post->ID .'"title="Remove post from this category">Remove</a>| '; echo edit_post_link('Edit Post','','',$post->ID); echo '</p></div>'; } } else { echo '<strong>No posts found</strong>'; } }
public static function render_postcount($category) { $count = wpcm_functions::get_postCount($category);
echo '<span id="wpcmpostcount">'.$count.'</span>'; }
public static function get_categories() { global $wpdb;
$wpdb->show_errors();
$querystr = "select wt.name,wt.term_id from $wpdb->terms wt join $wpdb->term_taxonomy wtt on wtt.term_id = wt.term_id where wtt.taxonomy = 'category' order by wt.name";
$catlist = $wpdb->get_results($querystr); return $catlist; } } endif;
?> |
DetailsAffected Software:Login LockDown for WordPress Fixed in Version: 1.5 Issue Type:SQL Injection Original Code: Found Here DescriptionThis week’s code sample comes from the “Login Lockdown” plug-in for WordPress. It’s always interesting when “security” software ends up having serious security flaws…. This patch contained several bug fixes. The first bug fix we see in the patch is the inclusion of nonce checking to prevent CSRF. It’s difficult to detect CSRF vulnerabilities by looking at individual function logic and it’s ok if the reader missed these bugs. CSRF token validation should be done at the framework level and including CSRF nonce validation in the logic of every function can quickly become unwieldy. If an application wide CSRF solution cannot be implemented at the framework level,then auditing for CSRF must be done at a function by function level. Personally,I prefer to check for CSRF vulnerabilities by identifying any function that performs a Create,Update,or Delete operation,mapping those functions back to the HTML markup and checking the markup to see if a nonce is passed as part of the POST or GET request. This is of course is done after an extensive audit of the nonce validation code. The bugs that should have been spotted by the spotthevuln reader are the SQL injection and the XSS vulnerabilities in the code. The SQL Injection is pretty straight forward. The “releaseme” POST parameter is taken and is eventually passed directly to a dynamically built SQL statement without any sanitization. The developers fixed the vulnerability by utilizing the WordPress escape logic. Finally,the last line of the code snippet actually contained an XSS vulnerability,echoing a $_SERVER variable without any sanitization. Developers Solution1 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
| function print_loginlockdownAdminPage() { global $wpdb; $table_name = $wpdb->prefix . "lockdowns"; $loginlockdownAdminOptions = get_loginlockdownOptions();
if (isset($_POST['update_loginlockdownSettings'])) {
+ //wp_nonce check + check_admin_referer('login-lockdown_update-options');
if (isset($_POST['ll_max_login_retries'])) { $loginlockdownAdminOptions['max_login_retries'] = $_POST['ll_max_login_retries']; } if (isset($_POST['ll_retries_within'])) { $loginlockdownAdminOptions['retries_within'] = $_POST['ll_retries_within']; } if (isset($_POST['ll_lockout_length'])) { $loginlockdownAdminOptions['lockout_length'] = $_POST['ll_lockout_length']; } if (isset($_POST['ll_lockout_invalid_usernames'])) { $loginlockdownAdminOptions['lockout_invalid_usernames'] = $_POST['ll_lockout_invalid_usernames']; } if (isset($_POST['ll_mask_login_errors'])) { $loginlockdownAdminOptions['mask_login_errors'] = $_POST['ll_mask_login_errors']; } update_option("loginlockdownAdminOptions",$loginlockdownAdminOptions); ?> <div><p><strong><?php _e("Settings Updated.","loginlockdown");?></strong></p></div> <?php } if (isset($_POST['release_lockdowns'])) {
+ //wp_nonce check + check_admin_referer('login-lockdown_release-lockdowns'); if (isset($_POST['releaseme'])) { $released = $_POST['releaseme']; foreach ( $released as $release_id ) { $results = $wpdb->query("UPDATE $table_name SET release_date = now() ". - "WHERE lockdown_ID = $release_id"); + "WHERE lockdown_ID = ". $wpdb->escape($release_id) . ""); } } update_option("loginlockdownAdminOptions",$loginlockdownAdminOptions); ?> <div><p><strong><?php _e("Lockdowns Released.","loginlockdown");?></strong></p></div> <?php } $dalist = listLockedDown(); ?> <div> -<form method="post"action="<?php echo $_SERVER["REQUEST_URI"];?>"> +<form method="post"action="<?php echo esc_attr($_SERVER["REQUEST_URI"]);?>"> |
DetailsAffected Software: Short URL Plugin Fixed in Version: 2.0 Issue Type:SQL Injection Original Code: Found Here DescriptionThis was a vulnerability that affected the Short URL WordPress plugin. The vulnerability is very straightforward and should have been easily detected by a security code reviewer. The vulnerable code section takes attacker controlled data directly from $_POST[‘form_url’],$_POST[‘form_desc’],and $_POST[‘id’] and uses the tainted value immediately in dynamically built SQL statements. One interesting piece of this particular code fix is that the developers chose to implement the code fixes near the assignment of the variable (as opposed to near consumption,in the SQL statement). Another interesting piece of the code fix is the logic for the following conditional: if($action == “delete”){
looks like the devs may have forgotten something Developers Solution1 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
| <?php 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!"; } } ?> <div> <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="<a href="http://www.harleyquine.com/php-scripts/short-url-plugin/%22%3Eplugin">http://www.harleyquine.com/php-scripts/short-url-plugin/">plugin</a>page</a>for more information about this plugin.</p>
<h2>Current Redirects</h2> <table> <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"> ?> |
DetailsAffected Software:WordPress Plugin Login LockDown Fixed in Version:1.5 Issue Type:SQL Injection and XSS Original Code: Found Here DescriptionIn a SpotTheVuln first,today’s example actually contains two different vulnerabilities. The sample comes from a WordPress plugin called “Login Lockdown”. The first vulnerability patched by the Login Lockdown developers is a SQL Injection vulnerability. In this particular example,we see that the “$release_id” variable is passed directly to a SQL statement without being sanitized. The Login Lockdown developers corrected this vulnerability by simply escaping the variable before including it in a dynamically built SQL statement. The other vulnerabilities are XSS vulnerabilities. The beginning of the code sample contains several “isset” statements which eventually assign the values from various POST parameters to php variables. These variables are then used to build the HTML markup later in the PHP code. The Login Lockdown developers corrected this vulnerability by using the “esc_attr” before using the user controlled data in the HTML markup. Developers Solution1 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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| <div id="_mcePaste">
<?php
function print_loginlockdownAdminPage() {
global $wpdb;
$table_name = $wpdb->prefix . "lockdowns";
$loginlockdownAdminOptions = get_loginlockdownOptions();
if (isset($_POST['update_loginlockdownSettings'])) {
if (isset($_POST['ll_max_login_retries'])) {
$loginlockdownAdminOptions['max_login_retries'] = $_POST['ll_max_login_retries'];
}
if (isset($_POST['ll_retries_within'])) {
$loginlockdownAdminOptions['retries_within'] = $_POST['ll_retries_within'];
}
if (isset($_POST['ll_lockout_length'])) {
$loginlockdownAdminOptions['lockout_length'] = $_POST['ll_lockout_length'];
}
if (isset($_POST['ll_lockout_invalid_usernames'])) {
$loginlockdownAdminOptions['lockout_invalid_usernames'] = $_POST['ll_lockout_invalid_usernames'];
}
if (isset($_POST['ll_mask_login_errors'])) {
$loginlockdownAdminOptions['mask_login_errors'] = $_POST['ll_mask_login_errors'];
}
update_option("loginlockdownAdminOptions",$loginlockdownAdminOptions);
?>
<div class="updated"><p><strong><?php _e("Settings Updated.","loginlockdown");?></strong></p></div>
<?php
}
if (isset($_POST['release_lockdowns'])) {
if (isset($_POST['releaseme'])) {
$released = $_POST['releaseme'];
foreach ( $released as $release_id ) {
$results = $wpdb->query("UPDATE $table_name SET release_date = now() ".
- "WHERE lockdown_ID = $release_id");
+ "WHERE lockdown_ID = ". $wpdb->escape($release_id) . "");
}
}
update_option("loginlockdownAdminOptions",$loginlockdownAdminOptions);
?>
<div class="updated"><p><strong><?php _e("Lockdowns Released.","loginlockdown");?></strong></p></div>
<?php
}
$dalist = listLockedDown();
?>
<div>
<form method="post"action="<?php echo $_SERVER["REQUEST_URI"];?>">
<h2><?php _e('Login LockDown Options','loginlockdown') ?></h2>
<h3><?php _e('Max Login Retries','loginlockdown') ?></h3>
-<input type="text"name="ll_max_login_retries"size="8"value="<?php echo $loginlockdownAdminOptions['max_login_retries'];?>">
+<input type="text"name="ll_max_login_retries"size="8"value="<?php echo esc_attr($loginlockdownAdminOptions['max_login_retries']);?>">
<h3><?php _e('Retry Time Period Restriction (minutes)','loginlockdown') ?></h3>
-<input type="text"name="ll_retries_within"size="8"value="<?php echo $loginlockdownAdminOptions['retries_within'];?>">
+<input type="text"name="ll_retries_within"size="8"value="<?php echo esc_attr($loginlockdownAdminOptions['retries_within']);?>">
<h3><?php _e('Lockout Length (minutes)','loginlockdown') ?></h3>
-<input type="text"name="ll_lockout_length"size="8"value="<?php echo $loginlockdownAdminOptions['lockout_length'];?>">
+<input type="text"name="ll_lockout_length"size="8"value="<?php echo esc_attr($loginlockdownAdminOptions['lockout_length']);?>">
</div> |
DetailsAffected Software:WordPress Fixed in Version:2.1 Issue Type:Defense in Depth Original Code: Found Here DescriptionI found this issue interesting for a couple reasons. Upon first glance,the patch appears to be a defense against SQL Injection and in essence,it is. It seems that the $q[‘cat’] value is controlled by the user and is eventually used to help build a SQL statement. Before the $q[‘cat’] value is used in a SQL statement,it is actually sanitized by the following lines: $q['cat'] = ”.urldecode($q['cat']).”; $q['cat'] = addslashes_gpc($q['cat']);
Once the value is sanitized,it is used to build various SQL statements. Now this particular patch was developed by the WordPress team because they discovered that when a user/attacker passes a “.” (period character) to $q[‘cat’],it would cause a SQL error which would be displayed to the user. While a single period character doesn’t give the attacker the ability to execute arbitrary SQL,it does give the attacker an information disclosure bug. In an academic sense however,the attacker has convinced the database that their provided value should be interpreted as code as opposed to data (ala SQL Injection). The reason the period character slips through is because it is not defined as a special character in the addslashes() php function… this could be useful in other situations. The WordPress prevented the information leak by checking to see if $q[‘cat’] is an integer value. The patch here is a single line fix. Developers Solution1 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
| // Category stuff
if ((empty($q['cat'])) || ($q['cat'] == '0') ||
// Bypass cat checks if fetching specific posts
( $this->is_single || $this->is_page )) {
$whichcat='';
} else {
$q['cat'] = ''.urldecode($q['cat']).'';
$q['cat'] = addslashes_gpc($q['cat']);
$join = "LEFT JOIN $wpdb->post2cat ON ($wpdb->posts.ID = $wpdb->post2cat.post_id) ";
$cat_array = preg_split('/[,\s]+/',$q['cat']);
$in_cats = $out_cats = '';
foreach ( $cat_array as $cat ) {
+ $cat = intval($cat);
$in = strstr($cat,'-') ? false:true;
$cat = trim($cat,'-');
if ( $in )
$in_cats .= "$cat,". get_category_children($cat,'',',');
else
$out_cats .= "$cat,". get_category_children($cat,'',',');
}
$in_cats = substr($in_cats,0,-2);
$out_cats = substr($out_cats,0,-2);
if ( strlen($in_cats) >0 )
$in_cats = "AND category_id IN ($in_cats)";
if ( strlen($out_cats) >0 )
$out_cats = "AND category_id NOT IN ($out_cats)";
$whichcat = $in_cats . $out_cats;
$distinct = 'DISTINCT';
}
// Category stuff for nice URIs
global $cache_categories;
if ('' != $q['category_name']) { |
DetailsAffected Software:OSCommerce2 Fixed in Version: Issue Type:SQL Injection Original Code: Found Here DescriptionImplementation bug here,leading to SQL injection. The primary change involves the changing of a regular expression. The initial check seemed be an attempt to validate data,but was too loose. The data that slipped through validation eventually makes its way into a dynamically built SQL statement,resulting in SQL injection. The Regular Expression is tightened up and some of the SQL statement was changed. It’s interesting that the developers chose to implement better regular expression checking instead of trying to move this particular SQL statement to a prepared statement. Developers Solution1 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 82 83 84 85 86 87 88 89 90 91 92 93
| // show the products of a specified manufacturer
if (isset($HTTP_GET_VARS['manufacturers_id'])) {
if (isset($HTTP_GET_VARS['filter_id']) &&tep_not_null($HTTP_GET_VARS['filter_id'])) {
// We are asked to show only a specific category
$listing_sql = "select ". $select_column_list . "p.products_id,p.manufacturers_id,p.products_price,p.products_tax_class_id,IF(s.status,s.specials_new_products_price,NULL) as specials_new_products_price,IF(s.status,s.specials_new_products_price,p.products_price) as final_price from ". TABLE_PRODUCTS . "p left join ". TABLE_SPECIALS . "s on p.products_id = s.products_id,". TABLE_PRODUCTS_DESCRIPTION . "pd,". TABLE_MANUFACTURERS . "m,". TABLE_PRODUCTS_TO_CATEGORIES . "p2c where p.products_status = '1' and p.manufacturers_id = m.manufacturers_id and m.manufacturers_id = '". (int)$HTTP_GET_VARS['manufacturers_id'] . "' and p.products_id = p2c.products_id and pd.products_id = p2c.products_id and pd.language_id = '". (int)$languages_id . "' and p2c.categories_id = '". (int)$HTTP_GET_VARS['filter_id'] . "'";
} else {
// We show them all
$listing_sql = "select ". $select_column_list . "p.products_id,p.manufacturers_id,p.products_price,p.products_tax_class_id,IF(s.status,s.specials_new_products_price,NULL) as specials_new_products_price,IF(s.status,s.specials_new_products_price,p.products_price) as final_price from ". TABLE_PRODUCTS . "p left join ". TABLE_SPECIALS . "s on p.products_id = s.products_id,". TABLE_PRODUCTS_DESCRIPTION . "pd,". TABLE_MANUFACTURERS . "m where p.products_status = '1' and pd.products_id = p.products_id and pd.language_id = '". (int)$languages_id . "' and p.manufacturers_id = m.manufacturers_id and m.manufacturers_id = '". (int)$HTTP_GET_VARS['manufacturers_id'] . "'";
}
} else {
// show the products in a given categorie
if (isset($HTTP_GET_VARS['filter_id']) &&tep_not_null($HTTP_GET_VARS['filter_id'])) {
// We are asked to show only specific catgeory
$listing_sql = "select ". $select_column_list . "p.products_id,p.manufacturers_id,p.products_price,p.products_tax_class_id,IF(s.status,s.specials_new_products_price,NULL) as specials_new_products_price,IF(s.status,s.specials_new_products_price,p.products_price) as final_price from ". TABLE_PRODUCTS . "p left join ". TABLE_SPECIALS . "s on p.products_id = s.products_id,". TABLE_PRODUCTS_DESCRIPTION . "pd,". TABLE_MANUFACTURERS . "m,". TABLE_PRODUCTS_TO_CATEGORIES . "p2c where p.products_status = '1' and p.manufacturers_id = m.manufacturers_id and m.manufacturers_id = '". (int)$HTTP_GET_VARS['filter_id'] . "' and p.products_id = p2c.products_id and pd.products_id = p2c.products_id and pd.language_id = '". (int)$languages_id . "' and p2c.categories_id = '". (int)$current_category_id . "'";
} else {
// We show them all
$listing_sql = "select ". $select_column_list . "p.products_id,p.manufacturers_id,p.products_price,p.products_tax_class_id,IF(s.status,s.specials_new_products_price,NULL) as specials_new_products_price,IF(s.status,s.specials_new_products_price,p.products_price) as final_price from ". TABLE_PRODUCTS_DESCRIPTION . "pd,". TABLE_PRODUCTS . "p left join ". TABLE_MANUFACTURERS . "m on p.manufacturers_id = m.manufacturers_id left join ". TABLE_SPECIALS . "s on p.products_id = s.products_id,". TABLE_PRODUCTS_TO_CATEGORIES . "p2c where p.products_status = '1' and p.products_id = p2c.products_id and pd.products_id = p2c.products_id and pd.language_id = '". (int)$languages_id . "' and p2c.categories_id = '". (int)$current_category_id . "'";
}
}
- if ( (!isset($HTTP_GET_VARS['sort'])) || (!ereg('[1-8][ad]',$HTTP_GET_VARS['sort'])) || (substr($HTTP_GET_VARS['sort'],0,1) >sizeof($column_list)) ) {
+ if ( (!isset($HTTP_GET_VARS['sort'])) || (!preg_match('/^[1-8][ad]$/',$HTTP_GET_VARS['sort'])) || (substr($HTTP_GET_VARS['sort'],0,1) >sizeof($column_list)) ) {
for ($i=0,$n=sizeof($column_list);$i<$n;$i++) {
if ($column_list[$i] == 'PRODUCT_LIST_NAME') {
$HTTP_GET_VARS['sort'] = $i+1 . 'a';
+ $listing_sql .= "order by pd.products_name";
+ break;
+ }
+ }
} else {
$sort_col = substr($HTTP_GET_VARS['sort'],0 ,1);
$sort_order = substr($HTTP_GET_VARS['sort'],1);
- $listing_sql .= ' order by ';
switch ($column_list[$sort_col-1]) {
case 'PRODUCT_LIST_MODEL':
+ $listing_sql .= "order by p.products_model ". ($sort_order == 'd' ? 'desc':'') . ",pd.products_name";
- $listing_sql .= "p.products_model ". ($sort_order == 'd' ? 'desc':'') . ",pd.products_name";
break;
case 'PRODUCT_LIST_NAME':
$listing_sql .= "order by pd.products_name ". ($sort_order == 'd' ? 'desc':'');
break;
case 'PRODUCT_LIST_MANUFACTURER':
$listing_sql .= "order by m.manufacturers_name ". ($sort_order == 'd' ? 'desc':'') . ",pd.products_name";
break;
case 'PRODUCT_LIST_QUANTITY':
$listing_sql .= "order by p.products_quantity ". ($sort_order == 'd' ? 'desc':'') . ",pd.products_name";
break;
case 'PRODUCT_LIST_IMAGE': |
| |