Popular Vulnerable Code

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

});
}

})();

Drop Top - Cross Site ScriptingDrop Top –Cross Site Scripting

Details

Affected Software:Openfire by Ignite Realtime

Fixed in Version:3.6.1

Issue Type:XSS

Original Code: Found Here

Description

This is a straightforward XSS bug that affecting the Admin Console of OpenFire by Ignite Realtime/Jive software.  The code fix is simple,encode a tainted URL variable before using it in markup.  The URL variable is assigned an attacker controlled value here:

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

And is later used in the HTML markup here:

<input value=”<%= url %>”>

The one line fix is to encode the URL parameter,which was done here:

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

Looking through the code,we see that OpenFire had previously fixed an XSS vulnerability just a few lines above in the USERNAME variable.  There is even comment indicating so!  It surprising that the Ignite Realtime/Jive developers missed this one as it is literally two lines below the previous fix.

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

if (username != null){

username = JID.escapeNode(username);

}

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

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

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

The assignment of the PASSWORD variable is interesting :)

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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
<%!

static String go(String url) {

if (url == null) {

return "index.jsp";

}

else {

return url;

}

}

%>

<%-- Check if in setup mode --%>

<%

if (admin.isSetupMode()) {

response.sendRedirect("setup/index.jsp");

return;

}

%>

<% // get parameters

String username = ParamUtils.getParameter(request,"username");

if (username != null) {

username = JID.escapeNode(username);

}

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

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;

... SNIP ...

<html>

<head>

<title><%= AdminConsole.getAppName() %><fmt:message key="login.title"/></title>

<script language="JavaScript">

<!--

// break out of frames

if (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"value="<%= url %>">

<%  } catch (Exception e) { Log.error(e);} } %>

<input value="true">

<div align="center">

<!-- BEGIN login box -->

<div id="jive-loginBox">

<div align="center">

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

<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 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 style="padding-left:5px;color:#cc0000;"><%= error%></div></td>

</tr>

<% } %>

</table>

</td>

</tr>

<%  } %>

<tr>

<td><input size="15"maxlength="50"value="<%= (username != null ? username:"") %>"></td>

<td><input size="15"maxlength="50"></td>

<td align="center"><input value="&nbsp;<fmt:message key="login.login"/>&nbsp;"></td>

</tr>

<tr valign="top">

<td><label for="u01"><fmt:message key="login.username"/></label></td>

<td><label for="p01"><fmt:message key="login.password"/></label></td>

<td>&nbsp;</td>

</tr>

</table>

</td>

</tr>

<tr>

<td align="right">

<div align="right">

<%= AdminConsole.getAppName() %>,<fmt:message key="login.version"/>:<%= AdminConsole.getVersionString() %>

</div>

</td>

</tr>

</table>

</div>

</div>

</div>

<!-- END login box -->

</div>

</form>

<script type="text/javascript">

<!--

if (document.loginForm.username.value == '')  {

document.loginForm.username.focus();

} else {

document.loginForm.password.focus();

}

//-->

</script>

</body>

</html>

Weird Clothes – Cross Site Scripting

Details

Affected Software:Openfire by Ignite Realtime

Fixed in Version:3.6.3

Issue Type:XSS

Original Code: Found Here

Description

XSS bug in Openfire by Ignite Realtime.  Openfire is an Open Source,real time collaboration server.  The bug is very straightforward and a simple string like the one presented below takes advantage of the vulnerability.

http://www.example.com/group-summary.jsp?search=%22%3E%3C[xss]

This bug was actually part of a number of security bugs reported by Core Security Technologies.  You can read their advisory here.

The patch simply HTML encodes the tainted search parameter…

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
<%  // Get parameters
int start = ParamUtils.getIntParameter(request,"start",0);
int range = ParamUtils.getIntParameter(request,"range",webManager.getRowsPerPage("group-summary", 15));

if (request.getParameter("range") != null) {
webManager.setRowsPerPage("group-summary", range);
}

int groupCount = webManager.getGroupManager().getGroupCount();
Collection<Group> groups = webManager.getGroupManager().getGroups(start, range);

String search = null;
if (webManager.getGroupManager().isSearchSupported() && request.getParameter("search") != null
&& !request.getParameter("search").trim().equals(""))
{
search = request.getParameter("search");
+    // Santize variables to prevent vulnerabilities
+    search = StringUtils.escapeHTMLTags(search);

// Use the search terms to get the list of groups and group count.
groups = webManager.getGroupManager().search(search, start, range);
// Get the count as a search for *all* groups. That will let us do pagination even
// though it's a bummer to execute the search twice.
groupCount = webManager.getGroupManager().search(search).size();
}

// paginator vars
int numPages = (int)Math.ceil((double)groupCount/(double)range);
int curPage = (start/range) + 1;
%>

