# A Certificate Name That Runs as Root: Five Bugs in Sophos Firewall, Found with Codex

A firewall appliance is one of those boxes where the job description and the threat model are the same sentence. It sits at the edge of the network, it terminates the VPN, it holds the credentials for everything it inspects, and it is supposed to be the thing that is still standing when other things fall over.

So I pointed Codex at Sophos Firewall v22.0.1 MR-1 and asked it a fairly boring question: **where does user-supplied text end up somewhere it shouldn't?**

The answer turned into five findings. Four of them are access-control problems in the portal — interesting, worth reporting, and I'll go through them quickly at the end. The one worth your time is the first one, where a field inside a certificate ends up in a shell, running as root.

This is a walkthrough, not a deep code analysis. If you've never touched a Sophos box, you'll still be able to follow it.

* * *

## Thirty seconds of context

Two things about this appliance matter for the rest of the post.

**It has a web admin console** (Sophos calls it WebAdmin) where an administrator configures everything: firewall rules, VPN, certificates, web filtering. Access to it is broken into **profiles** — you can create an administrator who is allowed to manage, say, certificates and VPN, and nothing else. That is a normal thing to do in an organisation: the person who rotates certificates is not necessarily the person who owns the box.

**It has a separate user portal** for ordinary end users — not admins, just people on the network. They log in to grab a VPN client, view their web-filter overrides, that sort of thing.

Those two boundaries — "limited admin vs. root" and "one user vs. another user" — are what all five bugs cross.

* * *

## How Codex was pointed at it

I ran this as a source-and-behaviour review rather than blind fuzzing. The prompt was deliberately unglamorous, because the useful thing an agent does here is not being clever — it's being tireless:

> Find every place a value that came from outside the appliance ends up in a shell command, a SQL query, or a database lookup that is supposed to be scoped to one user. For each one, tell me who can control that value and what privileges they need.

That framing produces two different kinds of hit, and I got both.

The **injection** half of the question pointed at the appliance's own shell scripts — the glue that turns configuration into running services. That's where the root command injection came from.

The **scoping** half of the question pointed at the portal's data-grid and record endpoints — the ones that take an id from the client and go fetch it. That's where the four access-control bugs came from.

Codex is good at the part humans are bad at: following a value across files without getting bored, and noticing when an identifier arrives from the client and nothing ever checks who owns it. It is not good at telling you whether the thing it found actually matters. Every candidate it raised, I stood up on a real appliance and tried for myself. Most went nowhere. Five did not.

* * *

## The main event: a certificate name that runs as root

