Code Review Adventures
Python 01
import hashlib
from flask import Flask,redirect
from secrets import token_hex
secret = "[....]"
app = Flask(__name__)
def sign_for_payment(payment_information):
# compute signature to ensure the payment details
# cannot be tampered with
data = secret+payment_information
return hashlib.sha256(data.encode('utf-8')).hexdigest()
@app.route('/redirect_for_payment')
def redirect_for_payment():
tx_id = token_hex(16)
payment_info = "transaction_id="+tx_id+"&amount=20.00"
params =payment_info+"&sign="+sign_for_payment(payment_info)
return redirect("https://pentesterlab.com/payment?"+params, code=302)
This code snippet is an example of cryptographic flaw of using sha2 as a hashing algorithm. SHA2 is susceptible to an attacked called length extension. A length extension attack can allow an attacker to use a Hash and the length of the message and extend the message. In the example code snippet, the sign_for_payment
function is using SHA2 to encode the hash and the redirect_for_payment
function will use it to sign payment information as a validation control measure. The weakness in this code snippet is found in the payment_info
variable of which the amount
is concatenated before encoding into hex format. By appending a duplicate amount
parameter with a nafarious value assigned, it would overwrite the initial float amount ($20). This is possible because with length extension attacks, an attacker knows the state of the hashed key and messaging pair up to the point of appending a duplicate amount
parameter. It is then trivial to figure out the hashing algorithm used and generate a new hash digest. The new forged hash digest will be sent back to the server as a valid request because the signature being the same length as the original. Hence why there is no need to know the original key to sign the message and the forged hash digest is accepted as if the password was known.
Python 02
import urllib
import os
from flask import Flask,redirect,request
from secrets import token_hex
app = Flask(__name__)
@app.route('/fetch')
def fetch():
url = request.args.get('url', '')
if url.startswith("https://vulndomain.com"):
response = urllib.request.urlopen(url)
html = response.read()
return html
return ""
This code review snippet is an example of why URLs should be ended in a /
. Without the /
, a malicious attacker can register a nefarious top level domain that could bypass the filter by creating a subdomain such as vulndomain.com.evildomain.com
. This flaw is called a server side request forgery (SSRF) as in the line where response.read()
is found, the server will read the request and return the response value. This flaw can be leveraged by fetching data from malicious endpoints and have the server parse the information.
Python 03
from http.server import BaseHTTPRequestHandler, HTTPServer
from http.cookies import SimpleCookie
import base64
import pickle
class MyServer(BaseHTTPRequestHandler):
def do_GET(self):
cookies = SimpleCookie(self.headers.get('Cookie'))
if cookies.get('username'):
username=pickle.loads(base64.b64decode(cookies.get('username').value))
else:
username='stranger'
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes("<html><head><title>Hello</title></head>", "utf-8"))
self.wfile.write(bytes("<body>", "utf-8"))
self.wfile.write(bytes("<h1>Hello %s</h1>" % username, "utf-8"))
self.wfile.write(bytes("</body></html>", "utf-8"))
if __name__ == "__main__":
webServer = HTTPServer(('0.0.0.0', 1337), MyServer)
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")
The weakness in this python source code can be found in the pickle.loads
method. Pickle is a python module that is used for serializing and deserializing data. Data serialization is a process to convert a state of data object into a byte stream such as the following commonly used formats: JSON, YAML, and XML. Data deserialization is the reverse process where the byte stream is recreated into an object stored in memory. In the code snippet, the cookie is fetched from HTTP headers and reads the cookie value from the username
attribute. This can pose an issue as a cookie can be easily manipulated as a malicious source of data for the server to be read from. As displayed in the example of pickle.loads
, the module deserializes the username
cookie value using base64.b64decode
method. Further research shows that a directive called reduce
can be used to create a malicious serialized object. In theory, if a serialized object is encoded into a base64 payload into the cookie value, the HTTP server would know how to deserialize it using base64 decode function.
Python 04
from http.server import BaseHTTPRequestHandler, HTTPServer
from http.cookies import SimpleCookie
class MyServer(BaseHTTPRequestHandler):
def do_GET(self):
cookies = SimpleCookie(self.headers.get('Cookie'))
if cookies.get('session_id'):
username=open(cookies.get('session_id').value).readlines()[0]
else:
username='stranger'
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(bytes("<html><head><title>Hello</title></head>", "utf-8"))
self.wfile.write(bytes("<body>", "utf-8"))
self.wfile.write(bytes("<h1>Hello %s</h1>" % username, "utf-8"))
self.wfile.write(bytes("</body></html>", "utf-8"))
if __name__ == "__main__":
webServer = HTTPServer(('0.0.0.0', 1337), MyServer)
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")
The weakness in the code snippet can be found when the cookie session_id
value is read by using open
method. The cookie can act as a source of malicious input and by modifying the cookie value contained in session_id
, the open
method can be vulnerable if validation of the input is not performed. Python's open
method is designed to open a file and read the file line by line. Knowing this fact, if the cookie value input contains a directory traversal path such as ../../file.txt
the open
method will read the contents as such. A sample output can be seen like this:
~ curl -b 'session_id=key.txt' http://localhost:1337
<html><head><title>Hello</title></head><body><h1>Hello key read
</h1></body></html>%
Python 05
def get_post(post_id):
conn = get_db_connection()
num_format = re.compile(r'^\d+$', re.M)
if re.match(num_format,post_id):
post = conn.execute('SELECT * FROM posts WHERE id = '+post_id).fetchone()
conn.close()
if post is None:
abort(404)
return post
else:
abort(404)
The weakness in the code snippet can be found in the conn.execute
as it's susceptible to SQL injection. The post_id
can act as a malicious source of which if the post_id
is not validated or sanitized, you can append other SQL query operators that would modify the statement to extract sensitive information from other fields. To mitigate against this type of attack, never use the input directly and use prepared statements. Prepared statement is also called parameterized queries which is a coding technique to define the SQL statement first then pass in the parameter to the query later. If a malicious input would be attempted, the input will be treated as a data instead of code.
Python 06
from http.server import BaseHTTPRequestHandler, HTTPServer
import re
from os.path import exists
import os
class MyServer(BaseHTTPRequestHandler):
def do_GET(self):
path = os.getcwd()
pattern = r'/\.\.\/\.\.\/'
if re.match(pattern, self.path ):
self.send_response(404)
return
path += self.path
if path.endswith('/'):
path+='index.html'
print(path)
if exists(path):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(open(path).read().encode('utf-8'))
else:
self.send_response(404)
if __name__ == "__main__":
webServer = HTTPServer(('0.0.0.0', 1337), MyServer)
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")
The weakness in this python code can be found in the pattern
variable. The regex pattern assigned to the variable is a string filter designed to keep out strings in the URL that contain ../../
. However, I still don't know how this filtering can be bypassed as I've tried all sorts of combination which includes trying to manipulate the getcwd
method.