<%  if (request.getParameter("deletesuccess") != null) { %>

<div class="jive-success">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr><td class="jive-icon"><img src="images/success-16x16.gif" width="16" height="16" border="0" alt=""></td>
<td class="jive-icon-label">
<fmt:message key="group.summary.delete_group" />
</td></tr>
</tbody>
</table>
</div><br>

<%  } %>

<% if (webManager.getGroupManager().isSearchSupported()) { %>

<form action="group-summary.jsp" method="get" name="searchForm">
<table border="0" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td valign="bottom">
<fmt:message key="group.summary.total_group" /> <b><%= groupCount %></b>
<%  if (numPages > 1) { %>

, <fmt:message key="global.showing" /> <%= LocaleUtils.getLocalizedNumber(start+1) %>-<%= LocaleUtils.getLocalizedNumberstartrange > groupCount ? groupCount:start+range) %>

<%  } %>
</td>
<td align="right" valign="bottom">
<fmt:message key="group.summary.search" />: <input type="text" size="30" maxlength="150" name="search" value="<%= ((search!=null) ? search : "") %>">
</td>
</tr>
</table>
</form>

<script language="JavaScript" type="text/javascript">
document.searchForm.search.focus();
</script>

<% }
// Otherwise, searching is not supported.
else {
%>
<p>
<fmt:message key="group.summary.total_group" /> <b><%= groupCount %></b>
<%  if (numPages > 1) { %>

, <fmt:message key="global.showing" /> <%= (start+1) %>-<%= (start+range) %>

<%  } %>
</p>
<% } %>

