This blog post series is designed to be a companion to the Spotthevuln.com website(thanks to Billy Rios - @XSSniper).
Spotthevuln.com was designed to give developers more insight into designing code with security in mind.
When developers write source code they rarely think about security.
After insecure code is deployed,one of two things can happen.
- The bug can be found,in which case the developers have to waste development time in order to rewrite their solutions.
- The vulnerability is exploited,and the organization loses money,consumer trust,and can gain a negative reputation to their brand.
These problems can be avoided if the developers wrote the code correctly (securely) the first time.
Spotthevuln.com can aid developers,development managers,and QA staff by helping them sharpen their skills in spotting vulnerabilities in source code.
Spotthevuln.com use actual code snippets from open source applications to demonstrate how often vulnerable pieces of code get deployed into the real world.
The purpose is simple:
- Every Monday a vulnerable piece of code is posted.
- Every Friday the solution is posted.
On Monday, look at the piece of the code to see if you can identify what the security vulnerability is. Like everything else being able to spot vulnerable code takes practice.
Doing this exercise should take between 5 and 10 minutes out of your day. Do it while you drink your morning coffee and you will already be on your way to being able to write more secure applications.
The more secure code is,the better off we will all be.
Where SpotTheVuln focuses on developers and source code flaws, PatchTheVuln gives operational security staff more insight and experience creating active, external defenses for vulnerabilities identified within their live web applications.
Every Monday, we will provide examples of IDS/IPS/WAF rules solutions that can be used to identify and prevent expliot attempts targeting the web vulnerability discussed the prevoius week on SpotTheVuln.
Details from SpotTheVuln website:
Vulnerability Details
Affected Software: Corpse C&C
Fixed in Version: Not Patched
Issue Type: SQL Injection
Original Code: Found Here
Details
This week's bugs are in the CORPSE C&C (in the bsrv.php file). There are a couple of bugs here, most of them are very straight forward. Funny stuff first if $ver is blank, we will fail the "security check". So, in order to reach any of these vulns, we have to provide an arbitrary value for $ver. $ver is set from $_GET[ver'], so we have to provide a bsrv.php?ver=pwnd for each request in order to reach the vulnerable code. It's rigorous security checks like this that make exploitation difficult.
$id and $param are validated through a manual process (code on line 27 — 36). I don't know why the developer didn't take advantage of built-in escaping routines but the validation provided here seem to defend against common vulns. What's also puzzling, is why the other variables weren't validated/escaped. Veteran Spot the Vuln readers have seen this pattern before (escape one variable, miss the next variable) in other software, but for some reason I still find it surprising every time I see it. Now, let's get onto the actual bugs. The most obvious are $uid and $httpport which are set directly from user controlled input ($_GET). These two variables are then used to build a dynamic SQL statement. This results in SQL injection.
Additionally, $uptimem and $uptimeh are used to set $sql_uptime. $sql_uptime is then used in a dynamic SQL statement resulting in yet another SQL injection.
$browser is taken from getenv("HTTP_USER_AGENT"), which is attacker controlled (the user agent header in the HTTP request). Although $browser isn't used on this page, it's just asking for trouble :)Upon first glance, it seems like $real_ip and $socksport could be used to reach a SQL injection. After some more investigation, it is likely that the fsockopen call would probably fail before $real_ip and $socksport could be passed to a dynamic SQL statement. With that said, $real_ip and $socksport can be still be used to generate a fsockopen request to an arbitrary system (line 59).
Vulnerable Code Location(s)
<?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 checkif($ver == ''){ exit;} include_once('./mysqllog.php'); //Replace symbols$id = ereg_replace("<","‹",$id);$id = ereg_replace(">","›",$id);$id = ereg_replace("\\"",""",$id);$id = ereg_replace(";","",$id);$id = ereg_replace("%","",$id);$param = ereg_replace("<","‹",$param);$param = ereg_replace(">","›",$param);$param = ereg_replace("\\"",""",$param);$param = ereg_replace(";","",$param);$param = ereg_replace("%","",$param); /*=========================$flip = file("logs/cip.dat");$size = strlen($flip);if ($size > 10) { $arr = explode(":", $flip[0]); $aport=311;} if($arr[1] == $uid || $arr[1] == "0") { $fp = fsockopen($arr[0],$aport, $errno, $errstr, 30); fputs($fp,"IP:$ip PORT:$param $tim SOCKS:$socksport HTTP:$httpport Uptime:$uptimeh:$uptimem lang-$lang uid:$uid id:$id v:$ver\\"); fclose($fp);}//=========================*/ $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\\"); //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\\"); //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\\";} ?>
From the vulnerabilty data highlighted above, a virtual patching solution with ModSecurity would need to enforce input validation for a number of request variables. A quick google search for "bsrv.php" shows that this is example format for requests to this page:
http://www.example.com/bsrv.php?pal=0&bay=0&gold=0&id=0000¶m=1010&socksport=37350&httpport=28770&uptimem=2&uptimeh=4&uid=
After reviewing the GET parameters that are used in the vulnerable queries above, it appears that we could enforce input validation for these parameters by only allowing digit characters for:
SecRule ARGS:uid|ARGS:httpport|ARGS:uptimem|ARGS:uptimeh|ARGS:socksport "!^\d+$" \"phase:2,t:none,t:urlDecodeUni,log,block,msg:'Input Validation Error for Parameter.',logdata:'%{matched_var_name}: %{matched_var}'"
The $real_ip paramter is taken from the X-Forwarded-For request header data of the request, which can be controlled by the client. We must therefore add some input validate to that data as well to ensure that it has the proper IP address format.
SecRule REQUEST_HEADERS:x-forwarded-for "!^\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(, )?)*\b$" \"phase:1,t:none,log,block,msg:'Input Validation Error for Parameter.',logdata:'%{matched_var_name}: %{matched_var}'"