# frp: Unauthenticated Remote Denial of Service in the frp SSH Tunnel Gateway via Integer Overflow

Integer overflow in frp SSH tunnel gateway's exec length parsing wraps 4+0xFFFFFFFF to 3, causing an out-of-range slice panic that crashes frps pre-auth.

- Payout: Research
- Severity: high
- Program: frp
- Found by: arkmarta
- Disclosed: 2026-07-24
- CVE: CVE-2026-73564
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-26gq-p25f-99cp
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-26gq-p25f-99cp
- Tags: area:devices domain:network impact:dos kind:research platform:ghsa sev:high tech:go technique:source-review vuln:dos

**Target:** frps SSH Tunnel Gateway exec channel request handler (pkg/ssh/server.go)
**Root cause:** The exec payload's attacker-controlled uint32 length is added to constant 4 in uint32 arithmetic, wrapping 0xFFFFFFFF to 3; the bounds check compares against the wrapped value so a 5-byte payload passes and the slice payload[4:3] panics.
**Steps:**
1. Enable SSH tunnel gateway with no authorized-keys file (default NoClientAuth) in frps config: `[sshTunnelGateway] bindPort = 2200`.
2. Connect as unauthenticated SSH client and open a session channel.
3. Send one `exec` request with payload `ff ff ff ff 41` (length prefix 0xFFFFFFFF plus one byte).
4. Server panics `slice bounds out of range [4:3]` at pkg/ssh/server.go:319 and the whole frps process exits.
5. Re-send after restart to sustain the outage.
**How found:** Code review of the exec request parsing in pkg/ssh/server.go: noticed the length field is attacker-controlled uint32 and the end offset is computed as `4 + length` in uint32 arithmetic, then checked whether the bounds guard uses the wrapped value and whether the handler goroutine has a recover.
**Exploitation:** Unauthenticated attacker opens an SSH session channel to the gateway and sends a single five-byte exec request; the handler goroutine panics with no recover, terminating the entire frps process and dropping all tunnels for all tenants. Repeatable after restart.
**Insight:** When a length prefix is added to a constant in the same unsigned type, the wrap can defeat the bounds check itself; also check whether the parsing goroutine has a recover, since an unrecovered panic in a per-connection goroutine kills the whole process rather than one connection.

## Proof of concept (copied exactly from the original report)

**Five-byte exec payload with 0xFFFFFFFF length prefix**

```
As an unauthenticated SSH client, open a session channel and send one `exec` request whose four-byte length prefix is `0xFFFFFFFF` followed by a single byte, giving the five-byte payload `ff ff ff ff 41`. The server immediately panics and exits:
```

**Vulnerable slice code in server.go**

```
end := 4 + binary.BigEndian.Uint32(req.Payload[:4])  // 4 + 0xFFFFFFFF wraps to 3
if len(req.Payload) < int(end) {                     // 5 < 3 is false, so it passes
    continue
}
extraPayload := string(req.Payload[4:end])           // payload[4:3] -> panic
```

**Gateway config enabling unauthenticated mode**

```
bindPort = 7000

[sshTunnelGateway]
bindPort = 2200
```