<%  if (numPages > 1) { %>

<p>
<fmt:message key="global.pages" />
[
<%  for (int i=0; i<numPages; i++) {
String sep = ((i+1)<numPages) ? " " : "";
boolean isCurrent = (i+1) == curPage;
%>
<a href="group-summary.jsp?start=<%= (i*range) %><%= search!=null? "&search=" + URLEncoder.encode(search, "UTF-8") : ""%>"
class="<%= ((isCurrent) ? "jive-current" : "") %>"
><%= (i+1) %></a><%= sep %>

<%  } %>
]
</p>

Watched – Cross Site Scripting

Details

Affected Software: qTranslate  plugin

Fixed in Version:2.0.2

Issue Type:XSS

Original Code: Found Here

Description

Whew!  This is a lot of code for a simple change!  This bug affected the qTranslate plugin for WordPress.  The bug used the $_SERVER['REQUEST_URI'] variable without realizing it could contain arbitrary values supplied by an attacker.  The $_SERVER['REQUEST_URI'] variable is used directly in an HREF in the HTML markup,resulting in a classic XSS vulnerability.

The PHP documentation states that [‘REQUEST_URI’] represents:

The URI which was given in order to access this page;for instance,‘/index.html’.

No escaping is done before returning the predefined value.  Also,[‘REQUEST_URI’] actually returns the file requested as well as any query string parameters in the URI.  For example,a request for http://server/index.php?blah=foo will return ‘index.php?blah=foo’.  With this in mind,the attacker is free to set up arbitrary query string parameters which contain the XSS payload

http://server/qtranslate_widget.php?xss=”><script>alert(document.domain)</script>

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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
<?php
function qtrans_convertURL($url='',$lang='',$forceadmin = false) {
 global $q_config;
 
 // invalid language
 if($url=='') $url = clean_url($q_config['url_info']['url']);
 if($lang=='') $lang = $q_config['language'];
 if(defined('WP_ADMIN')&&!$forceadmin) return $url;
 if(!qtrans_isEnabled($lang)) return "";
 
 // &workaround
 $url = str_replace('&amp;','&',$url);
 $url = str_replace('&','&',$url);
 
 // check if it's an external link
 $urlinfo = qtrans_parseURL($url);
 $home = rtrim(get_option('home'),"/");
 if($urlinfo['host']!='') {
  // check for already existing pre-domain language information
  if($q_config['url_mode'] == QT_URL_DOMAIN &&preg_match("#^([a-z]{2}).#i",$urlinfo['host'],$match)) {
   if(qtrans_isEnabled($match[1])) {
    // found language information,remove it
    $url = preg_replace("/".$match[1]."\./i","",$url,1);
    // reparse url
    $urlinfo = qtrans_parseURL($url);
   }
  }
  if(substr($url,0,strlen($home))!=$home) {
   return $url;
  }
  // strip home path
  $url = substr($url,strlen($home));
 } else {
  // relative url,strip home path
  $homeinfo = qtrans_parseURL($home);
  if($homeinfo['path']==substr($url,0,strlen($homeinfo['path']))) {
   $url = substr($url,strlen($homeinfo['path']));
  }
 }
 
 // check for query language information and remove if found
 if(preg_match("#(&|\?)lang=([^&\#]+)#i",$url,$match) &&qtrans_isEnabled($match[2])) {
  $url = preg_replace("#(&|\?)lang=".$match[2]."&?#i","$1",$url);
 }
 
 // remove any slashes out front
 $url = ltrim($url,"/");
 
 // remove any useless trailing characters
 $url = rtrim($url,"?&");
 
 // reparse url without home path
 $urlinfo = qtrans_parseURL($url);
 
 // check if its a link to an ignored file type
 $ignore_file_types = preg_split('/\s*,\s*/',strtolower($q_config['ignore_file_types']));
 $pathinfo = pathinfo($urlinfo['path']);
 if(isset($pathinfo['extension']) &&in_array(strtolower($pathinfo['extension']),$ignore_file_types)) {
  return $home."/".$url;
 }
 
 switch($q_config['url_mode']) {
  case QT_URL_PATH: // pre url
   // might already have language information
   if(preg_match("#^([a-z]{2})/#i",$url,$match)) {
    if(qtrans_isEnabled ($match[1])) {
     // found language information,remove it
     $url = substr($url,3);
    }
   }
   if($lang!=$q_config['default_language']) $url = $lang."/".$url;
   break;
  case QT_URL_DOMAIN: // pre domain
   if($lang!=$q_config['default_language']) $home = preg_replace("#//#","//".$lang.".",$home,1);
   break;
  default:// query
   if($lang!=$q_config['default_language']){
    if(strpos($url,'?')===false) {
     $url .= '?';
    } else {
     $url .= '&';
    }
    $url .= "lang=".$lang;
   }
 }
 
 // see if cookies are activated
 if(!$q_config['cookie_enabled'] &&!$q_config['url_info']['internal_referer'] &&$urlinfo['path'] == '' &&$lang == $q_config['default_language'] &&$q_config['language'] != $q_config['default_language']) {
  //:( now we have to make unpretty URLs
  $url = preg_replace("#(&|\?)lang=".$match[2]."&?#i","$1",$url);
  if(strpos($url,'?')===false) {
   $url .= '?';
  } else {
   $url .= '&';
  }
  $url .= "lang=".$lang;
 }
 
 // &amp;workaround
 $complete = str_replace('&','&amp;',$home."/".$url);
 return $complete;
}

...<SNIP...

function qtrans_use($lang,$text,$show_available=false) {
        global $q_config;
        // return full string if language is not enabled
        if(!qtrans_isEnabled($lang)) return $text;
        if(is_array($text)) {
                // handle arrays recursively
                foreach($text as $key =>$t) {
                        $text[$key] = qtrans_use($lang,$text[$key],$show_available);
                }
                return $text;
        }
        
        if(is_object($text)) {
                foreach(get_object_vars($text) as $key =>$t) {
                        $text->$key = qtrans_use($lang,$text->$key,$show_available);
                }
                return $text;
        }
        
        // get content
        $content = qtrans_split($text);
        // find available languages
        $available_languages = array();
        foreach($content as $language =>$lang_text) {
                $lang_text = trim($lang_text);
                if(!empty($lang_text)) $available_languages[] = $language;
        }
        
        // if no languages available show full text
        if(sizeof($available_languages)==0) return $text;
        // if content is available show the content in the requested language
        $content[$lang] = trim($content[$lang]);
        if(!empty($content[$lang])) {
                return $content[$lang];
        }
        // content not available in requested language (bad!!) what now?
        if(!$show_available){
                // check if content is available in default language,if not return first language found. (prevent empty result)
                if($lang!=$q_config['default_language'])
                        return "(".$q_config['language_name'][$q_config['default_language']].") ".qtrans_use($q_config['default_language'],$text,$show_available);
                foreach($content as $language =>$lang_text) {
                        $lang_text = trim($lang_text);
                        if(!empty($lang_text)) {
                                return $lang_text;
                        }
                }
        }
        // display selection for available languages
        $available_languages = array_unique($available_languages);
        $language_list = "";
        if(preg_match('/%LANG:([^:]*):([^%]*)%/',$q_config['not_available'][$lang],$match)) {
                $normal_seperator = $match[1];
                $end_seperator = $match[2];
                // build available languages string backward
                $i = 0;
                foreach($available_languages as $language) {
                        if($i==1) $language_list  = $end_seperator.$language_list;
                        if($i>1) $language_list  = $normal_seperator.$language_list;
-                        $language_list = "<a href=\"".qtrans_convertURL($_SERVER['REQUEST_URI'],$language)."\">".$q_config['language_name'][$language]."</a>".$language_list;
+                       $language_list = "<a href=\"".qtrans_convertURL('',$language)."\">".$q_config['language_name'][$language]."</a>".$language_list;
                        $i++;
                }
        }
        return "<p>".preg_replace('/%LANG:([^:]*):([^%]*)%/',$language_list,$q_config['not_available'][$lang])."</p>";
}
 
?>

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;

?>

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