Popular Vulnerable Code

Tree – Cross Site Scripting

Details

Affected Software:WordPress-to-lead for Salesforce CRM

Fixed in Version:1.0.5

Issue Type:Cross Site Scripting

Original Code: Found Here

Description

This 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 Solution

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
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/>';
}

Burnout –SQL Injection

Details

Affected Software:TimesToCome Stop Bot Registration (WordPress Plugin)

Fixed in Version:1.9

Issue Type:SQL Injection

Original Code: Found Here

Description

Straight 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 Solution

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
// 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 );
}

Reboot – SQL Injection

Details

Affected Software:The Hacker’s Diet (WordPress Plugin)

Fixed in Version:0.9.7b

Issue Type:SQL Injection

Original Code: Found Here

Description

This 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 Solution

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
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++;
}
}

Nails –Cross Site Scripting

Details

Affected Software:DojoToolkit

Fixed in Version:1.4.2

Issue Type:XSS

Original Code: Found Here

Description

This 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 Solution

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
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);
}

});
}

})();

Will - Sql InjectionWill – Sql Injection

Details

Affected Software:WP Category Manager plugin

Fixed in Version:1.3.1.0

Issue Type:Sql Injection

Original Code: Found Here

Description

This 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 Solution

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
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;

?>

Learning - Insecure Logging (Defense in Depth)Learning –Insecure Logging (Defense in Depth)

Details

Affected Software:AskApache Password Protect

Fixed in Version: 4.3.2

Issue Type:Insecure Logging (Defense in Depth)

Original Code: Found Here

Description

This 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 Solution

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
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 }
}//=========================================================================================================================
 

Widths – SQL Injection

Details

Affected Software:Login LockDown for WordPress

Fixed in Version: 1.5

Issue Type:SQL Injection

Original Code: Found Here

Description

This 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 Solution

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
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"]);?>">

Pictures – SQL Injection

Details

Affected Software: Short URL Plugin

Fixed in Version: 2.0

Issue Type:SQL Injection

Original Code: Found Here

Description

This 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 Solution

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
<?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">
?>

Theory – Code Execution

Details

Affected Software: BackupWordPress

Fixed in Version: 0.4.3

Issue Type:Code Execution

Original Code: Found Here

Description

This 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 Solution

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
<?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;
    }
}

?>

Working Clothes – XSS

Details

Affected Software:WordPress

Fixed in Version:2.9

Issue Type:Cross Site Scripting (XSS)

Original Code: Found Here

Description

Another 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 Solution

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
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 ) ? '&hellip;':'' ) . '</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 ):
?>