Popular Vulnerable Code

Errors

Errors using inadequate data are much less than those using no data at all.
-Charles Babbage

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
<?php
if (isset($_GET['action']) || isset($_POST['prune']) || isset($_POST['prune_comply']))
{
if (isset($_POST['prune_comply']))
{
confirm_referrer('admin_prune.php');

$prune_from = $_POST['prune_from'];
$prune_days = intval($_POST['prune_days']);
$prune_date = ($prune_days) ? time() - ($prune_days*86400) : -1;

@set_time_limit(0);

if ($prune_from == 'all')
{
$result = $db->query('SELECT id FROM '.$db->prefix.'forums') or error('Unable to fetch forum list', __FILE__, __LINE__, $db->error());
$num_forums = $db->num_rows($result);

for ($i = 0; $i < $num_forums; ++$i)
{
$fid = $db->result($result, $i);

prune($fid, $_POST['prune_sticky'], $prune_date);
update_forum($fid);
}
}
else
{
$prune_from = intval($prune_from);
prune($prune_from, $_POST['prune_sticky'], $prune_date);
update_forum($prune_from);
}

// Locate any "orphaned redirect topics" and delete them
$result = $db->query('SELECT t1.id FROM '.$db->prefix.'topics AS t1 LEFT JOIN '.$db->prefix.'topics AS t2 ON t1.moved_to=t2.id WHERE t2.id IS NULL AND t1.moved_to IS NOT NULL') or error('Unable to fetch redirect topics', __FILE__, __LINE__, $db->error());
$num_orphans = $db->num_rows($result);

if ($num_orphans)
{
for ($i = 0; $i < $num_orphans; ++$i)
$orphans[] = $db->result($result, $i);

$db->query('DELETE FROM '.$db->prefix.'topics WHERE id IN('.implode(',', $orphans).')') or error('Unable to delete redirect topics', __FILE__, __LINE__, $db->error());
}

redirect('admin_prune.php', 'Posts pruned. Redirecting &hellip;');
}
?>
...<snip>...

<div>
<h2><span>Prune</span></h2>
<div>
<form method="post" action="admin_prune.php?action=foo">
<div>
<input type="hidden" name="prune_days" value="<?php echo $prune_days ?>" />
<input type="hidden" name="prune_sticky" value="<?php echo $_POST['prune_sticky'] ?>" />
<input type="hidden" name="prune_from" value="<?php echo $prune_from ?>" />
<fieldset>
<legend>Confirm prune posts</legend>
<div>
<p>Are you sure that you want to prune all topics older than <?php echo $prune_days ?> days from <?php echo $forum ?>? (<?php echo $num_topics ?> topics)</p>
<p>WARNING! Pruning posts deletes them permanently.</p>
</div>
</fieldset>
</div>
<p><input type="submit" name="prune_comply" value="Prune" /><a href="javascript:history.go(-1)">Go back</a></p>

Spinning – Defense in Depth

Details

Affected Software: Dojo Toolkit

Fixed in Version: 1.4.1

Issue Type: Defense in Depth

Original Code: Found Here

Description

This was a vulnerability affecting the Dojo toolkit.   Apparently, the dojo toolkit shipped with a SWF file that had a few vulnerabilities.  This particular vulnerability affected one of those SWF files.  First, SWF files are compiled files, however they can be decompiled.  Unlike traditional server side web application languages (PHP, ASP, JSP…etc), SWF files are downloaded and rendered on the clientside.  Decompiling the SWF file gives the attacker full access to the ActionScript source code for the SWF application.

In this particular SWF file, we see that the developers explicitly set the Security.allowDomain to “*”.  This makes it so SWF flies from other, external domains can include the Dojo toolkit SWF file and script/access its internal functionality.

The Dojo toolkit devs fixed this particular issue by removing the allowDomain call and adding an Externalinterface call checking to see if a particular wrapper was available in HTML.  If you’re interested in Flash security, an excellent presentation on Flash security given by Stefano Di Paola can be found here:

http://www.slideshare.net/guestb0af15/owasp-wasc-app-sec2007-san-jose-finding-vulnsin-flash-apps

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
public class FLVideo extends Sprite {
private var videoUrl:String;
private var video:Video;
private var connection:NetConnection;
private var autoPlay:Boolean;
private var videoStream:NetStream;
private var videoWidth:Number;
private var videoHeight:Number;
private var _currentVideo:VideoContainer;
private var preview:VideoContainer;
private var currentVolume:Number = 1;
private var isFullscreen:Boolean = false;
private var playlist:VideoPlaylist;
private var hasPlaylist:Boolean = false;
private var mode:String = "preview";

public function FLVideo() {
-    Security.allowDomain("*");
+    var secure:* = ExternalInterface.call("swfIsInHTML");
+    if(secure !== true){
+        return;
+    }
+    //Security.allowDomain("*");

stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.addEventListener(Event.RESIZE, onStageResize);
stage.addEventListener(FullScreenEvent.FULL_SCREEN , onFullscreenChange);
stage.addEventListener(MouseEvent.CLICK, onClick);

var obj:Object = LoaderInfo(this.root.loaderInfo).parameters;
trace(obj)

if(!obj.videoUrl){
obj = {
autoPlay: true,
isDebug: true,
videoUrl:"demo_video.flv"
};
}


// ugh - booleans not coming through
if(obj.autoPlay===true || obj.autoPlay=="true"){
autoPlay = true;
}

if(obj.volume) {
currentVolume = obj.volume;
}

if(obj.isDebug===true || obj.isDebug=="true"){
console.isDebug(true);
Tracer.init({both:true})
Tracer.log("FLVideo initialized...")
}

+    Tracer.log("secure?::", secure)
MovieIdentity.identity = obj.id || "default";
this.playlist = new VideoPlaylist(autoPlay, currentVolume);

if(obj.videoUrl) {
videoUrl = obj.videoUrl;
}

preview = new VideoContainer(videoUrl, autoPlay, currentVolume);
addChild(preview);
provideCallbacks();
}


public function get currentVideo():VideoContainer{

if(mode=="playlist" && hasPlaylist){
return this.playlist.current;
}else{
return preview;
}
}

Spinning

My advice is keep your lips away from the spinning things.
-Adam Savage

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
public class FLVideo extends Sprite {
private var videoUrl:String;
private var video:Video;
private var connection:NetConnection;
private var autoPlay:Boolean;
private var videoStream:NetStream;
private var videoWidth:Number;
private var videoHeight:Number;
private var _currentVideo:VideoContainer;
private var preview:VideoContainer;
private var currentVolume:Number = 1;
private var isFullscreen:Boolean = false;
private var playlist:VideoPlaylist;
private var hasPlaylist:Boolean = false;
private var mode:String = "preview";

public function FLVideo() {
Security.allowDomain("*");
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.addEventListener(Event.RESIZE, onStageResize);
stage.addEventListener(FullScreenEvent.FULL_SCREEN , onFullscreenChange);
stage.addEventListener(MouseEvent.CLICK, onClick);

var obj:Object = LoaderInfo(this.root.loaderInfo).parameters;
trace(obj)

if(!obj.videoUrl){
obj = {
autoPlay: true,
isDebug: true,
videoUrl:"demo_video.flv"
};
}


// ugh - booleans not coming through
if(obj.autoPlay===true || obj.autoPlay=="true"){
autoPlay = true;
}

if(obj.volume) {
currentVolume = obj.volume;
}

if(obj.isDebug===true || obj.isDebug=="true"){
console.isDebug(true);
Tracer.init({both:true})
Tracer.log("FLVideo initialized...")
}

MovieIdentity.identity = obj.id || "default";
this.playlist = new VideoPlaylist(autoPlay, currentVolume);

if(obj.videoUrl) {
videoUrl = obj.videoUrl;
}

preview = new VideoContainer(videoUrl, autoPlay, currentVolume);
addChild(preview);
provideCallbacks();
}


public function get currentVideo():VideoContainer{

if(mode=="playlist" && hasPlaylist){
return this.playlist.current;
}else{
return preview;
}
}

CaddyShack – Cross Site Scripting

Details

Affected Software: WebChat Module for Jive

Fixed in Version: August of 2008

Issue Type: Cross Site Scripting

Original Code: Found Here

Description

This week’s vulnerability affected a webchat module created by Jive Software.  The bug is straightforward,  the JSP code takes an attacker controlled value and uses it to build dynamic HTML.  Although the bug is straightforward, this week’s example was a great/simple exercise in identifying a vulnerable pattern and tracing to find other vulnerable patterns in the code.  This week’s sample has three separate vulnerabilities that were all addressed via single patch.  All these have similar symptoms/patterns (although the specifics are a bit different).  Identifying vulnerable patterns and searching for these patterns in other places in code is an essential skill for security code auditors.  Did you find all three bugs that were patched?

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
 public class FormUtils {

private FormUtils() {
}

public static String createAnswers(FormField formField, HttpServletRequest request) {
final StringBuffer builder = new StringBuffer();
if (formField.getType().equals(FormField.TYPE_TEXT_SINGLE)) {
String cookieValue = getCookieValueForField(formField.getVariable(), request);
String insertValue = "";
if(ModelUtil.hasLength(cookieValue)){
insertValue = "value=\""+cookieValue+"\"";
}
- builder.append("<input type=\"text\" name=\"" + formField.getVariable() + "\" "+insertValue+" style=\"width:75%\">");
+builder.append("<input type=\"text\" name=\"" + formField.getVariable() + "\" "+StringUtils.escapeHTMLTags(insertValue)+" style=\"width:75%\">");
}
else if (formField.getType().equals(FormField.TYPE_TEXT_MULTI)) {
builder.append("<textarea name=\"" + formField.getVariable() + "\" cols=\"30\" rows=\"3\">");
builder.append("</textarea>");
}
else if (formField.getType().equals(FormField.TYPE_LIST_SINGLE)) {
builder.append("<select name=\"" + formField.getVariable() + "\" >");
Iterator iter = formField.getOptions();
String cookieValue = ModelUtil.emptyStringIfNull(getCookieValueForField(formField.getVariable(), request));
while (iter.hasNext()) {
FormField.Option option = (FormField.Option)iter.next();
String selected = option.getValue().equals(cookieValue) ? "selected" : "";
- builder.append("<option value=\"" + option.getValue() + "\" "+selected+">" + option.getLabel() + "</option>");
+builder.append("<option value=\"" + StringUtils.escapeHTMLTags(option.getValue()) + "\" "+selected+">" + option.getLabel() + "</option>");
}
builder.append("</select>");
}
else if (formField.getType().equals(FormField.TYPE_BOOLEAN)) {
Iterator iter = formField.getOptions();
int counter = 0;
while (iter.hasNext()) {
FormField.Option option = (FormField.Option)iter.next();
String value = option.getLabel();
builder.append("<input type=\"checkbox\" value=\"" + value + "\" name=\"" + formField.getVariable() + counter + "\">");
builder.append("&nbsp;");
-builder.append(value);
+builder.append(StringUtils.escapeHTMLTags(value));
builder.append("<br/>");
counter++;
}
}

CaddyShack

I asked Dalai Lama the most important question that I think you could ask – if he had ever seen Caddyshack.
-Jesse Ventura

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
public class FormUtils {

private FormUtils() {
}

public static String createAnswers(FormField formField, HttpServletRequest request) {
final StringBuffer builder = new StringBuffer();
if (formField.getType().equals(FormField.TYPE_TEXT_SINGLE)) {
String cookieValue = getCookieValueForField(formField.getVariable(), request);
String insertValue = "";
if(ModelUtil.hasLength(cookieValue)){
insertValue = "value=\""+cookieValue+"\"";
}
builder.append("<input name="\""" type="\"text\"" />");
}
else if (formField.getType().equals(FormField.TYPE_TEXT_MULTI)) {
builder.append("
<textarea cols="\"30\"" rows="\"3\"" name="\""">");<br /> builder.append("</textarea>");
}
else if (formField.getType().equals(FormField.TYPE_LIST_SINGLE)) {
builder.append("<select name="\"""> <option value="\""">" + option.getLabel() + "</option</select>");
}
else if (formField.getType().equals(FormField.TYPE_BOOLEAN)) {
Iterator iter = formField.getOptions();
int counter = 0;
while (iter.hasNext()) {
FormField.Option option = (FormField.Option)iter.next();
String value = option.getLabel();
builder.append("<input name="\""" type="\"checkbox\"" value="\""" />");
builder.append(" ");
builder.append(value);
builder.append(" ");

counter++;
}
}

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

/**
* Add a link to the settings page to the plugins list
*/
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');
}
}

/**
* Create a Checkbox input field
*/
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/>';
}

/**
* Create a Text input field
*/
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/>';
}

Tree

A tree’s a tree. How many more do you need to look at?
-Ronald Reagan

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

/**
* Add a link to the settings page to the plugins list
*/

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

/**
* Create a Checkbox input field
*/

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

/**
* Create a Text input field
*/

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

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

Burnout

Better to burn out than rust out.
-Neil Young

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

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