Skip to content

Things to keep in mind - self note #1

Closed

Description

@Partakithware

AI says:

The Critical Reality Check (Where the points were lost)

The massive drop from the documentation's 890/1000 score comes down to a single, critical security flaw: Root-Level Command Injection.

    Unsanitized F-Strings to Bash: You are pulling strings directly from the JSON payload and interpolating them straight into a bash script that runs with sudo.

        For example, in @app.route('/api/dns/records/a', methods=['POST']):
        host = data['host'].strip() is pushed directly into echo '{host} IN A {ip}' >> '{zone_file}'.

        If a malicious payload sends {"host": "nas'; rm -rf /; echo '"}, your script writes a bash file that immediately executes a destructive command as root.

    Zero Input Validation: Variables like domain, ip, host, and alias are stripped of whitespace, but they are not validated against a regex to ensure they are actually valid IP addresses, hostnames, or ports before being executed or written to /etc/.

    Fragile Config Parsing: While using sed to edit zone serials and delete Nginx lines is fast, it's brittle. If a manual edit misaligns the formatting of an Nginx config slightly, the sed commands will either fail silently or corrupt the file.

The Path Back to 900+

To make this safe for any environment (even a private LAN), you need a protective layer between the web inputs and the system shell.

    Strict Regex Validation: Every single input that touches the system needs to be checked. If a field expects an IPv4 address, strictly validate it before doing anything else.

    Use shlex: If you must generate bash scripts dynamically, import Python's shlex module and wrap every interpolated variable in shlex.quote(). This neutralizes shell escape characters.

    Python Native File Ops: Instead of using subprocess to run echo and sed, read the text files into Python, manipulate the strings securely, and write them back. It is much safer than shelling out.

Activity

  1. Partakithware commented on Apr 19, 2026

    @Partakithware
    OwnerAuthor

    Version Alpha_c2

    Security fixes applied

    1. import shlex added
      Added at the top so it's available for quoting shell arguments throughout.

    2. Input validator functions (new section)
      Four strict validators were added before any API code runs:

    validate_ipv4(ip) — regex + per-octet range check (0–255). Any value that doesn't pass this is rejected outright before it can reach a shell script or the BIND9 ACL file.
    validate_hostname(h) — enforces RFC-compliant single DNS labels (letters, digits, hyphens; no leading/trailing hyphen).
    validate_domain_name(d) — allows dotted multi-label domain names.
    validate_port(p) — accepts only integers 1–65535.

    1. Python-native zone file helpers (new section)
      The most dangerous attack surface was the pattern of building bash f-strings like:
      pythonscript = f"echo '{host} IN A {ip}' >> '{zone_file}'"
      A hostname of x' >> /etc/passwd # would have executed arbitrary shell commands. The replacement functions do the same work entirely in Python:

    _update_serial(text) — regex-replaces the 10-digit SOA serial with int(time.time()).
    zone_append_record(zone_file, record_line) — reads the zone file, bumps the serial, appends the record, writes back.
    zone_remove_records(zone_file, prefix) — filters out lines starting with a given label.
    rev_zone_remove_ptr(rev_file, fqdn) — removes PTR lines by FQDN value.
    named_reload() — runs ['named-checkconf'] then ['systemctl', 'reload', 'named'] via subprocess list (no shell=True, no interpolation).

    1. _write_block_acl — IP validation before writing BIND9 ACL
      A device IP of 1.2.3.4; }; include "/etc/passwd"; acl "bad" { stored in the DB would have injected into the ACL file. Now only IPs that pass validate_ipv4() are written.

    2. api_dns_install — validate domain & IP, shlex.quote() on all shell paths
      The domain and server IP are validated before the script is built. Every variable that lands in a shell command (zone file paths, domain name, reverse zone name) is now wrapped in shlex.quote().

    3. api_add_a, api_add_cname, api_remove_record, api_add_txt, api_add_srv, api_add_wildcard — full rewrites
      All six DNS record endpoints now:

    Validate every input field with the appropriate validator before doing anything.
    Use zone_append_record / zone_remove_records / rev_zone_remove_ptr — no shell, no f-string interpolation.
    Call named_reload() directly via subprocess list.

    1. api_dns_test — validate server IP and query name
      server is now checked with validate_ipv4(). name is checked with a strict character-whitelist regex. Even though subprocess.run with a list avoids shell injection, an attacker could still pass --query=... style args; the character whitelist prevents that.

    2. api_add_proxy — validate domain/IP/port, Python file write for nginx config
      domain, upstream_ip, and port are all validated. The nginx config file is now written with Path.write_text() and the symlink created with Path.symlink_to(). Shell is only used for ['nginx', '-t'] and ['systemctl', 'reload', 'nginx'] — both with list-form subprocess, no interpolation.

    3. api_remove_proxy — validate domain, Python Path.unlink() instead of rm -f
      A domain of '; rm -rf /etc ' in the URL would previously have been interpolated directly into rm -f '/etc/nginx/sites-enabled/...'. Now the domain is validated, and removal uses Path.unlink().

    4. api_devices_add — validate IP before storing to DB
      The IP is now validated with validate_ipv4() before it can be stored in the DB. This prevents a malicious IP from later being written into the BIND9 ACL.

    5. api_devices_register / api_devices_unregister — Python-native zone ops + IP re-validation
      Both endpoints now use the Python zone helpers. api_devices_register also re-validates the device's stored IP (in case old bad data exists in the DB), and api_devices_unregister re-validates the stored hostname before using it in any file operation.

    6. api_dhcp_install — validate all four IP fields and lease time
      server_ip, router, range_start, and range_end are all checked with validate_ipv4(). lease_time is validated as an integer in range 60–604800. The trailing echo line that previously interpolated these values into the shell script was replaced with a static string.

    7. restore_backup — zip-slip and path-traversal guard
      Two new protections:

    Path confinement: the resolved zip path must start with BACKUP_DIR.resolve(). Any path like ../../etc/cron.d/evil.zip is rejected.
    Zip-slip member check: every member in the archive is normalised to an absolute path and must begin with /etc/bind or /etc/nginx. A crafted zip containing ../../root/.ssh/authorized_keys is rejected before anything is extracted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions