Popular Vulnerable Code

Beer –XSS

Details

Affected Software:OpenFire

Fixed in Version:3.7.0b

Issue Type:XSS

Original Code: Found Here

Description

This week’s bug was an XSS vulnerability caused by the improper escaping of an HTML attribute.  It’s obvious that the developers attempted to protect their software from XSS vulnerabilities.  They even wrote their own XSS sanitizing method (escapeHTMLTags).  The escapeHTMLTags() method is simple,strip out <and >characters and return the string.  Unfortunately,this simple pattern isn’t sufficient in defending against all XSS vulnerabilities.  There is a bit of tracing that is required to understand this bug,so let’s start the tracing.  The bug begins with the following variable assignment:

String username = ParamUtils.getParameter(request,“username”);

The username value is assigned directly from the HTTP request.  Later,the username variable is escaped using the custom escapeHTMLTags() function.  The escaping occurs in the following line:

username = org.jivesoftware.util.StringUtils.escapeHTMLTags(username);

Later in the code,the escaped username value is used in the markup as part of an HTML attribute.  The vulnerable line is presented below:

<input size=”15″maxlength=”50″value=”<%= (username != null ? username:“”) %>”>

The line above checks to see if the username variable has been assigned a value.  If the username variable contains a value,it is displayed in the markup as the value attribute for an input field.  While sanitizing the <and >characters would prevent an attacker closing the input field and starting a new html tag,it doesn’t prevent an attacker from closing off the attribute value and injecting a new HTML attribute for the input field.  Some consider injection into a input field to be unexploitable (or limited to certain browsers),check out Gareth Heyes blog post about exploiting text fields with new HTML5 events http://www.thespanner.co.uk/2009/12/06/html5-new-xss-vectors/

