John Tuyen

Adventures of Functional Mediocrity

09 Jul 22

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.