| DetailsAffected Software:WordPress-to-lead for Salesforce CRM Fixed in Version:1.0.5 Issue Type:Cross Site Scripting Original Code: Found Here DescriptionThis week’s vulnerability is an XSS bug in a Salesforce plugin for WordPress. This bug is a bit interesting as it seems the developer attempted to sanitize an attacker controlled variable,but used the incorrect API. Looking at the vulnerable code,we see the following line: return ‘<label for=”‘.$id.’”>’.$label.’:</label><br/><input size=”45″name=”‘.$id.’”value=”‘.stripslashes($options[$id]).’”/><br/><br/>’;
In the line above,we see that $options[$id] is placed into the rendered HTML. $options[$id] appears to be attacker controlled and the developers used the stripslashes() API to sanitize $options[$id] before displaying the value in HTML. Unfortunately,stripslashes() doesn’t help prevent XSS vulnerabilities and created an opportunity for XSS. The developer fixed this vulnerability by using the correct API to sanitize against XSS vulnerability (htmlentities). 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
| <?php if (!class_exists('OV_Plugin_Admin')) { class OV_Plugin_Admin {
var $hook = ''; var $filename = ''; var $longname = ''; var $shortname = ''; var $ozhicon = ''; var $optionname = ''; var $homepage = ''; var $accesslvl = 'manage_options';
function Yoast_Plugin_Admin() { add_action( 'admin_menu',array(&$this,'register_settings_page') ); add_filter( 'plugin_action_links',array(&$this,'add_action_link'),10,2 ); add_filter( 'ozh_adminmenu_icon',array(&$this,'add_ozh_adminmenu_icon' ) );
add_action('admin_print_scripts',array(&$this,'config_page_scripts')); add_action('admin_print_styles',array(&$this,'config_page_styles')); }
function add_ozh_adminmenu_icon( $hook ) { if ($hook == $this->hook) return WP_CONTENT_URL . '/plugins/' . plugin_basename(dirname($filename)). '/'.$this->ozhicon; return $hook; }
function config_page_styles() { if (isset($_GET['page']) &&$_GET['page'] == $this->hook) { wp_enqueue_style('dashboard'); wp_enqueue_style('thickbox'); wp_enqueue_style('global'); wp_enqueue_style('wp-admin'); wp_enqueue_style('ov-admin-css',WP_CONTENT_URL . '/plugins/' . plugin_basename(dirname(__FILE__)). '/yst_plugin_tools.css'); } }
function register_settings_page() { add_options_page($this->longname,$this->shortname,$this->accesslvl,$this->hook,array(&$this,'config_page')); }
function plugin_options_url() { return admin_url( 'options-general.php?page='.$this->hook ); }
function add_action_link( $links,$file ) { static $this_plugin; if( empty($this_plugin) ) $this_plugin = $this->filename; if ( $file == $this_plugin ) { $settings_link = '<a href="' . $this->plugin_options_url() . '">' . __('Settings') . '</a>'; array_unshift( $links,$settings_link ); } return $links; }
function config_page() {
}
function config_page_scripts() { if (isset($_GET['page']) &&$_GET['page'] == $this->hook) { wp_enqueue_script('postbox'); wp_enqueue_script('dashboard'); wp_enqueue_script('thickbox'); wp_enqueue_script('media-upload'); } }
function checkbox($id,$label) { $options = get_option($this->optionname); return '<input type="checkbox"id="'.$id.'"name="'.$id.'"'. checked($options[$id],true,false).'/><label for="'.$id.'">'.$label.'</label><br/>'; }
function textinput($id,$label) { $options = get_option($this->optionname); - return '<label for="'.$id.'">'.$label.':</label><br/><input size="45"type="text"id="'.$id.'"name="'.$id.'"value="'.stripslashes($options[$id]).'"/><br/><br/>'; + return '<label for="'.$id.'">'.$label.':</label><br/><input size="45"type="text"id="'.$id.'"name="'.$id.'"value="'.htmlentities(stripslashes($options[$id])).'"/><br/><br/>'; } |
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:DojoToolkit Fixed in Version:1.4.2 Issue Type:XSS Original Code: Found Here DescriptionThis week’s vulnerability is a DOM based XSS that could be found in a JavaScript file provided by the DojoToolkit. This JavaScript file was included (via script src) in many pages throughout the DojoToolkit,making those pages vulnerable to XSS. Unlike traditional XSS bugs,server side processing is not required for certain types of DOM based XSS. This is an important concept to understand as some code auditors will skip static pages assuming the attacker will not have the ability to control any values used by the page. The bug starts here: if(window.location.href.indexOf(“?”) >-1){
The JavaScript pulls the address of the loaded page and checks to see if the address contains the “?” character. If the “?” character is found,the JavaScript begins parsing and splitting the URI into various arrays. This parsing and splitting is done in the lines provided below: var str = window.location.href.substr(window.location.href.indexOf(“?”)+1).split(/#/); var ary = str[0].split(/&/); for(var i=0;i<ary.length;i++){ var split = ary[i].split(/=/),
The vulnerable assignment occurs here: value = split[1];
The JavaScript above essentially grabs a querystring value (attacker supplied) and assigns it to the “value” variable. Later,the “value” variable is used in several places,for example: dojo.config.locale = locale = value; document.getElementsByTagName(“html”)[0].dir = value; theme = value;
Considering the assignments listed above,we have a couple different variables that are tainted. I’ve highlighted the tainted variables in red. Tracing the “theme” assignment shown above,we see the tainted value being passed to a document.write statement,resulting in XSS. var themeCss = d.moduleUrl(“dijit.themes”,theme+”/”+theme+”.css”); var themeCssRtl = d.moduleUrl(“dijit.themes”,theme+”/”+theme+”_rtl.css”); document.write(‘<link rel=”stylesheet”type=”text/css”href=”‘+themeCss+’”>’); document.write(‘<link rel=”stylesheet”type=”text/css”href=”‘+themeCssRtl+’”>’);
The patch checked in by the DojoToolkit team sanitizes the “value” JavaScript variable by allowing only word characters (^\w). 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
| if(window.location.href.indexOf("?") >-1){ var str = window.location.href.substr(window.location.href.indexOf("?")+1).split(/#/); var ary = str[0].split(/&/); for(var i=0;i<ary.length;i++){ var split = ary[i].split(/=/), key = split[0], -value = split[1]; +value = split[1].replace(/[^\w]/g,"");// replace() to prevent XSS attack switch(key){ case "locale": // locale string | null dojo.config.locale = locale = value; break; case "dir": // rtl | null document.getElementsByTagName("html")[0].dir = value; break; case "theme": // tundra | soria | noir | squid | nihilo | null theme = value; break; case "a11y": if(value){ testMode = "dijit_a11y";} } } }
// always include the default theme files: if(theme || testMode){
if(theme){ var themeCss = d.moduleUrl("dijit.themes",theme+"/"+theme+".css"); var themeCssRtl = d.moduleUrl("dijit.themes",theme+"/"+theme+"_rtl.css"); document.write('<link rel="stylesheet"type="text/css" href="'+themeCss+'">'); document.write('<link rel="stylesheet"type="text/css" href="'+themeCssRtl+'">'); }
if(dojo.config.parseOnLoad){ dojo.config.parseOnLoad = false; dojo.config._deferParsing = true; }
d.addOnLoad(function(){
// set the classes var b = dojo.body(); if(theme){ dojo.removeClass(b,defTheme); if(!d.hasClass(b,theme)){ d.addClass(b,theme);} var n = d.byId("themeStyles"); if(n){ d.destroy(n);} } if(testMode){ d.addClass(b,testMode);} if(dojo.config._deferParsing){ // attempt to elimiate race condition introduced by this // test helper file. 120ms to allow CSS to finish/process? setTimeout(dojo.hitch(d.parser,"parse",b),120); }
}); }
})(); |
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:AskApache Password Protect Fixed in Version: 4.3.2 Issue Type:Insecure Logging (Defense in Depth) Original Code: Found Here DescriptionThis week’s bug was discovered in the AskApache Password Protect plugin for WordPress. Once again,we are examining “security software” that is designed to provide various security protection mechanisms for a deployed WordPress blog. The description for the AskApache security plug-in is as follows: Advanced Security:Password Protection,Anti-Spam,Anti-Exploits,more to come
A very noble effort indeed This vulnerability was in the aa_pp_hashit() function. The aa_pp_hashit() function takes three arguments:$format,$user,and $pass. The aa_pp_hashit() function then attempts to create a hash containing the creds. Whenever I see functions utilizing crypto,I’m always reminded of this scene in Office Space . In this particular patch,vulnerability was in this line: aa_pp_mess(‘Created ‘.$format.’Hash for ‘.$user.’with Password ‘.$pass);
The aa_pp_mess() function actually logged the clear text username and password before putting it through a hashing function. There is rarely a need to log a clear text password… in fact,I’m going to go out on a limb here and say there is NEVER a good time when you should log a clear text password. Even password hashes or other weird representations of passwords shouldn’t be logged. Logging sensitive data is always tricky. If you’re logging sensitive data please consider the permissions required to access that sensitive data,ensure the file is properly ACL’d and conduct regular audits of log file access. Most importantly,ask yourself: Why do I need to log this data? The vulnerability was fixed by removing references to user password (and even references to the user that called the function). Now I just have to figure out why the AskApache devs are passing a default value for $pass 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
| // aa_pp_hashit //------------------------------------------------------------------------------------------- function aa_pp_hashit($format,$user='',$pass=''){ global $aa_PP; - aa_pp_mess('Created '.$format.' Hash for '.$user.' with Password '.$pass); + aa_pp_mess('Created '.$format.' Hash'); $hash=''; switch ($format){ case 'TEST': $hash=array(); foreach($aa_PP['algorithms'] as $key=>$value)$hash[]=aa_pp_hashit($key,"test{$key}","test{$key}"); return $hash; break; case 'PLAIN': $hash=$user.':'.$pass; break; case 'CRYPT': $seed = NULL; for ($i = 0;$i <8;$i++) {$seed .= substr('0123456789abcdef',rand(0,15),1);} $hash=$user.':'.crypt($pass,"$1$".$seed); break; case 'SHA1': $hash=$user.':{SHA}'.base64_encode(pack("H*",sha1($pass))); break; case 'MD5':// php.net/crypt.php#73619 $saltt = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"),0,8); $len = strlen($pass);$text = $pass.'$apr1$'.$saltt;$bin = pack("H32",md5($pass.$saltt.$pass)); for($i = $len;$i >0;$i -= 16) { $text .= substr($bin,0,min(16,$i));} for($i = $len;$i >0;$i >>= 1) { $text .= ($i &1) ? chr(0):$pass{0};} $bin = pack("H32",md5($text)); for($i=0;$i<1000;$i++) { $new = ($i &1) ? $pass:$bin;if ($i % 3) $new .= $saltt;if ($i % 7) $new .= $pass;$new .= ($i &1) ? $bin:$pass;$bin = pack("H32",md5($new));} for($i=0;$i<5;$i++) { $k = $i + 6;$j=$i + 12;if($j==16){ $j = 5;} $TRp = $bin[$i].$bin[$k].$bin[$j].$TRp;} $TRp = chr(0).chr(0).$bin[11].$TRp; $TRp = strtr(strrev(substr(base64_encode($TRp),2)),"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); $hash="$user:$"."apr1"."$".$saltt."$".$TRp; break; }
return $hash; }//========================================================================================================================= // aa_pp_show_encryptions //------------------------------------------------------------------------------------------- function aa_pp_show_encryptions($label,$type=0){ global $aa_PP; if($type==0) { ?> <p><label><?php _e($label);?><br /> <select name="aapassformat"id="aapassformat"> <?php foreach($aa_PP['algorithms'] as $key=>$value){?> <option value="<?php echo $key;?>"<?php if($aa_PP['format']==$key)echo ' selected="selected"';elseif($aa_PP['algorithms'][$key]['enabled']!='1')echo ' disabled="disabled"';?>><?php echo $key;?> </option> <?php }?> </select> </label></p> <?php } elseif($type==3) { ?> <p><label><?php _e($label);?><br /> <input id="aapassformat"name="aapassformat"type="hidden"value="<?php echo $aa_PP['format'];?>"/></label></p> <ul> <?php foreach($aa_PP['algorithms'] as $key=>$value){?> <li><label><input name="aapassformat"id="aapassformat<?php echo strtolower($key);?>"type="radio"value="<?php echo $key;?>"<?php if($aa_PP['format']==$key)echo 'checked="checked"'; elseif($aa_PP['algorithms'][$key]['enabled']!='1')echo 'disabled="disabled"';?>/><strong><?php echo $key;?></strong>- <?php echo $aa_PP['algorithms'][$key]['desc'];?></label></li> <?php }?> </ul> <?php } else if($type==4) { ?> <h4><?php _e($label);?></h4> <?php foreach($aa_PP['algorithms'] as $key=>$value){?> <p><strong><?php echo $key;?></strong>- <?php echo $aa_PP['algorithms'][$key]['desc'];?></p> <?php }?> <hr style="visibility:hidden;padding-top:.25em;clear:both;"/> <?php } }//=========================================================================================================================
// aa_pp_mess //------------------------------------------------------------------------------------------- function aa_pp_mess($message=''){ if(@defined('AA_PP_DEBUG_LOGFILE'))error_log($message,3,AA_PP_DEBUG_LOGFILE); - else error_log($message); + else if(AA_PP_DEBUG)error_log($message) if(AA_PP_DEBUG){ ?><div id="message"style="margin:1em auto;"><p><?php echo $message;?></p></div><?php } }//========================================================================================================================= |
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: BackupWordPress Fixed in Version: 0.4.3 Issue Type:Code Execution Original Code: Found Here DescriptionThis particular bug was a remote file inclusion vulnerability in a WordPress plugin known as BackupWordPress. This particular vulnerability was actually publically disclosed on Milworm by the “Xmors Underground Team” (http://www.milw0rm.com/exploits/4593). The vulnerability,combined with the register_globals behavior in older versions of PHP allowed attackers to simply provide the “$GLOBALS['bkpwp_plugin_path']” via the URL in a GET request,supplying an attacker controlled location for the include. The developers fixed this particular vulnerability by removing the $GLOBALS from the source. 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
| <?php
-require_once $GLOBALS['bkpwp_plugin_path']."Archive/Predicate.php"; +require_once BKPWP_PLUGIN_PATH."Archive/Predicate.php"; require_once "MIME/Type.php";
class File_Archive_Predicate_MIME extends File_Archive_Predicate { var $mimes;
function File_Archive_Predicate_MIME($mimes) { if (is_string($mimes)) { $this->mimes = explode(",",$mimes); } else { $this->mimes = $mimes; } } function isTrue(&$source) { $sourceMIME = $source->getMIME(); foreach ($this->mimes as $mime) { if (MIME_Type::isWildcard($mime)) { $result = MIME_Type::wildcardMatch($mime,$sourceMIME); } else { $result = ($mime == $sourceMIME); } if ($result !== false) { return $result; } } return false; } }
?> |
DetailsAffected Software:WordPress Fixed in Version:2.9 Issue Type:Cross Site Scripting (XSS) Original Code: Found Here DescriptionAnother classic XSS vulnerability in WordPress. This particular vulnerability was fixed in WordPress version 2.9. In this fix,the WordPress developers realized that they had not provided any sanitization or encoding for the $title variable. What’s interesting is although the WordPress developers missed a HTML encode,they managed to attribute escape same variable literally a few characters away in the same line of code! The WordPress developers simply called an HTML escape function to defend against XSS attacks. Its a simple one line change which is provided below. 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
| function wp_dashboard_recent_drafts( $drafts = false ) { if ( !$drafts ) { $drafts_query = new WP_Query( array( 'post_type' =>'post', 'post_status' =>'draft', 'author' =>$GLOBALS['current_user']->ID, 'posts_per_page' =>5, 'orderby' =>'modified', 'order' =>'DESC' ) ); $drafts =&$drafts_query->posts; } if ( $drafts &&is_array( $drafts ) ) { $list = array(); foreach ( $drafts as $draft ) { $url = get_edit_post_link( $draft->ID ); $title = _draft_or_post_title( $draft->ID ); - $item = "<h4><a href='$url' title='". sprintf( __( 'Edit %s' ),esc_attr( $title ) ) . "'>$title</a><abbr title='". get_the_time(__('Y/m/d g:i:s A'),$draft) . "'>". get_the_time( get_option( 'date_format' ),$draft ) . '</abbr></h4>'; + $item = "<h4><a href='$url' title='". sprintf( __( 'Edit %s' ),esc_attr( $title ) ) . "'>". esc_html($title) . "</a><abbr title='". get_the_time(__('Y/m/d g:i:s A'),$draft) . "'>". get_the_time( get_option( 'date_format' ),$draft ) . '</abbr></h4>'; if ( $the_content = preg_split( '#\s#',strip_tags( $draft->post_content ),11,PREG_SPLIT_NO_EMPTY ) ) $item .= '<p>' . join( ' ',array_slice( $the_content,0,10 ) ) . ( 10 <count( $the_content ) ? '…':'' ) . '</p>'; $list[] = $item; } ?> <ul> <li><?php echo join( "</li>\n<li>",$list );?></li> </ul> <p><a href="edit.php?post_status=draft"><?php _e('View all');?></a></p> <?php } else { _e('There are no drafts at the moment'); } }
function wp_dashboard_recent_comments() { global $wpdb; if ( current_user_can('edit_posts') ) $allowed_states = array('0','1'); else $allowed_states = array('1'); // Select all comment types and filter out spam later for better query performance. $comments = array(); $start = 0; while ( count( $comments ) <5 &&$possible = $wpdb->get_results( "SELECT * FROM $wpdb->comments c LEFT JOIN $wpdb->posts p ON c.comment_post_ID = p.ID WHERE p.post_status != 'trash' ORDER BY c.comment_date_gmt DESC LIMIT $start,50") ) { foreach ( $possible as $comment ) { if ( count( $comments ) >= 5 ) break; if ( in_array( $comment->comment_approved,$allowed_states ) ) $comments[] = $comment; } $start = $start + 50; } if ( $comments ): ?> |
|