Developers Solution

  public static String escapeHTMLTags(String in){ if (in == null){ return null; }  char ch; int i = 0; int last = 0; char[] input = in.toCharArray(); int len = input.length; StringBuilder out = new StringBuilder((int)(len * 1.3)); for (;i <len;i++){ ch = input[i]; if (ch >'>'){ }  else if (ch == '<'){if (i >last){out.append(input,last,i - last);} last = i + 1;out.append(LT_ENCODE); }  else if (ch == '>'){if (i >last){out.append(input,last,i - last);} last = i + 1;out.append(GT_ENCODE); }  else if (ch == '\n'){if (i >last){out.append(input,last,i - last);} last = i + 1;out.append("<br>"); }  }  if (last == 0){ return in; }  if (i >last){ out.append(input,last,i - last); }  return out.toString(); }... <snip>...<% // get parameters  String username = ParamUtils.getParameter(request,"username"); String password = ParamUtils.getParameter(request,"password"); String url = ParamUtils.getParameter(request,"url"); url = org.jivesoftware.util.StringUtils.escapeHTMLTags(url); // SSO between cluster nodes  String secret = ParamUtils.getParameter(request,"secret"); String nodeID = ParamUtils.getParameter(request,"nodeID"); String nonce = ParamUtils.getParameter(request,"nonce"); // The user auth token: AuthToken authToken = null; // Check the request/response for a login token  Map<String,String>errors = new HashMap<String,String>(); if (ParamUtils.getBooleanParameter(request,"login")){ String loginUsername = username; if (loginUsername != null){ loginUsername = JID.escapeNode(loginUsername); }  try{ if (secret != null &&nodeID != null){if (StringUtils.hash(AdminConsolePlugin.secret).equals(secret) &&ClusterManager.isClusterMember(Base64.decode(nodeID,Base64.URL_SAFE))){authToken = new AuthToken(loginUsername);} else if ("clearspace".equals(nodeID) &&ClearspaceManager.isEnabled()){ClearspaceManager csmanager = ClearspaceManager.getInstance();String sharedSecret = csmanager.getSharedSecret();if (nonce == null || sharedSecret == null || !csmanager.isValidNonce(nonce) ||   !StringUtils.hash(loginUsername + ":"+ sharedSecret + ":"+ nonce).equals(secret)){throw new UnauthorizedException("SSO failed. Invalid secret was provided");} authToken = new AuthToken(loginUsername);} else{throw new UnauthorizedException("SSO failed. Invalid secret or node ID was provided");}  }  else{// Check that a username was provided before trying to verify credentials if (loginUsername != null){if (LoginLimitManager.getInstance().hasHitConnectionLimit(loginUsername,request.getRemoteAddr())){throw new UnauthorizedException("User '"+ loginUsername +"' or address '"+ request.getRemoteAddr() + "' has his login attempt limit.");} if (!AdminManager.getInstance().isUserAdmin(loginUsername,true)){throw new UnauthorizedException("User '"+ loginUsername + "' not allowed to login.");} authToken = AuthFactory.authenticate(loginUsername,password);} else{errors.put("unauthorized",LocaleUtils.getLocalizedString("login.failed.unauthorized"));}  }... <snip>...  // Escape HTML tags in username to prevent cross-site scripting attacks. This  // is necessary because we display the username in the page below.  username = org.jivesoftware.util.StringUtils.escapeHTMLTags(username);%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title><%= AdminConsole.getAppName() %><fmt:message key="login.title"/></title><script language="JavaScript"type="text/javascript"><!--// break out of framesif (self.parent.frames.length != 0){self.parent.location=document.location}  function updateFields(el){ if (el.checked){document.loginForm.username.disabled = true;document.loginForm.password.disabled = true; }  else{document.loginForm.username.disabled = false;document.loginForm.password.disabled = false;document.loginForm.username.focus(); }  }//--></script> <link rel="stylesheet"href="style/global.css"type="text/css"> <link rel="stylesheet"href="style/login.css"type="text/css"></head><body><form action="login.jsp"name="loginForm"method="post"><% if (url != null){try{%> <input type="hidden"name="url"value="<%= url %>"><% } catch (Exception e){Log.error(e);} } %><input type="hidden"name="login"value="true"><div align="center"> <!-- BEGIN login box --> <div id="jive-loginBox"> <div align="center"id="jive-loginTable"> <span id="jive-login-header"style="background:transparent url(images/login_logo.gif) no-repeat left;padding:29px 0 10px 205px;"> <fmt:message key="admin.console"/> </span> <div style="text-align:center;width:380px;"> <table cellpadding="0"cellspacing="0"border="0"align="center"><tr><td align="right"class="loginFormTable"><table cellpadding="2"cellspacing="0"border="0"><noscript>  <tr>  <td colspan="3">  <table cellpadding="0"cellspacing="0"border="0">  <tr valign="top"> <td><img src="images/error-16x16.gif"width="16"height="16"border="0"alt=""vspace="2"></td> <td><div class="jive-error-text"style="padding-left:5px;color:#cc0000;"><fmt:message key="login.error"/></div></td>  </tr>  </table>  </td>  </tr></noscript><% if (errors.size() >0){%>  <tr>  <td colspan="3">  <table cellpadding="0"cellspacing="0"border="0"> <% for (String error:errors.values()){%>  <tr valign="top"> <td><img src="images/error-16x16.gif"width="16"height="16"border="0"alt=""vspace="2"></td> <td><div class="jive-error-text"style="padding-left:5px;color:#cc0000;"><%= error%></div></td>  </tr> <% } %>  </table>  </td>  </tr><% } %><tr>+   <td><input type="text"name="username"size="15"maxlength="50"id="u01"value="<%= (username != null ? StringUtils.removeXSSCharacters(username):"") %>"></td>-   <td><input type="text"name="username"size="15"maxlength="50"id="u01"value="<%= (username != null ? username:"") %>"></td>  <td><input type="password"name="password"size="15"maxlength="50"id="p01"></td>  <td align="center"><input type="submit"value="&nbsp;<fmt:message key="login.login"/>&nbsp;"></td></tr><tr valign="top">  <td class="jive-login-label"><label for="u01"><fmt:message key="login.username"/></label></td>  <td class="jive-login-label"><label for="p01"><fmt:message key="login.password"/></label></td>  <td>&nbsp;</td></tr></table></td></tr><tr><td align="right"><div align="right"id="jive-loginVersion"><%= AdminConsole.getAppName() %>,<fmt:message key="login.version"/>:<%= AdminConsole.getVersionString() %></div></td></tr> </table> </div> </div> </div> <!-- END login box --></div></form><script language="JavaScript"type="text/javascript"><!--  if (document.loginForm.username.value == ''){ document.loginForm.username.focus(); } else{ document.loginForm.password.focus(); }//--></script></body></html>

Noble –Cross Site Scripting

Details

Affected Software:WordPress (core)