![](https://cdn.hashnode.com/uploads/covers/699fec8cc9015c37f6e5364f/1e2c41e4-0681-45aa-8d94-907438930d28.png align="middle")

Here's the whole bug in one sentence: **when the appliance sets up an IPsec VPN connection, it reads a text field out of the certificate involved and passes that text through a shell** `eval` **as root.**

If you're not certificate-shaped, the field in question is the **Distinguished Name** — the DN. It's the human-readable identity baked into a certificate: the organisation, the common name, the country. When you look at a certificate and see `C=VN, O=Example Corp, CN=Example Root CA`, that's the DN.

The important property of a DN is that it is **just text that whoever made the certificate typed in**. Nothing validates it. It isn't a name in any meaningful sense — it's a string, and you choose it.

So: make a CA certificate whose DN contains a shell command instead of a company name. Upload it. Reference it from a VPN connection. When the appliance builds its IPsec config, it reads your "name" out of the certificate and hands it to a shell.

The command runs as **root**.

### The part that makes it a privilege escalation

You could shrug at this if it took a full super-admin to pull off. It doesn't.

For the test I built the most boxed-in administrator the product will let you create: a profile with **every single feature set to None**, except the two you'd hand to whoever looks after certificates and tunnels. That account can log into the web console, manage certificates, manage VPN connections — and nothing else. **No CLI, no advanced shell, no system settings.**

That account gets root on the firewall.

### A note on what I'm not publishing

There is no public fix for this yet. Sophos has the full report — the certificate-generation script, the exact configuration path, the video, all of it — and I'm not putting any of that here while the issue is open.

So: no payload, no step-by-step, no request captures for this one. What I'm describing is the *shape* of the bug, which is the part that's useful to learn from. The part that turns it into a working exploit stays between me and the vendor until there's something for defenders to patch to.

What I can show is the result. After the configuration change lands, from a root console on the appliance:

```plaintext
uid=0(root) gid=0(root) groups=0(root)
```

Before it, that file didn't exist. No memory corruption, no race, no exotic timing - a command was typed where a company name was expected, and the box ran it.

![](https://cdn.hashnode.com/uploads/covers/699fec8cc9015c37f6e5364f/7f51be08-a8f1-4455-be19-0c15d2cae660.png align="middle")

### Why this one is the interesting one

The appliance has a permissions model that lets an administrator say "you may handle certificates, and nothing else." That statement is the product feature. This bug means the statement isn't true — anyone who can upload a certificate and touch a VPN connection can step outside the role entirely and own the box.

And it's worth sitting with *why* the bug exists, because it isn't exotic. Somewhere in the appliance's plumbing, certificate content got treated as configuration rather than as input. A DN looks like metadata. It reads like a label. It is neither — it's a string an attacker wrote, and it ended up in a shell.

* * *

## The rest of the chain, briefly

The other four are all the same shape in different clothes: **the endpoint does the work the request asks for without checking who's asking.**

### SQL injection in the web content filter grid

The admin console's Content Filter category list lets you filter by a key. That key goes into a SQL query without sanitisation, so an administrator with **Web & Content Filter set to Read-Only** — an account that cannot change anything — can inject SQL and read the database.

It's blind, so you prove it with timing. A normal request comes back in about 20 milliseconds. The injected condition runs in both the count query and the list query, so the delay lands at roughly twice the sleep you ask for:

**\[pg\_sleep(1) → 3,055 ms\]**

![](https://cdn.hashnode.com/uploads/covers/699fec8cc9015c37f6e5364f/928fe4af-57eb-44c8-919e-34cd1ae5b2f8.png align="middle")

**\[pg\_sleep(5) → 11,068 ms\]**

![](https://cdn.hashnode.com/uploads/covers/699fec8cc9015c37f6e5364f/b8ad5f8b-c859-4695-bec3-a5e3852a6866.png align="middle")

From there it's a character-at-a-time read of anything in the database:

![](https://cdn.hashnode.com/uploads/covers/699fec8cc9015c37f6e5364f/7eb9c9cf-1506-4f24-9561-de6a8d109bff.png align="middle")

A read-only admin is supposed to be able to *look*. This turns "look at the configuration" into "read the entire database", which is not the same permission at all.

### Reading, editing and deleting other people's web-filter overrides

Over on the user portal, a "web filter policy override" is a per-user exception — a pass-code that lets someone unblock certain web categories.

The endpoints that read, update and delete those overrides take an id from the request and act on it, with no check that the record belongs to you. The ids are a plain sequential integer, so you find other people's records by counting upwards.

Any ordinary portal user can therefore read someone else's override — including its pass-code in cleartext — rewrite it, or delete it. I confirmed all three against a second account and checked the effect from the victim's own session.

### Creating overrides that ignore every gate — including whose name is on them

The *create* endpoint for the same feature is worse in an interesting way. The admin console has two switches governing overrides: a global on/off, and an allowlist of who's permitted to use them. The create call checks neither — it works while the feature is switched off, and it works for a user the administrator explicitly did not authorise.

It also takes the owner straight from the request. So you can create an override **attributed to somebody else** — and if you attribute it to a user who *is* on the authorised list, it looks entirely legitimate and carries a pass-code you picked.

### Every hotspot voucher, to anyone who asks

Last one, and the simplest. The portal's hotspot voucher list doesn't scope its query to the logged-in user. Send the request with an empty filter and you get every voucher on the appliance — each one including its code in cleartext, plus its status and usage.

A voucher is a credential for getting onto the network. This hands out all of them to any account that can log into the portal.

* * *

## What I'd take away from this

**The four portal bugs are one bug.** Read, update, delete, create, list — five endpoints, one missing idea: *the request says which record; nobody asks whether it's yours.* When you find one of these, stop and enumerate the whole family before you write anything up. They almost never travel alone.

**The root command injection is a different lesson.** It wasn't a missing authorization check — it was a category error. A certificate DN got handled as though it were configuration the appliance wrote, when it's actually text an attacker chose. Any field that arrives inside a file someone uploads is input, no matter how official the file format looks. Certificates feel authoritative. They're just structured text with a signature on part of it.

**And on running an agent over something like this:** the value wasn't a clever idea. It was coverage. Codex read the shell scripts nobody reads, followed values across files without losing the thread, and pointed at places where an identifier came from the client and nothing checked it. Then I went and tried each one by hand, because knowing whether a finding is real is still the part you can't hand off.

* * *

*Tested against Sophos Firewall v22.0.1 MR-1. All findings were reported privately to Sophos through their bug bounty program.*
