Popular Vulnerable Code

Boundaries –SQL Injection

Details

Affected Software:My Calendar WordPress Plugin

Fixed in Version:>1.7.2

Issue Type:SQL Injection

Original Code:Found Here

Details

This week’s bug was a subtle mistake in the usage of an escaping routine. It seems the developer understood the dangers of SQL injection and therefore used an escaping routine to sanitize user controlled input before using that input to build a SQL statement. Unfortunately,the developer overlooked a crucial characteristic and used the wrong escaping routine. Looking at the vulnerable line,we see the following:

1
$sql = "SELECT * FROM " . WP_CALENDAR_CATEGORIES_TABLE . "WHERE category_id=".mysql_escape_string($_GET['category_id']);

As you can clearly see,the developer chose to utilize the mysql_escape_string() function to escape $_GET[‘category_id] before using category_id to build a SQL statement. Looking at the documentation (http://php.net/manual/en/function.mysql-escape-string.php) for mysql_escape_string(),we see that the specific characters escaped are:null byte (0),newline (\n),carriage return (\r),backslash (\),single quote (‘),double quote (“) and substiture (SUB,or \032). In this case,none of these characters are required in order for SQL injection to be successful. The user controlled $_GET[‘category_id’] is not enclosed in quotes,so there is no need to break out of quotes for SQL injection. For example,the attacker can pass the following:

http://path-to-server/calendar.php? category_id=1%20union%20select%201,2,3,4,5,6%20from%20users;

This would result in the following SQL statement:
SELECT * FROM WP_CALENDAR_CATEGORIES_TABLE WHERE category_id=1 union select 1,2,3,4,5,6 from users;

As you can see,the attacker can craft a valid SQL injection without using any of the characters escaped by mysql_escape_string(). The developers addressed this issue by casting the $_GET[‘category_id’] to an int before using it in a SQL statement.

If you look closely…you’ll see other,unpatched SQL injections with the same symptom littered throughout the code…

Vulnerable Code

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
...snip...
   
</style>
<?php
 // We do some checking to see what we're doing
 if (isset($_POST['mode']) && $_POST['mode'] == 'add')
  {
   // Proceed with the save  
   $sql = "INSERT INTO " . WP_CALENDAR_CATEGORIES_TABLE . "SET category_name='".mysql_escape_string($_POST['category_name'])."',category_colour='".mysql_escape_string($_POST['category_colour'])."'";
   $wpdb->get_results($sql);
   echo "<div class=\"updated\"><p><strong>".__('Category added successfully','calendar')."</strong></p></div>";
  }
 else if (isset($_GET['mode']) && isset($_GET['category_id']) && $_GET['mode'] == 'delete')
  {
   $sql = "DELETE FROM " . WP_CALENDAR_CATEGORIES_TABLE . "WHERE category_id=".mysql_escape_string($_GET['category_id']);
   $wpdb->get_results($sql);
   $sql = "UPDATE " . WP_CALENDAR_TABLE . "SET event_category=1 WHERE event_category=".mysql_escape_string($_GET['category_id']);
   $wpdb->get_results($sql);
   echo "<div class=\"updated\"><p><strong>".__('Category deleted successfully','calendar')."</strong></p></div>";
  }
 else if (isset($_GET['mode']) && isset($_GET['category_id']) && $_GET['mode'] == 'edit' && !isset($_POST['mode']))
  {
   $sql = "SELECT * FROM " . WP_CALENDAR_CATEGORIES_TABLE . "WHERE category_id=".mysql_escape_string($_GET['category_id']);
   $cur_cat = $wpdb->get_row($sql);
   ?>
<div class="wrap">
  <h2><?php _e('Edit Category','calendar'); ?></h2>
  <form name="catform"id="catform"class="wrap"method="post"action="<?php echo bloginfo('wpurl'); ?>/wp-admin/admin.php?page=calendar-categories">
        <input type="hidden"name="mode"value="edit"/>
        <input type="hidden"name="category_id"value="<?php echo stripslashes($cur_cat->category_id) ?>"/>
        <div id="linkadvanceddiv"class="postbox">
            <div style="float:left;width:98%;clear:both;"class="inside">
    <table cellpadding="5"cellspacing="5">
                <tr>
    <td><legend><?php _e('Category Name','calendar'); ?>:</legend></td>
                <td><input type="text"name="category_name"class="input"size="30"maxlength="30"value="<?php echo stripslashes($cur_cat->category_name) ?>"/></td>
    </tr>
                <tr>
    <td><legend><?php _e('Category Colour (Hex format)','calendar'); ?>:</legend></td>
                <td><input type="text"name="category_colour"class="input"size="10"maxlength="7"value="<?php echo stripslashes($cur_cat->category_colour) ?>"/></td>
                </tr>
                </table>
            </div>
            <div style="clear:both;height:1px;">&nbsp;</div>
        </div>
        <input type="submit"name="save"class="button bold"value="<?php _e('Save','calendar'); ?> &raquo;"/>
  </form>
</div>
   <?php
  }
 else if (isset($_POST['mode']) && isset($_POST['category_id']) && isset($_POST['category_name']) && isset($_POST['category_colour']) && $_POST['mode'] == 'edit')
  {
   // Proceed with the save
   $sql = "UPDATE " . WP_CALENDAR_CATEGORIES_TABLE . "SET category_name='".mysql_escape_string($_POST['category_name'])."',category_colour='".mysql_escape_string($_POST['category_colour'])."' WHERE category_id=".mysql_escape_string($_POST['category_id']);
   $wpdb->get_results($sql);
   echo "<div class=\"updated\"><p><strong>".__('Category edited successfully','calendar')."</strong></p></div>";
  }

 $get_mode = 0;
 $post_mode = 0;
 if (isset($_GET['mode'])) {
  if ($_GET['mode'] == 'edit') {
   $get_mode = 1;
  }
 }
 if (isset($_POST['mode'])) {
  if ($_POST['mode'] == 'edit') {
   $post_mode = 1;
  }
 }
 if ($get_mode != 1 || $post_mode == 1)
  {
?>

 <div class="wrap">
  <h2><?php _e('Add Category','calendar'); ?></h2>
  <form name="catform"id="catform"class="wrap"method="post"action="<?php echo bloginfo('wpurl'); ?>/wp-admin/admin.php?page=calendar-categories">
        <input type="hidden"name="mode"value="add"/>
        <input type="hidden"name="category_id"value="">
        <div id="linkadvanceddiv"class="postbox">
            <div style="float:left;width:98%;clear:both;"class="inside">
       <table cellspacing="5"cellpadding="5">
                <tr>
                <td><legend><?php _e('Category Name','calendar'); ?>:</legend></td>
                <td><input type="text"name="category_name"class="input"size="30"maxlength="30"value=""/></td>
                </tr>
                <tr>
                <td><legend><?php _e('Category Colour (Hex format)','calendar'); ?>:</legend></td>
                <td><input type="text"name="category_colour"class="input"size="10"maxlength="7"value=""/></td>
                </tr>
                </table>
            </div>
      <div style="clear:both;height:1px;">&nbsp;</div>
        </div>
        <input type="submit"name="save"class="button bold"value="<?php _e('Save','calendar'); ?> &raquo;"/>
  </form>
  <h2><?php _e('Manage Categories','calendar'); ?></h2>

Floods –SQL Injection

Details

Affected Software:Corpse C&C

Fixed in Version:?

Issue Type:SQL Injection

Original Code:Found Here

Details

This week’s bug is in Corpse C&C. SpotTheVuln reader Christina hits it right on the head,line 32 contains a ridiculous amount of SQL injection. Most of the parameters passed to the INSERT statement results in SQL injection. $id,$info,and $user are all set directly from $_GET or $_POST and are used in the SQL statement without any sanitization. Despite its name,$real_ip is also completely attacker controlled and can be used for SQL injection. Getenv(“HTTP_X_FORWARDED_FOR”) doesn’t sanitize the user controlled value in any way. For some reason,many developers assume the X-Forwarded-For header will only specify an IP address or domain name. X-Forwarded-For can contain any characters (including angle brackets,single quotes,and double quotes).

Vulnerable Code

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
<?php

$use_mysql = 1;

if ($use_mysql == 1) {
 require_once('./mysqllog.php');
 require_once('./geoipcity.inc');
}

$ip = getenv("REMOTE_ADDR");
$real_ip = getenv("HTTP_X_FORWARDED_FOR");

if (isset($_GET['id'])) {
 $id = $_GET['id'];
} else {
 $id = $_POST['id'];
}

$info = $_POST['info'];
$user = $_POST['user'];

if ($use_mysql == 1) {
 //-----------------------------------
 $gi = geoip_open('./GeoIPCity.dat', GEOIP_STANDARD);
 $record = geoip_record_by_addr($gi, $ip);
 geoip_close($gi);
 //-----------------------------------
 $info = decode_string($info);
 if(@!mysql_connect($mysql_host,$mysql_login,$mysql_pass)) {echo '<p class="err">Error. Cant connect to mysql server </p>'; }
 if(@!mysql_selectdb($mysql_db)) {echo '<p class="err">Error. Cant connect to DB</p>'; }
 $query = 'INSERT INTO pass (add_date,id,uidlog,ip_real,ip,pass,country,city,zip)
    VALUES (now(),"'. $id . '","'. $user .'","'. $real_ip . '","'. $ip .'","'. $info .'","'. $record->country_name .'","'. $record->city .'","'. $record->postal_code .'")';
 if(@!mysql_query($query)) {echo '<p class="err">Error. Cant execute query</p>';  }
}
else {
 $date = date("Y-m-d");
 $time=date("H:i:s");
 
 list($year, $month, $day) = explode('-', $date);
 $filename = "pass.$day.$month.txt";
 $log = "$info@@@@@$user@@@@@$id@@@@@$real_ip@@@@@$ip@@@@@$date@@@@@$time\n";
 $fh = fopen("logs/$filename", "a+");
 fputs($fh, $log);  
 fclose($fh);
}

function decode_string($string) {
  $bindata = '';
  for ($i=0;$i<strlen($string);$i+=2) {
    $bindata.=chr(hexdec(substr($string,$i,2)));
  }
  return addslashes($bindata);
}
?>

Shape –SQL Injection

Details

Affected Software:Zunkerbot C&C

Fixed in Version:Not Patched

Issue Type:SQL Injection

Original Code:Found Here

Details

This week’s bug affects the task.php for the Zunkerbot C&C. Looking at line 3,we see that magic quotes is set:set_magic_quotes_runtime(1);


Obviously,this was done by the malware author to prevent SQL injection attacks. Assuming everything is working correctly,a rival malware author should be able to inject any quotes to break out of existing SQL statements. Unfortunately for the Zunkerbot author,magicqoutes doesn’t cover all cases. Take for example lines 59 and 70. Here we see $s_id and $s_ip are enclosed in quotes. These values should be protected against SQL injection. $s_id and $s_ip aren’t the only variables being used in this SQL statement however. At the end of the two SQL statements is the following LIMIT clause:limit ‘.$_POST['S_RESULTS'];


$_POST['S_RESULTS'] is NOT encapsulated within quotes,therefore the attacker is free to add their own SQL without having to use any quotes. Magic quotes does not escape semicolon characters (;),so the attacker is free to stack SQL queries and run arbitrary SQL on the Zunkerbot C&C. With this SQL injection vulnerability in hand,a rival botmaster could take over vulnerable Zunkerbot botnets with a single GET request.

Vulnerable Code

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
<?php

include_once('auth.php');

set_magic_quotes_runtime(1);

if(is_readable('html.php')) include_once('html.php');
 else die('Could not find HTML library.');
 
if(is_readable('mycommon.php')) require('mycommon.php');
else die('Could not open configuration file.');

 if(is_readable('lang.php')) include_once('lang.php');
 else die('Could not find language library.');

$CTRL=1;
if(!isset($_GET['wohead']))
 include_once('head.php');

$msg = '';
$srch = '';

...<snip>...

 if(isset($_POST['S_COMPID'])){
 $srch = search_bot();
 };
 
 
 $param =array(
 "SRCH"=>$srch,
 "LAND"=>get_land($mres),
 "TASKS"=>get_task($mres),
 "MSG"=>$msg
 );
  
 
 
  
 echo HTML_TASK_ADD($param);  

//include_once('bottom.php');
//Functions++++++++++++++++++++++++++



function search_bot(){
global $mres,$_POST;

if($_POST['S_COMPID'] == '')
if($_POST['S_IP'] == '')
return '';
 



if($_POST['S_COMPID'] > ''){
 $s_id = str_replace('*',"%",$_POST['S_COMPID']);
$q = 'SELECT * FROM `bots` WHERE `FCompID` like ("'.$s_id.'") limit '.$_POST['S_RESULTS'];
 $result = mysql_query($q,$mres);
 
 return  HTML_serch_res_tbl($result);  
};
 
 
if($_POST['S_IP'] > ''){
 
 $s_ip = str_replace('*',"%",$_POST['S_IP']);
 
 $q = 'SELECT * FROM `bots` WHERE `ip_addr` like ("'.$s_ip.'") limit '.$_POST['S_RESULTS'];
 $result = mysql_query($q,$mres);
 
 return  HTML_serch_res_tbl($result);  
};


 
};



function HTML_serch_res_tbl($result){
global $LNG; 
 
  $nr = @mysql_num_rows($result);
if(!$nr)
 return "<font color=#990000>Message</font>:<em>No Entries found.</em>";
 
$ret = "<br><table width=\"543\" border=\"0\" cellpadding=\"1\" cellspacing=\"1\">"
."<tr class=\"file2\">"
."<td colspan=\"8\" class=\"bhead\"><div align=\"center\">Select Results</div></td>"
."</tr>"
."<tr class=\"file2\">"
."<td width=\"21\" bgcolor=#FCFCFC>Add</td>"
."<td width=\"26\" nowrap=\"nowrap\" bgcolor=#FCFCFC>Land</td>"
."<td width=\"82\" bgcolor=#FCFCFC nowrap=\"nowrap\">IP</td>"
."<td width=\"93\" bgcolor=#FCFCFC nowrap=\"nowrap\">Rep. Count total </td>"
."<td width=\"56\" bgcolor=#FCFCFC nowrap=\"nowrap\">Last Report</td>"
."<td width=\"100\" bgcolor=#FCFCFC nowrap=\"nowrap\">First Report</td>"
."<td width=\"40\" bgcolor=#FCFCFC nowrap=\"nowrap\">Bot Ver.</td>"
."<td width=\"44\" bgcolor=#FCFCFC nowrap=\"nowrap\">CompID</td>"
."</tr>";  
?>

Third –SQL Injection

Details

Affected Software:Ninja Announcements

Fixed in Version:1.3

Issue Type:SQL Injection

Original Code:Found Here

Details

Lots 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 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
<?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...
?>

Notes –SQL Injection

Details

Affected Software:Sermon Browser WordPress Plugin

Fixed in Version:.44

Issue Type:Cross Site Scripting

Original Code:Found Here

Details

There are a couple of different issues here,but let’s focus on what the developers patched. On line 27,the developer uses the $_GET[‘getid3’] value to build a dynamic SQL statement. This is classic SQL injection. The patch seems straight forward,escape the $_GET[‘getid3’] value before using it in the SQL statement. Normally,SQL injection involves breaking out of a predefined SQL statement by closing off a quoted string and injecting your own SQL statement. Most escaping functions escape quotes and other special characters so that an attacker cannot escape out of a quoted string. There is a problem in this patch though. The tainted value is NOT enclosed within quotes,so the attacker does not need to escape out of a quoted string. The attacker is free to build a SQL injection payload as long as the payload doesn’t contain any special characters. So,despite escaping the $_GET[‘getid3’] value,SQL injection is still possible.

Anyone spot the unpatched XSS?

Developers Solution

<?php...snip...// tags$tags = explode(',',$_POST['tags']);$wpdb->query("DELETE FROM{$wpdb->prefix}sb_sermons_tags WHERE sermon_id = $id;");foreach ($tags as $tag){$clean_tag = trim(mysql_real_escape_string($tag));$existing_id = $wpdb->get_var("SELECT id FROM{$wpdb->prefix}sb_tags WHERE name='$clean_tag'");if (is_null($existing_id)){$wpdb->query("INSERT INTO{$wpdb->prefix}sb_tags VALUES (null,'$clean_tag')");$existing_id = $wpdb->insert_id}$wpdb->query("INSERT INTO{$wpdb->prefix}sb_sermons_tags VALUES (null,$id,$existing_id)")}sb_delete_unused_tags();// everything is fine,get out of here!if(!isset($error)){sb_ping_gallery();echo "<script>document.location = '".$_SERVER['PHP_SELF']."?page=sermon-browser/sermon.php&saved=true';</script>";die()}}$id3_tags = array();if (isset($_GET['getid3'])){require_once('getid3/getid3.php');-$file_data = $wpdb->get_row("SELECT name,type FROM{$wpdb->prefix}sb_stuff WHERE id = ".$_GET['getid3']);+$file_data = $wpdb->get_row("SELECT name,type FROM{$wpdb->prefix}sb_stuff WHERE id = ".$wpdb->escape($_GET['getid3']));if ($file_data !== NULL){$getID3 = new getID3;if ($file_data->type == 'url'){$filename = substr($file_data->name,strrpos ($file_data->name,'/')+1);$sermonUploadDir = SB_ABSPATH.sb_get_option('upload_dir');$tempfilename = $sermonUploadDir.preg_replace('/([ ])/e','chr(rand(97,122))','').'.mp3';if ($tempfile = @fopen($tempfilename,'wb'))if ($remote_file = @fopen($file_data->name,'r')){$remote_contents = '';while (!feof($remote_file)){$remote_contents .= fread($remote_file,8192);if (strlen($remote_contents) >65536)  break}fwrite($tempfile,$remote_contents);fclose($remote_file);fclose($tempfile);$id3_raw_tags = $getID3->analyze(realpath($tempfilename));unlink ($tempfilename)}} else{$filename = $file_data->name;$id3_raw_tags = $getID3->analyze(realpath(SB_ABSPATH.sb_get_option('upload_dir').$filename))}if (!isset($id3_raw_tags['tags'])){echo '<div id="message"class="updated fade"><p><b>'.__('No ID3 tags found.',$sermon_domain);if ($file_data->type == 'url') echo ' Remote files must have id3v2 tags.';echo '</b></div>'}...snip...?>

Charming –XSS (uhhh wait,actually –SQL Injection)

Details

Affected Software:StatPressCN

Fixed in Version:1.9.1

Issue Type:SQL Injection

Original Code:Found Here

Details

This 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>&nbsp;</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>

Curiosity –SQL Injection

Details

Affected Software:Comment-Rating Plugin

Fixed in Version:2.9.24

Issue Type:SQL Injection (SQLi)

Original Code:Found Here

Details

This 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");?>

Money –SQL Injection

Details

Affected Software:Surfnet IDS

Fixed in Version:1.03.07

Issue Type:SQL Injection

Original Code:Found Here

Description

There were a couple of SQL injection bugs here. Beginning at line 35,we see that the Surfnet IDS developers have accepted three POST parameters and have assigned tainted values to three different variables:$keyname,$vlanid,$action. $keyname is eventually passed to three different dynamic SQL queries,all of which result in SQL injection. Those queries can be seen on lines 52,59,and 69. $vlanid is passed to a dynamic SQL query,resulting in SQL injection. This dynamic query can be seen on line 59. Finally,$action is passed to a dynamic SQL statement,resulting in yet another SQL injection bug. This dynamic query can be found on line 69. All of the bugs were straightforward SQL injection bugs and should have been caught early in the dev cycle.

The developers addressed the issue by and/or validating all of the POST parameters before using those values in SQL statements.

Developers Solution

<?php...snip...include '../include/config.inc.php';include '../include/connect.inc.php';include '../include/functions.inc.php';session_start();header("Cache-control:private");if (!isset($_SESSION['s_admin'])){pg_close($pgconn);$address = getaddress($web_port);header("location:${address}login.php");exit}$s_org = intval($_SESSION['s_org']);$s_admin = intval($_SESSION['s_admin']);$s_access = $_SESSION['s_access'];$s_access_sensor = intval($s_access{0});if ($s_access_sensor == 0){$m = 90;pg_close($pgconn);header("location:sensorstatus.php?selview=$selview&m=$m");exit}if (isset($_GET['selview'])){$selview = intval($_GET['selview'])}$error = 0;-$keyname = $_POST['keyname'];-$vlanid = $_POST['vlanid'];-$action = $_POST['action'];-if (isset($_POST[tapip])){+$keyname = pg_escape_string($_POST['keyname']);+$vlanid = intval($_POST['vlanid']);+$action = pg_escape_string($_POST['action']);+$action_pattern = '/^(NONE|REBOOT|SSHOFF|SSHON|CLIENT|RESTART|BLOCK)$/';+if (preg_match($action_pattern,$action) != 1){+ $m = 44;+ $error = 1;+}++if (isset($_POST[tapip]) &&$error != 1){$tapip = pg_escape_string(stripinput($_POST[tapip]));if (preg_match($ipregexp,$tapip)){ $sql_checkip = "SELECT tapip FROM sensors WHERE tapip = '$tapip' AND NOT keyname = '$keyname'"; $result_checkip = pg_query($pgconn,$sql_checkip); $checkip = pg_num_rows($result_checkip); if ($checkip >0){$m = 101;$error = 1; } else{$sql_updatestatus = "UPDATE sensors SET tapip = '$tapip' WHERE keyname = '$keyname' AND vlanid ='$vlanid'";$result_updatestatus = pg_query($pgconn,$sql_updatestatus);$m = 7; } } else{ $m = 102; $error = 1;}}if ($error == 0){$sql_updatestatus = "UPDATE sensors SET action = '".$action. "' WHERE keyname = '$keyname'";$result_updatestatus = pg_query($pgconn,$sql_updatestatus);$m = 7}pg_close($pgconn);if ($m != 1){header("location:sensorstatus.php?selview=$selview&m=$m&key=$keyname")} else{header("location:sensorstatus.php?selview=$selview&m=$m")}?>

Wood –SQL Injection

Details

Affected Software:WordPress Core

Fixed in Version:2.2

Issue Type:SQL Injection

Original Code:Found Here

Description

This is a fairly straight forward SQL Injection bug here. First,although we can’t see exactly where $args[] is set,we have some strong clues that it contains user/attacker controlled data. For example,the first function on the code snippet wp_newCategory() takes an $args parameter and the first thing it does is escape the values within the array. The names of variables holding various values in the array also provide clues that $args cannot be trusted.

On line 66 we see that $max_results is assigned the value from $args[4]. $max_results is then used to build a portion of a SQL string which is assigned to the $limit variable. $limit is then passed to the end of a SQL statement on line 84,resulting in SQL injection. Some readers may point out that $args is escaped in on line 60,before it is used to build any SQL statement. Unfortunately,escaping values in this case doesn’t prevent SQL injection. The attacker controlled value is eventually used to build a LIMIT clause. The LIMIT clause doesn’t enclose the attacker supplied values within quotes,so there are no quotes to break out of.

The developers addressed this issue by casting args[4] to int during assignment to $max_results. If args[4] contains any characters that do not qualify as an integer,the value will not be passed to the LIMIT statement.

Developers Solution

<?php...snip...function wp_newCategory($args){$this->escape($args);$blog_id= (int) $args[0];$username= $args[1];$password= $args[2];$category= $args[3];if(!$this->login_pass_ok($username,$password)){return($this->error)}// Set the user context and make sure they are// allowed to add a category.set_current_user(0,$username);if(!current_user_can("manage_categories",$page_id)){return(new IXR_Error(401,__("Sorry,you do not have the right to add a category.")))}// We need this to make use of the wp_insert_category()// funciton.require_once(ABSPATH . "wp-admin/admin-db.php");// If no slug was provided make it empty so that// WordPress will generate one.if(empty($category["slug"])){$category["slug"] = ""}// If no parent_id was provided make it empty// so that it will be a top level page (no parent).if ( !isset($category["parent_id"]) )$category["parent_id"] = "";// If no description was provided make it empty.if(empty($category["description"])){$category["description"] = ""}$new_category = array("cat_name"=>$category["name"],"category_nicename"=>$category["slug"],"category_parent"=>$category["parent_id"],"category_description"=>$category["description"]);$cat_id = wp_insert_category($new_category);if(!$cat_id){return(new IXR_Error(500,__("Sorry,the new category failed.")))}return($cat_id)}function wp_suggestCategories($args){global $wpdb;$this->escape($args);$blog_id= (int) $args[0];$username= $args[1];$password= $args[2];$category= $args[3];-$max_results= $args[4];+$max_results  = (int) $args[4];if(!$this->login_pass_ok($username,$password)){return($this->error)}// Only set a limit if one was provided.$limit = "";if(!empty($max_results)){$limit = "LIMIT{$max_results}"}$category_suggestions = $wpdb->get_results("SELECT cat_ID category_id,cat_name category_nameFROM{$wpdb->categories}WHERE cat_name LIKE '{$category}%'{$limit}");return($category_suggestions)}function blogger_getUsersBlogs($args){$this->escape($args);$user_login = $args[1];$user_pass = $args[2];if (!$this->login_pass_ok($user_login,$user_pass)){return $this->error}set_current_user(0,$user_login);$is_admin = current_user_can('level_8');$struct = array('isAdmin' =>$is_admin,'url' =>get_option('home') . '/','blogid'  =>'1','blogName' =>get_option('blogname'));return array($struct)}...snip...?>

Vegetables –SQL Injection

Details

Affected Software:Short URL Plugin

Fixed in Version:Changeset 55280

Issue Type:SQL Injection

Original Code:Found Here

Description

This weeks’ vulnerabilities were a couple of SQL injection bugs in the Short URL Plugin for WordPress. The symptoms for the issues indicate classic SQL injection,let’s have a quick look at the code. First,looking over the code sample,we see a couple of dynamically built SQL statements. It would probably make sense to spend a bit of time and convert these dynamic SQL statements into prepared statements,that way you won’t have to worry about a code change inadvertently re-introducing a SQL injection flaw or an escaping filter bypass. With dynamically built SQL statements we’ll also have to trace each variable until we can determine whether the value can be controlled by an attacker. Lucky for us,the variable assignments are very close to the SQL statements. In the vulnerable sample,we see that the author is taking values directly from a POST request and using those tainted values to build SQL statements. Looking at the check-in,we see that the developer chose to use WordPress’ built-in escaping function for escaping user/attacker controlled data before passing it to a SQL statement.

Although the checked-in fixes were straightforward,I was surprised to see that the developers missed an obvious SQL injection on line 56. Same classic SQL injection symptoms,the only difference is the dynamic SQL being built is a DELETE SQL statement as opposed to an INSERT or UPDATE. For those that are wondering… YES,this SQL injection is still present in the latest version of the plug-in! If you happen to be using this plug-in on your website,I would recommend you escape $delete_id before passing it to a SQL statement! I notified the plug-in author,hopefully they’ll be a patch soon.

Is this the first Spot-The-Vuln.com 0day?

Developers Solution

<?php...snip...function kd_admin_options_su(){ global $table_prefix,$wpdb,$user_ID; $table_name = $table_prefix . "short_url"; if($wpdb->get_var("show tables like '$table_name'") != $table_name){ $sql = "CREATE TABLE ".$table_name."(  link_id int(11) NOT NULL auto_increment, link_url text NOT NULL, link_desc text NOT NULL, link_count int(11) NOT NULL default '0', PRIMARY KEY (`link_id`)  );"; require_once(ABSPATH . 'wp-admin/upgrade-functions.php'); dbDelta($sql); }  if(isset($_POST['action'])){$action = $_POST['action'];if($action == "create"){- $add_url = $_POST['form_url'];- $add_desc = $_POST['form_desc'];+ $add_url = $wpdb->escape($_POST['form_url']);+ $add_desc = $wpdb->escape($_POST['form_desc']);if($add_url == "http://"|| (!$add_url)){$ERR = $ERR . "<br>You must enter a URL to redirect to!";}  if(!$ERR){$wpdb->query("INSERT INTO $table_name (link_url,link_desc) VALUES ('$add_url','$add_desc')");  $new_url = get_option("siteurl") . "/u/". mysql_insert_id();  $MES = $MES . "<br>The redirect URL has been added. Your new Short URL is:". $new_url;  } }if($action == "edit"){- $edit_id = $_POST['id'];- $edit_url = $_POST['form_url'];- $edit_desc = $_POST['form_desc'];+ $edit_id = $wpdb->escape($_POST['id']);+ $edit_url = $wpdb->escape($_POST['form_url']);+ $edit_desc = $wpdb->escape($_POST['form_desc']); if($edit_url == "http://"|| (!$edit_url)){$ERR = $ERR . "<br>You must enter a URL to redirect to!";}  if(!$ERR){$wpdb->query("UPDATE $table_name SET link_url='$edit_url',link_desc='$edit_desc' WHERE link_id = $edit_id");  $MES = $MES . "<br>The redirect URL has been modified.";  } }if($action == "delete"){ $delete_id = $_POST['id']; $wpdb->query("DELETE FROM $table_name WHERE link_id = '$delete_id'"); $MES = $MES . "<br>Redirect deleted!"; } if($action == "clearall"){ $wpdb->query("UPDATE $table_name SET link_count='0' WHERE link_count >0"); $MES = $MES . "<br>Counts have been reset!"; }}  ?> <div class=wrap> <form method="post"><h2>Short URL Admin</h2><?php if($ERR){echo "<p>". $ERR . "</p>";}if($MES){echo "<p>". $MES . "</p>";} ?><p>Short URL allows you to create shorter URL's and keeps track of how manytimes a link has been clicked. It's useful for managing downloads,keeping trackof outbound links and for masking URL's. Clicking the Clear All Clicks buttonwill reset the count for each entry. Visit the <a href="http://www.harleyquine.com/php-scripts/short-url-plugin/">plugin page</a>for more information about this plugin.</p><h2>Current Redirects</h2><table class="widefat"> <thead> <tr> <th scope="col">Short URL (The URL to use)</th> <th scope="col">Real URL (Where it redirects to)</th> <th scope="col">Notes</th> <th scope="col">Amount of Clicks</th> <th scope="col">Manage</th> </tr></thead> <tbody id="the-list"><?php  $rowdata = $wpdb->get_results("SELECT * FROM $table_name"); foreach ($rowdata as $row){ $is_editing = $_POST['edit_id']; if($is_editing){if($is_editing == $row->link_id){$EDIT = 1;$EDIT_ID = $row->link_id;$EDIT_URL = $row->link_url;$EDIT_DESC = $row->link_desc;} }?> <tr class='<?php echo $class;?>'> <th scope="row"><a href="<? echo get_option("siteurl") . "/u/". $row->link_id;?>"target="_blank"><? echo get_option("siteurl") . "/u/". $row->link_id;?></a></th> <td><? echo $row->link_url;?></td> <td><? echo $row->link_desc;?></td> <td><? echo $row->link_count;?></td> <td><form method="post"name="delete"><input type="hidden"name="action"value="delete"><input type="hidden"name="id"value="<? echo $row->link_id;?>"><input type="submit"value="Delete"></form><form method="post"name="edit"><input type="hidden"name="edit_id"value="<? echo $row->link_id;?>"><input type="submit"value="Edit"></form></td>...snip...