Fixed in Version:1.2

Issue Type:XSS

Original Code: Found Here

Description

Looking at this code made me smile,its about 6 years old.  There’s a lot going on here and quite a few issues.  The first thing that jumped out at me was the use of $HTTP_POST_FILES.  $HTTP_POST_FILES means were working with user controlled files.  There are tons of things that can go wrong when dealing with user/attacker controlled files (a list too long to go into here).  Lets hope the WordPress devs are on their A-game here.  Looking at the patch submitted by the WordPress developers,we see that they changed references to $HTTP_POST_FILES to the superglobal $_FILES.  Within the $_FILES array there are a couple indexes that are commonly used.  These indexes are:

[name]
[type]
[tmp_name]
[error]
[size]

Name,type,and size are all controlled by the user/attacker,so the WordPress developers should be wary when dealing with these values.  Surprisingly (or unsurprisingly,depending on your point of view),this patch doesn’t contain seem to contain any robust validation of data associated with the uploaded file data.  Instead,the defenses put in place here seem to be centered around replacing a poor validation/sanitization routine with a more robust encoding routine which prevents a XSS vulnerability.  The replaced sanitization routine and the XSS bug are presented in the following lines:

-$imgdesc = str_replace(‘”‘,‘&amp;quot;’,$_POST['imgdesc']);
+$imgdesc = htmlentities2($imgdesc);

class=”uploadform”value=”<?php echo $imgdesc;?>”/>

What’s surprising is although the WordPress developers prevented this single XSS vulnerability,there is a large number of XSS vulnerabilities in this file.  The most obvious symptom is “<?php echo $_REQUEST[] ?>.  Additionally,the loose validation of the user uploaded file is concerning,especially with the number of problems that can be encountered when dealing with user controlled files.  Maybe the varsity team was on vacation when this patch went in.

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
<?php //Makes sure they choose a file

//print_r($HTTP_POST_FILES);
//die();


- $imgalt = (isset($_POST['imgalt'])) ? $_POST['imgalt']:$imgalt;
-
- $img1_name = (strlen($imgalt)) ? $_POST['imgalt']:$HTTP_POST_FILES['img1']['name'];
- $img1_type = (strlen($imgalt)) ? $_POST['img1_type']:$HTTP_POST_FILES['img1']['type'];
- $imgdesc = str_replace('"','&amp;quot;',$_POST['imgdesc']);
+$imgalt = basename( (isset($_POST['imgalt'])) ? $_POST['imgalt']:'' );
+
+$img1_name = (strlen($imgalt)) ? $imgalt:basename( $_FILES['img1']['name'] );
+$img1_type = (strlen($imgalt)) ? $_POST['img1_type']:$_FILES['img1']['type'];
+$imgdesc = htmlentities2($imgdesc);

$imgtype = explode(".",$img1_name);
$imgtype = strtolower($imgtype[count($imgtype)-1]);

if (in_array($imgtype,$allowed_types) == false) {
die(sprintf(__('File %1$s of type %2$s is not allowed.') ,$img1_name,$imgtype));
}

if (strlen($imgalt)) {
$pathtofile = get_settings('fileupload_realpath')."/".$imgalt;
-$img1 = $_POST['img1'];
+$img1 = $_POST['img1']['tmp_name'];
} else {
$pathtofile = get_settings('fileupload_realpath')."/".$img1_name;
-$img1 = $HTTP_POST_FILES['img1']['tmp_name'];
+$img1 = $_FILES['img1']['tmp_name'];
}

// makes sure not to upload duplicates,rename duplicates
$i = 1;
$pathtofile2 = $pathtofile;
$tmppathtofile = $pathtofile2;
$img2_name = $img1_name;

while (file_exists($pathtofile2)) {
$pos = strpos($tmppathtofile,'.'.trim($imgtype));
$pathtofile_start = substr($tmppathtofile,0,$pos);
$pathtofile2 = $pathtofile_start.'_'.zeroise($i++,2).'.'.trim($imgtype);
$img2_name = explode('/',$pathtofile2);
$img2_name = $img2_name[count($img2_name)-1];
}

