| DetailsAffected Software:WordPress Fixed in Version:2.1 Issue Type:HTTP Header Injection Original Code:Found Here DescriptionThis week’s bug was a Set-Cookie/HTTP Header injection bug in WordPress Core. Looking at the code,we see that the WordPress Developers assign several variables directly from the $_POST body. These assignments are listed below: $comment_author = trim($_POST['author']); $comment_author_email = trim($_POST['email']); $comment_author_url = trim($_POST['url']); $comment_content = trim($_POST['comment']);
Each of these variables should now be considered tainted and should be sanitized/encoded before using their values in any operations. It seems that the WordPress developers were aware that the values assigned to the variables listed above could not be trusted and proceeded to escape the values before using them in database related operations. Some examples of the sanitization can are provided below: $comment_author = $wpdb->escape($user_identity); $comment_author_email = $wpdb->escape($user_email); $comment_author_url = $wpdb->escape($user_url);
Unfortunately,the developers utilized the incorrect sanitizing routines for the tainted values in the setcookie API. The developers used stripslashes() to sanitize the user controlled value before passing it to the setcookie() API. Although stripslashes can help prevent other types of vulnerabilities,it was not intended to defend against HTTP HEADER injection or cookie poisoning. By injecting a series of CRLF (%0d%0a) characters,an attacker could use this bug to inject arbitrary headers into the HTTP response and in some cases use this header injection bug for XSS. The WordPress developers addressed this issue by passing the attacker controlled value to the clean_url() function before allowing it in the setcookie. The full source for clean_url() can be found here,however the more interesting sanitizing routines are provided below: $url = preg_replace(‘|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\\x80-\\xff]|i’,”,$url); $strip = array(‘%0d’,‘%0a’,‘%0D’,‘%0A’); $url = _deep_replace($strip,$url); $url = str_replace(‘;//’,‘://’,$url);
Developers Solution<?phprequire( dirname(__FILE__) . '/wp-config.php' );nocache_headers();$comment_post_ID = (int) $_POST['comment_post_ID'];$status = $wpdb->get_row("SELECT post_status,comment_status FROM $wpdb->posts WHERE ID = '$comment_post_ID'");if ( empty($status->comment_status) ){do_action('comment_id_not_found',$comment_post_ID);exit} elseif ( 'closed' == $status->comment_status ){do_action('comment_closed',$comment_post_ID);die( __('Sorry,comments are closed for this item.') )} elseif ( 'draft' == $status->post_status ){do_action('comment_on_draft',$comment_post_ID);exit}$comment_author = trim($_POST['author']);$comment_author_email = trim($_POST['email']);$comment_author_url = trim($_POST['url']);$comment_content = trim($_POST['comment']);// If the user is logged inget_currentuserinfo();if ( $user_ID ):$comment_author = $wpdb->escape($user_identity);$comment_author_email = $wpdb->escape($user_email);$comment_author_url = $wpdb->escape($user_url);else:if ( get_option('comment_registration') )die( __('Sorry,you must be logged in to post a comment.') );endif;$comment_type = '';if ( get_settings('require_name_email') &&!$user_ID ){if ( 6 >strlen($comment_author_email) || '' == $comment_author )die( __('Error:please fill the required fields (name,email).') );elseif ( !is_email($comment_author_email))die( __('Error:please enter a valid email address.') )}if ( '' == $comment_content )die( __('Error:please type a comment.') );$commentdata = compact('comment_post_ID','comment_author','comment_author_email','comment_author_url','comment_content','comment_type','user_ID');wp_new_comment( $commentdata );if ( !$user_ID ):setcookie('comment_author_' . COOKIEHASH,stripslashes($comment_author),time() + 30000000,COOKIEPATH,COOKIE_DOMAIN);setcookie('comment_author_email_' . COOKIEHASH,stripslashes($comment_author_email),time() + 30000000,COOKIEPATH,COOKIE_DOMAIN);-setcookie('comment_author_url_' . COOKIEHASH,stripslashes($comment_author_url),time() + 30000000,COOKIEPATH,COOKIE_DOMAIN);+setcookie('comment_author_url_' . COOKIEHASH,stripslashes(clean_url($comment_author_url)),time() + 30000000,COOKIEPATH,COOKIE_DOMAIN);endif;$location = ( empty( $_POST['redirect_to'] ) ) ? get_permalink( $comment_post_ID ):$_POST['redirect_to'];wp_redirect( $location );?>
DetailsAffected Software:Dojox Fixed in Version:1.4.1 Issue Type:XSS Original Code: Found Here DescriptionThe code sample presented this week have a few XSS vulnerabilities. The first XSS bug is caused when $_GET[‘messId’] doesn’t match any of the switch cases. The default behavior is to throw a friendly error message indicating the messId is unknown along with the value passed via the querystring. Unfortunately,the error message can also contain attacker controlled HTML/SCRIPT resulting in XSS. The two other XSS bugs fixed by the developers in this patch are classic XSS. Both issues take user/attacker controlled variables and display the variables in markup without sanitization/encoding. Although the XSS bugs are fairly straight forward,what I find interesting in this example is why this page is here in the first place. The code don’t seem to provide any useful functionality (other than to provide a place for attackers to abuse XSS). If you were a security engineer assigned to audit this page,it might be a good idea to ask WHY this page exists in the first place. It turns out that this page is indeed a test/debugging page that is included with dojox,offering no functionality intended for production users. Think long and hard before exposing test and debug functionality in your production environment. Check production systems for example/testing/debugging functionality and disable/remove this functionality if possible. Code developed for testing and debugging rarely undergoes the scrutiny of production code and will like contain security issues. 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
| <?php // this just bounces a message as a response,and optionally emulates network latency.
// default delay is 0 sec,to change: // getResponse.php?delay=[Int milliseconds]
// to change the message returned // getResponse.php?mess=whatever%20string%20you%20want.
// to select a predefined message // getResponse.php?messId=0
error_reporting(E_ALL ^ E_NOTICE);
$delay = 1;// 1 micro second to avoid zero division in messId 2 if(isset($_GET['delay']) &&is_numeric($_GET['delay'])){ $delay = (intval($_GET['delay']) * 1000); }
if(isset($_GET['messId']) &&is_numeric($_GET['messId'])){ switch($_GET['messId']){ case 0: echo "<h3>WARNING This should NEVER be seen,delayed by 2 sec!</h3>"; $delay = 2; break; case 1: echo "<div dojotype='dijit.TestWidget'>Testing attr('href',...)</div>"; break; case 2: echo "<div dojotype='dijit.TestWidget'>Delayed attr('href',...) test</div> <div dojotype='dijit.TestWidget'>Delayed by ". ($delay/1000000) . "sec.</div>"; break; case 3: echo "IT WAS the best of times,it was the worst of times,it was the age of wisdom,it was the age of foolishness,it was the epoch of belief,it was the epoch of incredulity,it was the season of Light,it was the season of Darkness,it was the spring of hope,it was the winter of despair,we had everything before us,we had nothing before us,we were all going direct to Heaven,we were all going direct the other way -- in short,the period was so far like the present period,that some of its noisiest authorities insisted on its being received,for good or for evil,in the superlative degree of comparison only"; break; case 4: echo "There were a king with a large jaw and a queen with a plain face,on the throne of England;there were a king with a large jaw and a queen with a fair face,on the throne of France. In both countries it was clearer than crystal to the lords of the State preserves of loaves and fishes,that things in general were settled for ever."; break; case 5: echo "It was the year of Our Lord one thousand seven hundred and seventy- five. Spiritual revelations were conceded to England at that favoured period,as at this. Mrs. Southcott had recently attained her five-and- twentieth blessed birthday,of whom a prophetic private in the Life Guards had heralded the sublime appearance by announcing that arrangements were made for the swallowing up of London and Westminster. Even the Cock-lane ghost had been laid only a round dozen of years,after rapping out its messages,as the spirits of this very year last past (supernaturally deficient in originality) rapped out theirs. Mere messages in the earthly order of events had lately come to the English Crown and People,from a congress of British subjects in America:"; break; default: -echo "unknown messId:{$_GET['messId']}"; +echo "unknown messId:". htmlentities($_GET['messId']); } }
if(isset($_GET['bounceGetStr']) && $_GET['bounceGetStr']){ -echo "<div id='bouncedGetStr'>{$_SERVER["QUERY_STRING"]}</div>"; +echo "<div id='bouncedGetStr'>".htmlentities($_SERVER["QUERY_STRING"])."</div>"; }
if(isset($_GET['message']) &&$_GET['message']){ -echo $_GET['message']; +echo htmlentities($_GET['message']); }
usleep($delay);
?> |
DetailsAffected Software:WordPress (core) Fixed in Version:1.2 Issue Type:XSS Original Code: Found Here DescriptionLooking 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(‘”‘,‘&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 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
| <?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('"','&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> |
DetailsAffected Software:Members-only WordPress plugin Fixed in Version:0.6.7 Issue Type:SQL Injection Original Code: Found Here DescriptionThis week’s vuln was an easy one The vulnerability affected the “members-only” plug-in for WordPress and was patched in version 0.6.7. There are a couple changes in the diff,but the most important change (from a security standpoint) is the transition from a string-built,dynamic SQL statement to a prepared statement. The following lines show the SQL injection bug: $feedkey = $_GET['feedkey']; … <snip>… $find_feedkey = $wpdb->get_results(“SELECT umeta_id FROM $wpdb->usermeta WHERE meta_value = ‘$feedkey’”);
The members-only developers assigned a value for the $feedkey variable directly from the querystring. They then used the attacker controlled value to build a SQL statement,using the $feedkey value to complete the SQL WHERE clause. The symptoms presented above are classic SQL injection. I’m happy to see that the members-only developers chose to use prepared statements to fix the SQL injection. There are a few other fixes here,but the SQL injection was the most important security fix. 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
| -if (empty($userdata->ID) &&$members_only_opt['feed_access'] != 'feednone') //Check if user is logged in or Feed Keys is required +if (empty($userdata->ID) || $members_only_opt['feed_access'] != 'feednone') //Check if user is logged in or Feed Keys is required { $feedkey = $_GET['feedkey']; if (!empty($feedkey)) { // Check if Feed Key is in the Database -$find_feedkey = $wpdb->get_results("SELECT umeta_id FROM $wpdb->usermeta WHERE meta_value = '$feedkey'"); +$find_feedkey = $wpdb->get_results( $wpdb->prepare( + "SELECT umeta_id FROM $wpdb->usermeta WHERE meta_value = %s", + $feedkey +) );
if (!empty($find_feedkey) &&$members_only_opt['feed_access'] == 'feedkeys') //If Feed Key is found and using Feed Keys { $feedkey_valid = TRUE; if (empty($feedkey) &&$members_only_opt['feed_access'] == 'feedkeys') { $feed = members_only_create_feed('No Feed Key Found',$errormsg['feedkey_missing']); -header("Content-Type:application/xml;charset=ISO-8859-1"); +header("Content-Type:application/xml;charset=". get_bloginfo( 'charset' ),true); echo $feed; exit; } elseif ($feedkey_valid == FALSE &&$members_only_opt['feed_access'] == 'feedkeys') { $feed = members_only_create_feed('Feed Key is Invalid',$errormsg['feedkey_invalid']); -header("Content-Type:application/xml;charset=ISO-8859-1"); +header("Content-Type:application/xml;charset=". get_bloginfo( 'charset' ),true); echo $feed; exit; }
if (empty($feedkey) &&$members_only_opt['feed_access'] == 'feedkeys') { $feed = members_only_create_feed('No Feed Key Found',$errormsg['feedkey_missing']); -header("Content-Type:application/xml;charset=ISO-8859-1"); +header("Content-Type:application/xml;charset=". get_bloginfo( 'charset' ),true); echo $feed; exit; } |
DetailsAffected Software:PunBB Fixed in Version:1.2 Issue Type:Cross Site Scripting (XSS) Original Code: Found Here DescriptionThis 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 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
| 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 …'); }
...<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> |
DetailsAffected Software:WordPress-to-lead for Salesforce CRM Fixed in Version:1.0.5 Issue Type:Cross Site Scripting Original Code: Found Here DescriptionThis 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 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
| <?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/>'; } |
DetailsAffected Software:TimesToCome Stop Bot Registration (WordPress Plugin) Fixed in Version:1.9 Issue Type:SQL Injection Original Code: Found Here DescriptionStraight up SQL Injection vulnerability. The following attacker controlled values are taken via the following lines: $request_time = $_SERVER['REQUEST_TIME']; $http_accept = $_SERVER['HTTP_ACCEPT']; $http_user_agent = $_SERVER['HTTP_USER_AGENT']; $http_remote_addr = $_SERVER['REMOTE_ADDR'];
Then,a few lines down the attacker controlled values are used as part of a dynamic INSERT SQL statement. $sql = “INSERT INTO ”. $registration_log_table_name . ”( ip,email,problem,accept,agent,day ) VALUES ( ‘$http_remote_addr’,‘$user’,‘$error’,‘$http_accept’,‘$http_user_agent’,NOW() )”; $result = $wpdb->query( $sql );
Understanding that taking attacker controlled data and using it as part of a dynamic SQL statement is a bad idea,the plug-in developers checked in a patch to sanitize the data before using in the SQL statement. The path is shown below: $http_accept = htmlentities($http_accept); $http_user_agent = htmlentities($http_user_agent); $http_remote_addr = htmlentities($http_remote_addr); $http_request_uri = htmlentities($html_request_uri);
There are a couple curious things about this patch. First,instead of transitioning away from dynamic SQL building (which can be difficult to get just right),the authors decided to sanitize the input before passing it to the dynamic SQL. Second (and more interesting),is the authors used htmlentities() to escape the data. Htmlentities() is typically used to escape data to prevent XSS attacks,not SQL Injection. Htmlentities() takes a few parameters,one of which is the optional $quote_style parameter. The $quote_style parameter defines how strings with double and single quotes will be escaped. According to the PHP documentation the three options are: ENT_COMPAT - Will convert double-quotes and leave single-quotes alone. ENT_QUOTES - Will convert both double and single quotes. ENT_NOQUOTES –Will leave both double and single quotes unconverted
If the $quote_style is not specified,PHP will default to ENT_COMPAT. Do you think this patch will hold up to the test of time? Developers 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
| // log all requests to register on our blog function ttc_add_to_log( $user,$error) {
global $wpdb; $registration_log_table_name = $wpdb->prefix . "ttc_user_registration_log"; $request_time = $_SERVER['REQUEST_TIME']; $http_accept = $_SERVER['HTTP_ACCEPT']; $http_user_agent = $_SERVER['HTTP_USER_AGENT']; $http_remote_addr = $_SERVER['REMOTE_ADDR'];
if($wpdb->get_var("show tables like '$registration_log_table_name'") != $registration_log_table_name) {ttc_wp_user_registration_install(); }
// wtf? accept statements coming in at over 255 chars? Prevent sql errors and any funny business // by shortening anything from user to 200 chars if over 255 if ( strlen($email) >200 ){ $email = substr ($email,0,200 );} if ( strlen($http_accept ) >200 ) { $http_accept = substr ( $http_accept,0,200 );} if ( strlen($http_user_agent ) >200 ) { $http_user_agent = substr ( $http_user_agent,0,200 );}
+// clean input for database +$http_accept = htmlentities($http_accept); +$http_user_agent = htmlentities($http_user_agent); +$http_remote_addr = htmlentities($http_remote_addr); +$http_request_uri = htmlentities($html_request_uri);
$sql = "INSERT INTO ". $registration_log_table_name . "( ip,email,problem,accept,agent,day ) VALUES ( '$http_remote_addr','$user','$error','$http_accept','$http_user_agent',NOW() )"; $result = $wpdb->query( $sql ); }
// add an email to our email blacklist if we decide it is an bot function ttc_add_to_blacklist( $email ) { global $wpdb; $blacklist_table_name = $wpdb->prefix . "ttc_user_registration_blacklist";
if($wpdb->get_var("show tables like '$blacklist_table_name'") != $blacklist_table_name) { ttc_wp_user_registration_install(); }
if ( strlen($email) >200 ){ $email = substr ($email,0,200 );}
$sql = "INSERT INTO ". $blacklist_table_name . "( blacklisted ) VALUES ( '$email' )"; $result = $wpdb->query( $sql );
}
// add an ip to our ip blacklist if we decide it is a bot function ttc_add_to_ip_blacklist( $ip ) { global $wpdb; $ip_table_name = $wpdb->prefix . "ttc_ip_blacklist";
if($wpdb->get_var("show tables like '$ip_table_name'") != $ip_table_name) { ttc_wp_user_registration_install(); }
$sql = "INSERT INTO ". $ip_table_name . "( ip ) VALUES ( '$ip' )"; $result = $wpdb->query( $sql ); } |
DetailsAffected Software:The Hacker’s Diet (WordPress Plugin) Fixed in Version:0.9.7b Issue Type:SQL Injection Original Code: Found Here DescriptionThis 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 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
| <?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++; } } |
DetailsAffected Software: qTranslate plugin Fixed in Version:2.0.2 Issue Type:XSS Original Code: Found Here DescriptionWhew! 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 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 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('&','&',$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; } // &workaround $complete = str_replace('&','&',$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>"; } ?> |
DetailsAffected Software:WP Category Manager plugin Fixed in Version:1.3.1.0 Issue Type:Sql Injection Original Code: Found Here DescriptionThis 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 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 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;
?> |
|