tags: ["RCE",: "Network", "Reverse Engineering"]
cve: CVE-2026-13371
cvss: 9.8
severity: critical
VPN concentrators sit at the most valuable position in a network: reachable from the public internet, trusted by everything behind them. The Aceris Secure Gateway (ASG) is a widely deployed appliance in that class — we found it fronting networks across finance and healthcare during routine attack-surface review. This post documents a chain of three independently-minor bugs that together yield unauthenticated remote code execution as root from a single HTTP request.
We reported the chain to the vendor on 2026-02-04. A fixed firmware (build 7.4.1-rc3) shipped on 2026-04-29, inside our 90-day window. Customers should update immediately; see the advisory for affected versions.
The ASG exposes two HTTP services on the WAN interface: the user-facing SSL-VPN portal on :443, and an internal management API on :8443 that is — per the documentation — "restricted to the management VLAN". In practice the bind is governed by a runtime flag that several OEM images ship disabled, leaving :8443 world-reachable. That assumption is the seam the whole chain pries open.
GET /mgmt/api/v2/diag/bundle?path= ../../../../etc/asg/session.key HTTP/1.1Host: gateway.example.ch:8443X-ASG-Diag: 1
The diagnostics endpoint reads operator-supplied file paths to assemble a support bundle. The sanitiser strips ../ sequences exactly once, non-recursively, so a folded payload like ....// collapses back into a traversal after the single pass. This is enough to read any file the asgd process can — including the long-lived session signing key.
The fix should have been a canonicalise-then-verify check against an allow-listed root. The shipped fix does exactly that, plus drops the endpoint's privileges to a dedicated
diaguser.
With the signing key we can mint a session token, but tokens are still bound to an account. Instead we turned to the logger: every authentication attempt is written through a path that passes the supplied username straight into the platform's logging primitive as the format argument.
<pre><code><span class="c-cmt">// asgd/auth/log.c (reconstructed)</span> <span class="c-ty">void</span> <span class="c-fn">log_attempt</span>(<span class="c-ty">const char</span> *user) { <span class="c-ty">char</span> line[256]; <span class="c-fn">snprintf</span>(line, <span class="c-num">sizeof</span> line, user); <span class="c-cmt">// user is attacker-controlled</span> <span class="c-fn">audit_write</span>(line); }</code></pre>
A username of %n writes to the stack; with the key-leak primitive from Bug 1 we already know the binary's load base (ASLR is on, but the diag bundle happily includes /proc/self/maps). From there it is a textbook write-what-where.
Exploiting a format string remotely is normally fiddly because you need to know exactly where your controlled data lands. ASG's session nonces are derived from time(NULL) XORed with a per-boot counter, so the heap layout at the moment of the logging call is deterministic for a given uptime. We can fingerprint uptime from the Date header drift and pick the matching offset table. Reliability across our test fleet was 96%.
We overwrite a function pointer in the audit dispatch table with the address of a small ROP stage built from gadgets in the statically-linked libasgcrypto. The stage calls execve("/bin/sh", …). Because asgd runs as root and never drops privileges, that is the whole game.
<ul class="timeline"> <li><span class="t-date">2026-02-04</span> Reported full chain + PoC to vendor PSIRT.</li> <li><span class="t-date">2026-02-06</span> Vendor acknowledged, reproduced internally.</li> <li><span class="t-date">2026-03-18</span> Vendor shared candidate patch; we confirmed two of three bugs fixed.</li> <li><span class="t-date">2026-04-29</span> Fixed firmware released (7.4.1-rc3).</li> <li><span class="t-date">2026-05-13</span> Public disclosure (this post), day 98.</li> </ul>
None of these three bugs is remarkable alone — a non-recursive sanitiser, a missing format specifier, a weak nonce. The lesson, as ever, is that a privileged process on the network edge converts trivial bugs into total compromise. Defence in depth here would have meant privilege separation in asgd and a hard bind of the management API. We are releasing the PoC after a further 30 days.