| DetailsAffected Software:Ninja Announcements Fixed in Version:1.3 Issue Type:SQL Injection Original Code:Found Here DetailsLots of potential issues here,but we’ll focus on what was patched. Here we have a basic SQL injection vulnerability. The bug is the most simple example of tracing a variable from assignment to usage. On line 54,the $ninja_annc_id is assigned a value directly from the user/attacker controlled $_REQUEST[‘ninja_annc_id’]. The very next line,the developer uses the tainted $ninja_annc_id to string build a SQL statement. The developers addressed this issue by moving the dynamic SQL statement to a prepared SQL statement. Prepared statements are the preferred method for dealing with SQL requests that could potentially contain tainted values. Developers Solution1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| <?php...snip ... //This if() statement handles user input from the edit section. if($_REQUEST['submitted'] == 'yes'){ // BEGIN submit handling if() $ninja_annc_id = $_REQUEST['ninja_annc_id']; $ninja_annc_begindate = $_REQUEST['begindate']; $ninja_annc_begintimehr = $_REQUEST['begintimehr']; $ninja_annc_begintimemin = $_REQUEST['begintimemin']; $ninja_annc_begintimeampm = $_REQUEST['begintimeampm']; $ninja_annc_enddate = $_REQUEST['enddate']; $ninja_annc_endtimehr = $_REQUEST['endtimehr']; $ninja_annc_endtimemin = $_REQUEST['endtimemin']; $ninja_annc_endtimeampm = $_REQUEST['endtimeampm']; $ninja_annc_begindate = $ninja_annc_begindate.' '.$ninja_annc_begintimehr.':'.$ninja_annc_begintimemin.$ninja_annc_begintimeampm; $ninja_annc_enddate = $ninja_annc_enddate.' '.$ninja_annc_endtimehr.':'.$ninja_annc_endtimemin.$ninja_annc_endtimeampm; //echo "begin before ".$ninja_annc_begindate; //echo "end before ".$ninja_annc_enddate; $ninja_annc_message = stripslashes($_REQUEST['content']); $ninja_annc_active = 1; $ninja_annc_location = $_REQUEST['ninja_annc_location']; $ninja_annc_begindate = strtotime($ninja_annc_begindate); $ninja_annc_enddate = strtotime($ninja_annc_enddate); //echo "begin after ".$ninja_annc_begindate; //echo "end after ".$ninja_annc_enddate; if($_REQUEST['ignoredates'] == "checked"){ $ninja_annc_begindate = 0; $ninja_annc_enddate = 0; } if($_REQUEST['ninja_annc_id'] == 'new'){ $rows_affected = $wpdb->insert( $ninja_annc_table_name, array( 'begindate' => $ninja_annc_begindate, 'enddate' => $ninja_annc_enddate, 'message' => $ninja_annc_message, 'active' => '0', 'location' => $ninja_annc_location ) ); }else{ $wpdb->update( $ninja_annc_table_name, array( 'begindate' => $ninja_annc_begindate, 'enddate' => $ninja_annc_enddate, 'message' => $ninja_annc_message, 'location' => $ninja_annc_location ), array( 'id' => $ninja_annc_id )); } echo "<script language='javascript'>window.location = '".$admin_url."'</script>"; } // END submit handling if() //This if...else() statement handles the nuts and bolts of our html output. //Eventually it will be replaced by a switch(). //Flow goes:Edit Announcement? ->New Announcement? ->Table. //This part of our If...else statement creates the editing HTML if($_REQUEST['action'] == 'edit') { //BEGIN edit handling if() $ninja_annc_id = $_REQUEST['ninja_annc_id'];- $ninja_annc_row = $wpdb->get_row("SELECT * FROM $ninja_annc_table_name WHERE id = $ninja_annc_id", ARRAY_A );+ $ninja_annc_row = $wpdb->get_row( + $wpdb->prepare( "SELECT * FROM $ninja_annc_table_name WHERE id = %d", $ninja_annc_id), ARRAY_A ); $ninja_annc_id = $ninja_annc_row['id']; $ninja_annc_location = $ninja_annc_row['location']; $ninja_annc_message = stripslashes($ninja_annc_row['message']); $ninja_annc_begin = $ninja_annc_row['begindate']; $ninja_annc_end = $ninja_annc_row['enddate']; $rightnow = current_time ("timestamp"); if($ninja_annc_end != 0){ $ninja_annc_begindate = date("m/d/Y", $ninja_annc_begin); $ninja_annc_begintimehr = date("g", $ninja_annc_begin); $ninja_annc_begintimemin = date("i", $ninja_annc_begin); $ninja_annc_begintimeampm = date("a", $ninja_annc_begin); $ninja_annc_enddate = date("m/d/Y", $ninja_annc_end); $ninja_annc_endtimehr = date("g", $ninja_annc_end); $ninja_annc_endtimemin = date("i", $ninja_annc_end); $ninja_annc_endtimeampm = date("a", $ninja_annc_end); }else{ $ninja_annc_ignore = 1; $ninja_annc_begindate = date("m/d/Y", $rightnow); $ninja_annc_enddate = date("m/d/Y", $rightnow); } //$ninja_annc_begindate = $ninja_annc_begindate.' '.$ninja_annc_begintimehr.':'.$ninja_annc_begintimemin.$ninja_annc_begintimeampm; //echo $ninja_annc_begindate; wp_tiny_mce ( false, // true makes the editor "teeny" array( "theme_advanced_path" => false ) ); wp_tiny_mce_preload_dialogs (); ?> <div class="wrap"> <div id="ninja_annc_options_edit"class="icon32"><br></div> <h2 id="opener">Edit Announcement - ID: <?php echo $ninja_annc_id;?></h2> <form name=""action=""method="post"> <input type="hidden"name="submitted"value="yes"> <input type="hidden"name="ninja_annc_id"value=" <?php echo $ninja_annc_id;?>"> Ignore Dates:<input type="checkbox"name="ignoredates"id="ignoredates"value="checked" <?php if($ninja_annc_ignore == 1){ echo "checked";}?>><br> Begin Date:<input type="text"class="date"name="begindate"id="begindate"value=" <?php echo $ninja_annc_begindate;?>" <?php if($ninja_annc_ignore == 1){ echo "style='background-color:gray' disabled";}?>> Time: <select name="begintimehr"id="begintimehr"class="time" <?php if($ninja_annc_ignore == 1){ echo "style='background-color:gray' disabled";}?>> <?php $x = 1; while($x <= 12){ echo "<option"; if($x <= 9){ echo "value = '0$x'"; }else{ echo "value = '$x'"; } if($ninja_annc_begintimehr == $x){ echo "selected"; }elseif($x == 12 && $ninja_annc_ignore == 1){ echo "selected"; } echo ">$x</option>"; $x++; } ?>...snip... ?> |
DetailsAffected Software:WordPress Core Fixed in Version:2.8 Issue Type:Cross Site Scripting Original Code:Found Here DetailsThis week’s bug was subtle. The patch submitted by the developer addresses an XSS bug. Looking at the diff,we see that $title and $selection come from the query string. These values are fixed up before being assigned to a variable. The developers changed the way $title is assigned in the diff. It’s difficult to see why $title needs to be changed,so we’ll ignore that change for now. $selection gives some hints towards XSS. $selection is assigned from the query string value $_GET[‘s’] which is sent through the trim() and aposfix() functions. Immediately following the $selection variable assignment we see the $selection being manipulated with some HTML tags. This is a good indication that $selection will eventually be used to build HTML markup. I usually recommend developers encode/sanitize values as close to the point of consumption as possible,but this case is different. $selection contains HTML tags (the <p>and </p>tags),so encoding the whole value at the point of consumption will also encode those tags. The <p>tags are probably designed to be rendered by the browser. Due to the mixing of HTML tags and regular data,the developer will have to encode the user controlled values during variable assignment. This is exactly what the WordPress developers did in the patch. Developers Solution<?phprequire_once('admin.php');header('Content-Type:' . get_option('html_type') . ';charset=' . get_option('blog_charset'));if ( ! current_user_can('edit_posts') )wp_die( __( 'Cheatin’uh?' ) );function aposfix($text){$translation_table[chr(34)] = '"';$translation_table[chr(38)] = '&';$translation_table[chr(39)] = ''';return preg_replace("/&(?![A-Za-z]{0,4}\w{2,3};|#[0-9]{2,3};)/","&",strtr($text,$translation_table))}function press_it(){// define some basic variables$quick['post_status'] = 'draft';// set as draft first$quick['post_category'] = isset($_REQUEST['post_category']) ? $_REQUEST['post_category']:null;$quick['tax_input'] = isset($_REQUEST['tax_input']) ? $_REQUEST['tax_input']:'';$quick['post_title'] = isset($_REQUEST['title']) ? $_REQUEST['title']:'';$quick['post_content'] = '';// insert the post with nothing in it,to get an ID$post_ID = wp_insert_post($quick,true);$content = isset($_REQUEST['content']) ? $_REQUEST['content']:'';$upload = false;if( !empty($_REQUEST['photo_src']) &¤t_user_can('upload_files') )foreach( (array) $_REQUEST['photo_src'] as $key =>$image)// see if files exist in content - we don't want to upload non-used selected files.if( strpos($_REQUEST['content'],$image) !== false ){$desc = isset($_REQUEST['photo_description'][$key]) ? $_REQUEST['photo_description'][$key]:'';$upload = media_sideload_image($image,$post_ID,$desc);// Replace the POSTED content <img>with correct uploaded ones. Regex contains fix for Magic Quotesif( !is_wp_error($upload) ) $content = preg_replace('/<img ([^>]*)src=\\\?(\"|\')'.preg_quote($image,'/').'\\\?(\2)([^>\/]*)\$http_status_codes=array( 100 =>'Continue', 101 =>'Switching Protocols', 102 =>'Processing', 200 =>'OK', 201 =>'Created', 202 =>'Accepted', 203 =>'Non-Authoritative Information', 204 =>'No Content', 205 =>'Reset Content', 206 =>'Partial Content', 207 =>'Multi-Status', 300 =>'Multiple Choices', 301 =>'Moved Permanently', 302 =>'Found', 303 =>'See Other', 304 =>'Not Modified', 305 =>'Use Proxy', 306 =>'unused', 307 =>'Temporary Redirect', 400 =>'Bad Request', 401 =>'Authorization Required', 402 =>'Payment Required', 403 =>'Forbidden', 404 =>'Not Found', 405 =>'Method Not Allowed', 406 =>'Not Acceptable', 407 =>'Proxy Authentication Required', 408 =>'Request Time-out', 409 =>'Conflict', 410 =>'Gone', 411 =>'Length Required', 412 =>'Precondition Failed', 413 =>'Request Entity Too Large', 414 =>'Request-URI Too Large', 415 =>'Unsupported Media Type', 416 =>'Requested Range Not Satisfiable', 417 =>'Expectation Failed', 418 =>'unused', 419 =>'unused', 420 =>'unused', 421 =>'unused', 422 =>'Unprocessable Entity', 423 =>'Locked', 424 =>'Failed Dependency', 425 =>'No code', 426 =>'Upgrade Required', 500 =>'Internal Server Error', 501 =>'Method Not Implemented', 502 =>'Bad Gateway', 503 =>'Service Temporarily Unavailable', 504 =>'Gateway Time-out', 505 =>'HTTP Version Not Supported', 506 =>'Variant Also Negotiates', 507 =>'Insufficient Storage', 508 =>'unused', 509 =>'unused', 510 =>'Not Extended',);$err_status_codes = array('100'=>array('Continue',''),'101'=>array('Switching Protocols',''),'102'=>array('Processing',''),'200'=>array('OK',''),'201'=>array('Created',''),'202'=>array('Accepted',''),'203'=>array('Non-Authoritative Information',''),'204'=>array('No Content',''),'205'=>array('Reset Content',''),'206'=>array('Partial Content',''),'207'=>array('Multi-Status',''),'300'=>array('Multiple Choices',''),'301'=>array('Moved Permanently','The document has moved <a href="THEREQUESTURI">here</a>.'),'302'=>array('Found','The document has moved <a href="THEREQUESTURI">here</a>.'),'303'=>array('See Other','The answer to your request is located <a href="THEREQUESTURI">here</a>.'),'304'=>array('Not Modified',''),'305'=>array('Use Proxy','This resource is only accessible through the proxy THEREQUESTURIYou will need to configure your client to use that proxy.'),'307'=>array('Temporary Redirect','The document has moved <a href="THEREQUESTURI">here</a>.'),'400' =>array('Bad Request','Your browser sent a request that this server could not understand.'),'401' =>array('Authorization Required','This server could not verify that you are authorized to access the document requested. Either you supplied the wrong credentials (e.g.,bad password),or your browser doesn\'t understand how to supply the credentials required.'),'402' =>array('Payment Required','INTERROR'),'403' =>array('Forbidden','You don\'t have permission to access THEREQUESTURI on this server.'),'404' =>array('Not Found','We couldn\'t find <acronym title="THEREQUESTURI">that uri</acronym>on our server,though it\'s most certainly not your fault.'),'405' =>array('Method Not Allowed','The requested method THEREQMETH is not allowed for the URL THEREQUESTURI.'),'406' =>array('Not Acceptable','An appropriate representation of the requested resource THEREQUESTURI could not be found on this server.'),'407' =>array('Proxy Authentication Required','This server could not verify that you are authorized to access the document requested. Either you supplied the wrong credentials (e.g.,bad password),or your browser doesn\'t understand how to supply the credentials required.'),'408' =>array('Request Time-out','Server timeout waiting for the HTTP request from the client.'),'409' =>array('Conflict','INTERROR'),'410' =>array('Gone','The requested resourceTHEREQUESTURIis no longer available on this server and there is no forwarding address. Please remove all references to this resource.'),'411' =>array('Length Required','A request of the requested method GET requires a valid Content-length.'),'412' =>array('Precondition Failed','The precondition on the request for the URL THEREQUESTURI evaluated to false.'),'413' =>array('Request Entity Too Large','The requested resource THEREQUESTURI does not allow request data with GET requests,or the amount of data provided in the request exceeds the capacity limit.'),'414' =>array('Request-URI Too Large','The requested URL\'s length exceeds the capacity limit for this server.'),'415' =>array('Unsupported Media Type','The supplied request data is not in a format acceptable for processing by this resource.'),'416' =>array('Requested Range Not Satisfiable',''),'417' =>array('Expectation Failed','The expectation given in the Expect request-header field could not be met by this server. The client sent <code>Expect:</code>'),'422' =>array('Unprocessable Entity','The server understands the media type of the request entity,but was unable to process the contained instructions.'),'423' =>array('Locked','The requested resource is currently locked. The lock must be released or proper identification given before the method can be applied.'),'424' =>array('Failed Dependency','The method could not be performed on the resource because the requested action depended on another action and that other action failed.'),'425' =>array('No code','INTERROR'),'426' =>array('Upgrade Required','The requested resource can only be retrieved using SSL. The server is willing to upgrade the current connection to SSL,but your client doesn\'t support it. Either upgrade your client,or try requesting the page using https://'),'500' =>array('Internal Server Error','INTERROR'),'501' =>array('Method Not Implemented','GET to THEREQUESTURI not supported.'),'502' =>array('Bad Gateway','The proxy server received an invalid response from an upstream server.'),'503' =>array('Service Temporarily Unavailable','The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.'),'504' =>array('Gateway Time-out','The proxy server did not receive a timely response from the upstream server.'),'505' =>array('HTTP Version Not Supported','INTERROR'),'506' =>array('Variant Also Negotiates','A variant for the requested resource <code>THEREQUESTURI</code>is itself a negotiable resource. This indicates a configuration error.'),'507' =>array('Insufficient Storage','The method could not be performed on the resource because the server is unable to store the representation needed to successfully complete the request. There is insufficient free space left in your storage allocation.'),'510' =>array('Not Extended','A mandatory extension policy in the request is not accepted by the server for this resource.'));if (isset($_SERVER['REDIRECT_STATUS'])) $err_code = $_SERVER['REDIRECT_STATUS'];$err_req_meth = $_SERVER['REQUEST_METHOD'];$err_req = htmlentities(strip_tags($_SERVER['REQUEST_URI']));$err_phrase = $err_status_codes[$err_code][0];$err_body = str_replace( array('INTERROR','THEREQUESTURI','THEREQMETH'),array('The server encountered an internal error or misconfiguration and was unable to complete your request.',$err_req,$err_req_meth),$err_status_codes[$err_code][1]);@header("HTTP/1.1 $err_code $err_phrase",1);@header("Status:$err_code $err_phrase",1);if ( $err_code=='400'||$err_code=='403'||$err_code=='405'||$err_code[0]=='5'){@header("Connection:close",1);if ($err_code == '405') @header('Allow:GET,HEAD,POST,OPTIONS,TRACE');echo "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html>\n<head>\n<title>{$err_code}{$err_phrase}</title>\n<h1>{$err_phrase}</h1>\n<p>{$err_body}<br>\n</p>\n</body></html>"} else echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xml:lang="en"lang="en"><head><title>'.$err_code.' '.$err_phrase.'</title><meta http-equiv="content-type"content="text/html;charset=UTF-8"/></head><body><h1>'.$err_code.' '.$err_phrase.'</h1><hr /><p>'.$err_body.'<br /></p>-<pre>-'.print_r($_SERVER,1).'-</pre></body></html>';?>DetailsAffected Software:WordPress Core Fixed in Version:2.2-alpha Issue Type:Cross Site Scripting Original Code:Found Here DetailsA couple of bugs affecting WordPress core here. On line 73,we see that $_SERVER['REQUEST_URI'] is passed to add_query_arg(). From the provided code sample,it’s difficult to see that this results in XSS. The developers addressed this by encoding the return value from add_query_arg(). The second issue starts at wp_get_referer(). This function checks $_REQUEST['_wp_http_referer'] and/or $_SERVER['HTTP_REFERER'] for possible values. This makes is so the attacker has several options for tainting wp_get_referer(). Passing a tainted GET or POST parameter will do the trick. Simply redirecting to the vulnerable WordPress installation from a tainted URL will also taint the value returned from wp_get_referer(). Later,in wp_nonce_ays(),the tainted wp_get_referer() value is passed to the $adminurl variable. $adminurl is then used to build HTML markup in a couple of different places. The WP developers addressed this issue by passing wp_get_referer() before assinging that value to $adminurl. Seems like an effective fix? Well,url values are always a bit tricky. These values have to be encoded before being used to build HTML markup. URLs also have to be validated if they are being passed to a SRC,HREF,or other HTML attribute which causes a browser navigation or request. In this example,the tainted $adminurl is used to populate an A HREF value. The value is encoded,however that encoding doesn’t stop an attacker from passing a javascript:payload url… Developers Solution<?php...snip...function wp_original_referer_field(){echo '<input type="hidden"name="_wp_original_http_referer"value="' . attribute_escape(stripslashes($_SERVER['REQUEST_URI'])) . '"/>'}function wp_get_referer(){foreach ( array($_REQUEST['_wp_http_referer'],$_SERVER['HTTP_REFERER']) as $ref )if ( !empty($ref) )return $ref;return false}function wp_get_original_referer(){if ( !empty($_REQUEST['_wp_original_http_referer']) )return $_REQUEST['_wp_original_http_referer'];return false}function wp_mkdir_p($target){// from php.net/mkdir user contributed notesif (file_exists($target)){if (! @ is_dir($target))return false;elsereturn true}// Attempting to create the directory may clutter up our display.if (@ mkdir($target)){$stat = @ stat(dirname($target));$dir_perms = $stat['mode'] &0007777;// Get the permission bits.@ chmod($target,$dir_perms);return true} else{if ( is_dir(dirname($target)) )return false}// If the above failed,attempt to create the parent node,then try again.if (wp_mkdir_p(dirname($target)))return wp_mkdir_p($target);return false}...snip...function wp_nonce_ays($action){global $pagenow,$menu,$submenu,$parent_file,$submenu_file;$adminurl = get_option('siteurl') . '/wp-admin';if ( wp_get_referer() )-$adminurl = wp_get_referer();+$adminurl = attribute_escape(wp_get_referer());$title = __('WordPress Confirmation');// Remove extra layer of slashes.$_POST = stripslashes_deep($_POST );if ( $_POST ){$q = http_build_query($_POST);$q = explode( ini_get('arg_separator.output'),$q);$html .= "\t<form method='post' action='$pagenow'>\n";foreach ( (array) $q as $a ){$v = substr(strstr($a,'='),1);$k = substr($a,0,-(strlen($v)+1));$html .= "\t\t<input type='hidden' name='". attribute_escape(urldecode($k)) . "' value='". attribute_escape(urldecode($v)) . "' />\n"}$html .= "\t\t<input type='hidden' name='_wpnonce' value='". wp_create_nonce($action) . "' />\n";$html .= "\t\t<div id='message' class='confirm fade'>\n\t\t<p>". wp_specialchars(wp_explain_nonce($action)) . "</p>\n\t\t<p><a href='$adminurl'>". __('No') . "</a><input type='submit' value='". __('Yes') . "' /></p>\n\t\t</div>\n\t</form>\n"} else{-$html .= "\t<div id='message' class='confirm fade'>\n\t<p>". wp_specialchars(wp_explain_nonce($action)) . "</p>\n\t<p><a href='$adminurl'>". __('No') . "</a><a href='". add_query_arg( '_wpnonce',wp_create_nonce($action),$_SERVER['REQUEST_URI'] ) . "'>". __('Yes') . "</a></p>\n\t</div>\n";+ $html .= "\t<div id='message' class='confirm fade'>\n\t<p>". wp_specialchars(wp_explain_nonce($action)) . "</p>\n\t<p><a href='$adminurl'>". __('No') . "</a><a href='". attribute_escape(add_query_arg( '_wpnonce',wp_create_nonce($action),$_SERVER['REQUEST_URI'] )) . "'>". __('Yes') . "</a></p>\n\t</div>\n"}$html .= "</body>\n</html>";wp_die($html,$title)}function wp_die($message,$title = ''){global $wp_locale;header('Content-Type:text/html;charset=utf-8');if ( empty($title) )$title = __('WordPress ›Error');if ( strstr($_SERVER['PHP_SELF'],'wp-admin') )$admin_dir = '';else$admin_dir = 'wp-admin/';?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"<?php if ( function_exists('language_attributes') ) language_attributes();?>><head><title><?php echo $title ?></title><meta http-equiv="Content-Type"content="text/html;charset=utf-8"/><link rel="stylesheet"href="<?php echo $admin_dir;?>install.css"type="text/css"/><?php if ( ('rtl' == $wp_locale->text_direction) ):?><link rel="stylesheet"href="<?php echo $admin_dir;?>install-rtl.css"type="text/css"/><?php endif;?></head><body><h1 id="logo"><img alt="WordPress"src="<?php echo $admin_dir;?>images/wordpress-logo.png"/></h1><p><?php echo $message;?></p></body></html>...snip...DetailsAffected Software:StatPressCN Fixed in Version:1.9.1 Issue Type:SQL Injection Original Code:Found Here DetailsThis patch was full of interesting tidbits. First,the change log for this patch is as follows: **1.9.1** + fix a flaw allowing a remote cross-site scripting attack
Keep the change list description in mind as we go over the patch submitted by the developers. The submitted patch is pretty simple. There is an additional qualifier set for an if statement that checks to see if $_GET["where$i"] is contained within array $f. It’s difficult to determine whether this is true… but it doesn’t really matter. The second change is an addslashes to $_GET["what$i"] before using the tainted query string parameter to build a dynamic SQL statement. This is to prevent an obvious SQL injection bug in the LIKE operator of the SQL statement. What’s surprising is the developer missed the $_GET["where$i"] query string parameter used to build the SQL statement on the same line. This bug is equally devastating and results in SQL injection against the application. So despite the change log description,this patch is to address a SQL injection bug,NOT an XSS. Looking through the rest of the code,we see XSS (lines 7-9 and 17) and SQL injection bugs (lines 57,65,77) littered throughout the code base. These bugs still exist in the latest version,are not patched,and put users at risk. If you have this plug-in installed,your server and users are at significant risk! Developers Solution </table> <br> <table> <tr><td><table><tr><td><input type=checkbox name=oderbycount value=checked <?php print $_GET['oderbycount'] ?>><?php _e('sort by count if grouped','statpresscn');?></td></tr><tr><td><input type=checkbox name=spider value=checked <?php print $_GET['spider'] ?>><?php _e('include spiders/crawlers/bot','statpresscn');?></td></tr><tr><td><input type=checkbox name=feed value=checked <?php print $_GET['feed'] ?>><?php _e('include feed','statpresscn');?></td></tr></table></td><td width=15></td><td><table><tr> <td><?php _e('Limit results to','statpresscn');?> <select name=limitquery><?php if($_GET['limitquery'] >0){print "<option>".$_GET['limitquery']."</option>"} ?><option>200</option><option>150</option><option>50</option></select> </td></tr><tr><td> </td></tr><tr> <td align=right><input type=submit value=<?php _e('Search','statpresscn');?>name=searchsubmit></td></tr></table></td> </tr> </table><!-- It's strange that the page value should be spc-search,and not others. --> <input type=hidden name=page value='spc-search'><input type=hidden name=statpress_action value=search> </form><br> <?php if(isset($_GET['searchsubmit'])){ # query builder $qry=""; # FIELDS $fields=""; for($i=1;$i<=5;$i++){- if($_GET["where$i"] != ''){+if($_GET["where$i"] != '' &&array_key_exists($_GET["where$i"],$f)){//??where?????? $fields.=$_GET["where$i"].",";} } $fields=rtrim($fields,","); # WHERE $where="WHERE 1=1"; if($_GET['spider'] != 'checked'){$where.="AND spider=''";} if($_GET['feed'] != 'checked'){$where.="AND feed=''";} for($i=1;$i<=5;$i++){if(($_GET["what$i"] != '') &&($_GET["where$i"] != '')){- $where.="AND ".$_GET["where$i"]."LIKE '%".$_GET["what$i"]."%'";+$where.="AND ".$_GET["where$i"]."LIKE '%".addslashes($_GET["what$i"])."%'";//addslashes?????? } } # ORDER BY $orderby=""; for($i=1;$i<=5;$i++){if(($_GET["sortby$i"] == 'checked') &&($_GET["where$i"] != '')){$orderby.=$_GET["where$i"].',';} } # GROUP BY $groupby=""; for($i=1;$i<=5;$i++){if(($_GET["groupby$i"] == 'checked') &&($_GET["where$i"] != '')){$groupby.=$_GET["where$i"].',';} } if($groupby != ''){$grouparray = explode(",",rtrim($groupby,','));$groupby="GROUP BY ".rtrim($groupby,',');$fields.=",count(*) as totale";if($_GET['oderbycount'] == 'checked'){$orderby="totale DESC,".$orderby;} } if($orderby != ''){$orderby="ORDER BY ".rtrim($orderby,',');} $limit="LIMIT ".$_GET['limitquery']; # Results print "<h2>".__('Results','statpresscn')."</h2>"; $sql="SELECT $fields FROM $table_name $where $groupby $orderby $limit;"; //print "$sql<br>"; print "<table class='widefat'><thead><tr>"; for($i=1;$i<=5;$i++){if($_GET["where$i"] != ''){print "<th scope='col'>";if((count($grouparray)>0)&&in_array($_GET["where$i"],$grouparray)){print "<font color=red>";} print ucfirst($f[$_GET["where$i"]]);if((count($grouparray)>0)&&in_array($_GET["where$i"],$grouparray)){print "</font>";} print "</th>";} } if($groupby != ''){print "<th scope='col'><font color=red>".__('Count','statpresscn')."</font></th>";} print "</tr></thead><tbody id='the-list'>"; $qry=$wpdb->get_results($sql,ARRAY_N); $cloumnscount = count($wpdb->get_col_info("name")); foreach ($qry as $rk){print "<tr>";for($i=1;$i<=$cloumnscount;$i++){print "<td>";if($_GET["where$i"] == 'urlrequested'){print "<a href=".heart5_config_url($rk[$i-1])."target=_heart5>";print iri_StatPress_Decode($rk[$i-1]);print "</a>";} else{print $rk[$i-1];}// print $rk[$i-1];print "</td>";} print "</tr>"; } print "</table>"; print "<br /><br /><font size=1 color=gray>sql:$sql</font>"; }?></div>DetailsAffected Software:Lazyest-Gallery Fixed in Version:0.9 Issue Type:Cross Site Scripting (XSS) Original Code:Found Here DetailsFor most security issues,I give the developer the benefit of the doubt. It’s tough to keep track of all the corner cases and security nuances. For this diff however,there is no excuse. First,let’s cover what the patch fixes. On line 18,the developer was taking a tainted value passed via query string parameter and using that value to build HTML markup. This is XSS in its most classic form. Also,on line 58 the same tainted input is used to build the SRC attribute for an image tag,also resulting in XSS. The developer chose to encode both of these tainted values before using them in the HTML output. Now,let’s talk about the problems with this patch. First,the tainted value used to build the SRC attribute for an image tag needs additional validation. SRC attributes are tricky as they usually cause the browser to issue a request. Escaping the tainted SRC value only prevents the attacker from breaking out of the attribute and injecting their own HTML. Escaping doesn’t prevent the attacker from passing a well formed URI like javascript:javascript-payload-here. I can let the developer slide on this one… chalk it up as a lesson on corner cases. Now,if you look at the patched line,you’ll see that the ALT attribute for the same image tag also contains a XSS vulnerability. Yes,the developer missed a XSS vulnerability that is less than 5 characters away from a fixed XSS vulnerability. This also shows that the developer never tested the patch. The tainted query string parameter is the same for all the vulnerable sections. If the developer tried to test this patch,they would have discovered they were still exposed… Developers Solution<?php// Don't remove this lines:require_once('../../../wp-blog-header.php');global $lg_gallery;?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN""http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type"content="<?php bloginfo('html_type');?>;charset=<?php bloginfo('charset');?>"/><meta name="generator"content="WordPress <?php bloginfo('version');?>"/>-<title><?php echo $_GET['image'] ?></title>+<title><?php echo esc_html($_GET['image']) ?></title><style type="text/css">body{text-align:center;margin:0;padding:0}img{border:none}</style><script type="text/javascript">function WinWidth(){if (window.innerWidth!=window.undefined) return window.innerWidth;if (document.compatMode=='CSS1Compat') return document.documentElement.clientWidth;if (document.body) return document.body.clientWidth;return window.undefined}function WinHeight(){if (window.innerHeight!=window.undefined) return window.innerHeight;if (document.compatMode=='CSS1Compat') return document.documentElement.clientHeight;if (document.body) return document.body.clientHeight;return window.undefined}function FitPic(){iWidth=WinWidth();iHeight=WinHeight();iWidth = document.images[0].width - iWidth;iHeight = document.images[0].height - iHeight;window.resizeBy((iWidth),(iHeight))self.focus()} </script></head><body onload="FitPic()"><a href="javascript:self.close()"title="<?php _e('Click to close',$lg_text_domain);?>">-<img src="<?php echo str_replace("","%20",$lg_gallery->address.$_GET['folder'].$_GET['image']);?>"alt="<?php echo $_GET['image'];?>"/>+<img src="<?php echo str_replace("","%20",$lg_gallery->address.esc_attr($_GET['folder']).esc_attr($_GET['image']));?>"alt="<?php echo $_GET['image'];?>"/></a></body></html><?php?>DetailsAffected Software:WPhone Plug-in Fixed in Version:1.5.2 Issue Type:Cross Site Scripting (XSS) Original Code:Found Here DetailsThis bug is a straightforward XSS bug. Once again,we see the familiar $_SERVER['PHP_SELF'] variable being echoed back to the user without any encoding. The fix is simple,remove the value for the ACTION form attribute completely. This removes the need for any type of sanitization and ensures the form is POSTed to the URL that is hosting the form. On a side note,many developers reduce the testing/defenses implemented in web pages designed for mobile clients. For some reason,it’s tempting to assume web pages designed for mobile applications have less exposure. Less exposure is obviously not the case;web pages designed for mobile clients have just as much exposure as web pages designed for normal web browsers. Please ensure your security diligence and security test cases cover your mobile attack surface. Just because the devices are smaller,that doesn’t make your attack surface is smaller too! Developers Solution<?php# Visit this file in your browser to simulate a mobile device's screensize via an <iframe>$devices = array('iphone_p' =>array('type' =>'iPhone:portrait (320x480)','width' =>320,'height' =>480),'iphone_l' =>array('type' =>'iPhone:landscape (480x320)','width' =>480,'height' =>320),'moto' =>array('type' =>'Motorola phone/browser (RAZR,v551,etc)','width' =>176,'height' =>220),'n80' =>array('type' =>'Nokia N80 (N60WebKit)','width' =>352,'height' =>416));if ( (int) $_REQUEST['w'] &&(int) $_REQUEST['h'] ){$choice = array('type' =>"Custom size ({$_REQUEST['w']}x{$_REQUEST['h']})",'width' =>$_REQUEST['w'],'height' =>$_REQUEST['h'])}elseif ( $devices[$_REQUEST['d']] )$choice = $devices[$_REQUEST['d']];else $choice = $devices['iphone_p'];?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type"content="text/html;charset=UTF-8"><title>WPhone iFramer test tool:<?php echo $choice['type'];?></title></head><body>-<form action="<?php echo $_SERVER['PHP_SELF'];?>"method="get">+ <form action=""method="get"><label for="h">CHOOSE</label><select name="d"id="d"><option></option><?phpforeach ( $devices as $this_d_key =>$this_d ){$selected = ( $_REQUEST['d'] == $this_d_key ) ? 'selected':'';echo '<option value="' . $this_d_key . '"' . $selected . '>' . $this_d['type'] . '</option>' . "\n\t\t\t"}echo "\n";?></select><br />OR INPUT<label for="w">Width</label><input type="text"name="w"id="w"value=""size="5"/>x<label for="h">Height</label><input type="text"name="h"id="h"value=""size="5"/><br /><input type="submit"name="submit"value="view"/></form><h2><?php echo $choice['type'];?></h2><iframe src="../../../wp-login.php"width="<?php echo $choice['width'];?>"height="<?php echo $choice['height'];?>">your browser does not support iframes.</iframe></body></html>DetailsAffected Software:Comment-Rating Plugin Fixed in Version:2.9.24 Issue Type:SQL Injection (SQLi) Original Code:Found Here DetailsThis week’s vulnerability was a tricky one. The bug patched in this change list affected the Comment-Rating plugin for WordPress (fixed in 2.9.24). Let’s take the bug step by step. First,the application takes a user/attacker supplied value and runs it through an escaping function here (line 9): $k_id = strip_tags($wpdb->escape($_GET['id'])); So,$k_id is now tainted and contains an escaped value provided by the attacker. A few lines later,we see the following code: if($k_id &&$k_action &&$k_path){ //Check to see if the comment id exists and grab the rating $query = “SELECT * FROM `$table_name` WHERE ck_comment_id = $k_id”; $result = mysql_query($query); The code above checks for a specific condition (which is a condition controllable by the attacker) then proceeds to build and execute a SQL query. On line 22 we see $k_id is used to build a dynamic SQL statement. Variables usage within stings are valid in PHP (http://php.net/manual/en/language.types.string.php –see Variable parsing). $k_id is escaped so we should be ok here…right? Actually,in this case escaping isn’t sufficient to prevent SQL injection. Escaping functions typically work by preventing a variable value from breaking out of quotes,unfortunately in this case there are no quotes to break out of. $k_id is designed to be a numeric value not a string,so there is no need to encapsulate the $k_id value in quotes. Although $k_id is designed to be numeric,there was nothing that would prevent an attacker from providing an arbitrary value for $k_id. For example,an attacker could provide a value like this for $k_id: 99999 union select uname,passwd from users As you can see,there are no special characters (double quotes,single quotes,or database escape characters) in the string above that would have been escaped by a database escaping function. When used to build the $query variable,we end up with: $query = “SELECT * FROM `$table_name` WHERE ck_comment_id = 99999 union select uname,passwd from users“; The developers addressed this vulnerability by validating that $k_id is indeed numeric before using the value to build a dynamic SQL statement. Developers Solution<?phprequire_once('../../../wp-config.php');require_once('../../../wp-includes/functions.php');// CSRF attack protection. Check the Referal field to be the same// domain of the script$k_id = strip_tags($wpdb->escape($_GET['id']));$k_action = strip_tags($wpdb->escape($_GET['action']));$k_path = strip_tags($wpdb->escape($_GET['path']));$k_imgIndex = strip_tags($wpdb->escape($_GET['imgIndex']));+// prevent SQL injection+if (!is_numeric($k_id)) die('error|Query error');$table_name = $wpdb->prefix . 'comment_rating';$comment_table_name = $wpdb->prefix . 'comments';if($k_id &&$k_action &&$k_path){ //Check to see if the comment id exists and grab the rating $query = "SELECT * FROM `$table_name` WHERE ck_comment_id = $k_id"; $result = mysql_query($query);if(!$result){die('error|mysql:'.mysql_error());} if(mysql_num_rows($result)){$duplicated = 0;// used as a counter to off set duplicated votes if($row = @mysql_fetch_assoc($result)){if(strstr($row['ck_ips'],getenv("REMOTE_ADDR"))){ // die('error|You have already voted on this item!'); // Just don't count duplicated votes $duplicated = 1; $ck_ips = $row['ck_ips']; } else{ $ck_ips = $row['ck_ips'] . ',' . getenv("REMOTE_ADDR");// IPs are separated by ',' } } $total = $row['ck_rating_up'] - $row['ck_rating_down'];if($k_action == 'add'){ $rating = $row['ck_rating_up'] + 1 - $duplicated; $direction = 'up'; $total = $total + 1 - $duplicated;} elseif($k_action == 'subtract'){ $rating = $row['ck_rating_down'] + 1 - $duplicated; $direction = 'down'; $total = $total - 1 + $duplicated;} else{ die('error|Try again later');//No action given. } if (!$duplicated){ $query = "UPDATE `$table_name` SET ck_rating_$direction = '$rating',ck_ips = '". $ck_ips . "' WHERE ck_comment_id = $k_id"; $result = mysql_query($query); if(!$result) { // die('error|query '.$query); die('error|Query error'); } // Now duplicated votes will not if(!mysql_affected_rows()) { die('error|affected '. $rating); } $karma_modified = 0; if (get_option('ckrating_karma_type') == 'likes' &&$k_action == 'add'){ $karma_modified = 1;$karma = $rating; } if (get_option('ckrating_karma_type') == 'dislikes' &&$k_action == 'subtract'){ $karma_modified = 1;$karma = $rating; } if (get_option('ckrating_karma_type') == 'both'){ $karma_modified = 1;$karma = $total; } if ($karma_modified){ $query = "UPDATE `$comment_table_name` SET comment_karma = '$karma' WHERE comment_ID = $k_id"; $result = mysql_query($query); if(!$result) die('error|Comment Query error'); } } } else{ die('error|Comment doesnt exist');//Comment id not found in db,something wrong ? }} else{ die('error|Fatal:html format error')}// Add the + sign,if ($total >0){$total = "+$total";}//This sends the data back to the js to process and show on the page// The dummy field will separate out any potential garbage that// WP-superCache may attached to the end of the return.echo("done|$k_id|$rating|$k_path|$direction|$total|$k_imgIndex|dummy");?> | |