if (file_exists($pathtofile) &&!strlen($imgalt)) {
$i = explode(' ',get_settings('fileupload_allowedtypes'));
$i = implode(',',array_slice($i,1,count($i)-2));
$moved = move_uploaded_file($img1,$pathtofile2);
// if move_uploaded_file() fails,try copy()
if (!$moved) {
$moved = copy($img1,$pathtofile2);
}
if (!$moved) {
die(sprintf(__("Couldn't upload your file to %s."),$pathtofile2));
} else {
chmod($pathtofile2,0666);
@unlink($img1);
}

//

// duplicate-renaming function contributed by Gary Lawrence Murphy
?>
<p><strong><?php __('Duplicate File?') ?></strong></p>
<p><b><em><?php printf(__("The filename '%s' already exists!"),$img1_name);?></em></b></p>
<p><?php printf(__("Filename '%1\$s' moved to '%2\$s'"),$img1,"$pathtofile2 - $img2_name") ?></p>
<p><?php _e('Confirm or rename:') ?></p>
<form action="upload.php"method="post"enctype="multipart/form-data">
<input type="hidden"name="MAX_FILE_SIZE"value="<?php echo  get_settings('fileupload_maxk') *1024 ?>"/>
<input type="hidden"name="img1_type"value="<?php echo $img1_type;?>"/>
<input type="hidden"name="img1_name"value="<?php echo $img2_name;?>"/>
<input type="hidden"name="img1_size"value="<?php echo $img1_size;?>"/>
<input type="hidden"name="img1"value="<?php echo $pathtofile2;?>"/>
<input type="hidden"name="thumbsize"value="<?php echo $_REQUEST['thumbsize'];?>"/>
<input type="hidden"name="imgthumbsizecustom"value="<?php echo $_REQUEST['imgthumbsizecustom'];?>"/>
<?php _e('Alternate name:') ?><br /><input type="text"name="imgalt"size="30"value="<?php echo $img2_name;?>"/><br />
<br />
<?php _e('Description:') ?><br /><input type="text"name="imgdesc"size="30"value="<?php echo $imgdesc;?>"/>
<br />
<input type="submit"name="submit"value="<?php _e('Rename') ?>"/>
</form>
</div>

Errors –Cross Site Scripting

Details

Affected Software:PunBB

Fixed in Version:1.2

Issue Type:Cross Site Scripting (XSS)

Original Code: Found Here

Description

This weeks’example was a XSS bug that affected PunBB.  The vulnerable code took attacker controlled variables directly from POST parameters and used those values for various operations.  Specifically,the $_POST[‘prune_sticky’] value was used in several places without any form of sanitization.  Looking through the patch submitted by the PunBB developers we see that the unsantized value was passed to a function named prune() and also echoed in HTML markup.  PHP echo of a $_POST variable is a classic symptom of XSS.  The PunBB developers addressed this issue by sanitizing the $_POST['prune_sticky'] value before echoing its value in HTML markup.  The developer forces the $_POST[‘prune_sticky’] value to either 1 or 0,which eliminates the possibility or arbitrary script being injected via the $prune_sticky variable.  The $_POST[‘prune_sticky’] value is sanitized here:

$prune_sticky = isset($_POST['prune_sticky']) ? ’1′:’0′;

In addition to the changes made to PHP echo there were other changes checked in the by the PunBB developers,most notably the sanitizing of values passed to the prune() function.  Using the code snippet provided here,it’s difficult to understand exactly what is accomplished by the prune() function.  Further variable tracing will be needed in order to determine the danger associated with passing a tainted value to prune().  The developers however felt it was necessary to sanitize the $prune_sticky value before passing it to prune().

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
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_sticky = isset($_POST['prune_sticky']) ? '1':'0';
$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);
+prune($fid,$prune_sticky,$prune_date);
update_forum($fid);
}
}
else
{
$prune_from = intval($prune_from);
-prune($prune_from,$_POST['prune_sticky'],$prune_date);
+prune($prune_from,$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_sticky"value="<?php echo $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>

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

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

Everything – Cross Site Scripting

Details

Affected Software:Dojo Toolkit SDK

Fixed in Version: 1.4.2

Issue Type:Cross Site Scripting (XSS)

Original Code: Found Here

Description

This was a bug reported by the Gotham Digital Science against the Dojo toolkit SDK.  The Dojo toolkit is a popular toolkit used by numerous websites… so in essence this bug provided attackers an opportunity to XSS a large number of websites across the Internet.

The bug begins by the capturing of untrusted parameter values from the querystring.  This is done by the following JavaScript:

1
var qstr = window.location.search.substr(1);

qstr is then split based on the “&” character,proving values for various JavaScript variables including DoJoURL and TestURL.   The attacker is free to provide arbitrary values for DoJoURL and TestURL by simply providing the proper querystring values.  For example:

runner.html?dojoUrl=attacker-controlled&testUrl=attackercontrolled

the attacker supplied values are then used in a document.write() statement,giving the attacker the opprotuntiy to inject arbitrary client side script into any website that happens to include the Dojo library.  The vulnerable document.write() statements are provided below:

document.write(“<scr”+”ipt type=’text/javascript’djConfig=’isDebug:true’src=’”+dojoUrl+”‘></scr”+”ipt>”);

document.write(“<scr”+”ipt type=’text/javascript’src=’”+testUrl+”.js’></scr”+”ipt>”);

The Dojo developers addressed this vulnerability by replacing characters from the attacker controlled input.  The specific regular expression used is provided below:

value=tp[1].replace(/[<>"']/g,“”);

I see a major issue with this code fix… can you spot it as well?

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
                <script type="text/javascript">
                        // workaround for bug in Safari 3.  See #7189
                        if (/3[\.0-9]+ Safari/.test(navigator.appVersion))
                        {
                                window.console = {
                                    origConsole:window.console,
                                    log:function(s){
                                                this.origConsole.log(s);
                                        },
                                        info:function(s){
                                                this.origConsole.info(s);
                                        },
                                        error:function(s){
                                                this.origConsole.error(s);
                                        },
                                        warn:function(s){
                                                this.origConsole.warn(s);
                                        }
                               };
                        }
                </script>
 
                <script type="text/javascript">
                        window.dojoUrl = "../../dojo/dojo.js";
                        window.testUrl = "";
                        window.testModule = "";
 
                        // parse out our test URL and our Dojo URL from the query string
                        var qstr = window.location.search.substr(1);
                        if(qstr.length){
                                var qparts = qstr.split("&");
                                for(var x=0;x<qparts.length;x++){
-                                       var tp = qparts[x].split("=");
-                                       if(tp[0] == "dojoUrl"){
-                                               window.dojoUrl = tp[1];
-                                       }
-                                       if(tp[0] == "testUrl"){
-                                               window.testUrl = tp[1];
-                                       }
-                                       if(tp[0] == "testModule"){
-                                               window.testModule = tp[1];
-                                       }
-                                       if(tp[0] == "registerModulePath"){
-                                               var modules = tp[1].split(";");
-                                               window.registerModulePath=[];
-                                               for (var i=0;i<modules.length;i++){
-                                                        window.registerModulePath.push(modules[i].split(","));
-                                               }
-                                       }
+                                       var tp = qparts[x].split("="),name=tp[0],value=tp[1].replace(/[<>"']/g,"");  // replace() to avoid XSS attack 
+                                       switch(name){ 
+                                               case "dojoUrl": 
+                                               case "testUrl": 
+                                               case "testModule": 
+                                                       window[name] = value; 
+                                                       break; 
+                                               case "registerModulePath": 
+                                                       var modules = value.split(";")
+                                                       window.registerModulePath=[]
+                                                       for (var i=0;i<modules.length;i++){ 
+                                                               window.registerModulePath.push(modules[i].split(","))
+                                                       } 
+                                               break; 
                                }
                        }
 
                        document.write("<scr"+"ipt type='text/javascript' djConfig='isDebug:true' src='"+dojoUrl+"'></scr"+"ipt>");
                </script>
                <script type="text/javascript">
                        try{
                                dojo.require("doh.runner");
                        }catch(e){
                                document.write("<scr"+"ipt type='text/javascript' src='runner.js'></scr"+"ipt>");
                        }
                        if(testUrl.length){
                                document.write("<scr"+"ipt type='text/javascript' src='"+testUrl+".js'></scr"+"ipt>");
                        }
                </script>
                <style type="text/css">
                        @import "../../dojo/resources/dojo.css";
                        var SHRSB_Globals ={"src":"http:\/\/spotthevuln.com\/wordpress\/wp-content\/plugins\/sexybookmarks\/spritegen_default","perfoption":null};;var SHRSB_Globals ={"src":"http:\/\/spotthevuln.com\/wordpress\/wp-content\/plugins\/sexybookmarks\/spritegen_default","perfoption":null};