← THE INDEX  ·  WRITEUP

AXIS OS httptest.cgi SSRF via IPv6-Mapped Loopback Bypass

httptest.cgi blocks 127.0.0.1 and ::1 but treats [::ffff:127.0.0.x] as a routable address, bypassing loopback validation and reaching privileged internal Apache VirtualHosts.

Summary

The VAPIX endpoint httptest.cgi accepts a URL, validates it against a loopback blocklist, and then makes an outbound HTTP request with libcurl. The binary's internal check correctly blocks 127.0.0.x (resolved to an IPv4 AF_INET address) and ::1 (pure IPv6 loopback), but does not check for IPv6-mapped IPv4 addresses of the form [::ffff:127.0.0.x]. When such an address is supplied, the binary skips the Local host not allowed response and attempts a real TCP connection.

AXIS OS binds privileged internal Apache VirtualHosts to non-standard loopback addresses. Port 80 on 127.0.0.12 serves the ACAP service-account VHost (localhost-acap), which accepts service-account-level credentials and provides access to all VAPIX CGI endpoints. The bypass was confirmed by executing the extracted binary in QEMU ARM emulation.

Authentication required: operator (low privilege).

Impact

An operator-level user can reach internal loopback VirtualHosts that are inaccessible from the external network. The proven impact is SSRF: the server makes TCP connections to [::ffff:127.0.0.x] addresses instead of returning the blocklist error.

A potential escalation chain exists and is documented for vendor verification: if the ACAP VHost at 127.0.0.12 auto-authenticates requests arriving from localhost (or accepts weaker service-account credentials), an operator could use the SSRF to enable unsigned ACAP package installation, then upload a crafted .eap package whose package.conf is sourced as shell by install-package.sh running as root. Whether the ACAP VHost auto-authenticates loopback requests is a live-hardware question Axis can answer definitively.

The CVSS score of 7.6 reflects the confirmed SSRF bypass alone. The escalation path, if verified, would raise it to 9.8.

Root cause

After resolving the hostname with getaddrinfo(), httptest.cgi checks the resulting address against known loopback values. The check handles AF_INET addresses in 127.0.0.0/8 and the AF_INET6 address ::1, but does not handle AF_INET6 structures where IN6_IS_ADDR_V4MAPPED is true and the embedded IPv4 address falls in 127.0.0.0/8.

The sister binary validateaddr (used by tcptest.cgi and ftptest.cgi) blocks the full loopback range including 127.0.0.2, 127.0.0.12, and 127.0.0.255. httptest.cgi is an ELF binary that does not call validateaddr and implements its own check, which has this gap.

The fix requires adding a mapped-address check after getaddrinfo():

if (addr->sa_family == AF_INET6) {
    struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
    if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
        uint32_t v4 = ntohl(sin6->sin6_addr.s6_addr32[3]);
        if ((v4 & 0xff000000) == 0x7f000000)
            return "Local host not allowed";
    }
}

Proof of concept

The following reproduces the bypass using QEMU ARM emulation on the extracted firmware. No credentials or live camera identifiers are required for this step.

Disclosure and fix

Reported to the AXIS Security team through coordinated disclosure. The immediate fix for httptest.cgi is to add an IPv6-mapped address check in the post-resolution loopback test (code sketch in the root cause section). The architecture-level recommendation is to bind internal VirtualHosts to Unix domain sockets rather than loopback TCP addresses, removing this class of SSRF surface entirely.

For the ACAP installation handler (install-package.sh), the fix is to parse package.conf as structured key-value data rather than sourcing it as shell. The . ./$ADPPACKCFG pattern executes arbitrary commands from untrusted package content before any validation.

Related prior art: CVE-2023-21413 (AXIS OS command injection during ACAP installation) covers a related code path in the package installation handler.