The Challenge#
The server runs a custom DNS resolver on port 10500. Each DNS node in the maze is a hostname. Querying CNAME for direction.nodename (where direction is up, down, left, or right) either fails (dead end) or returns the next node’s hostname. TXT records on a node might contain the flag.
Approach#
Standard graph DFS with a visited set to avoid cycles. For each node:
- Query
TXT — if any result contains "flag", print and return.
- For each of the four directions, query
CNAME with a direction.nodename pattern. If a CNAME exists, recurse into that node’s hostname.
The dns.resolver.Resolver is pointed at the challenge server’s IP and port. Exceptions (no answer, NXDOMAIN, timeout) are silently ignored — they just mean that direction is a dead end.
Solution#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
#!/usr/bin/env python3
import socket
import dns.resolver
# Configure the custom DNS server and port
res = dns.resolver.Resolver()
res.nameservers = [ socket.gethostbyname("pisani.challs.olicyber.it") ]
res.port = 10500
res.timeout = 5
res.lifetime = 5
visited = set()
def dfs(name):
"""Depth‑first search through the DNS maze."""
if name in visited:
return False
visited.add(name)
# 1) Try to get a TXT record for this node
try:
answers = res.resolve(name, "TXT")
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.exception.Timeout):
answers = []
for rdata in answers:
# rdata.strings is a list of byte-strings; join and decode
txt = b"".join(rdata.strings).decode('utf-8', errors='ignore')
if "flag" in txt:
print("FOUND:", txt)
return True
else:
# If it's a navigation hint, it might start with e.g. "Navigate:"
print(f"{name} TXT → {txt}")
# 2) Otherwise, look up CNAMEs for the four possible directions
for direction in ("up", "down", "left", "right"):
target = f"{direction}.{name}"
print(f"Trying {target}...")
try:
cname_ans = res.resolve(target, "CNAME")
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.exception.Timeout):
continue
# Each CNAME RR has a .target attribute (a Name); str() yields the FQDN
for rdata in cname_ans:
next_name = str(rdata.target).rstrip('.')
if dfs(next_name):
return True
|
res.nameservers and res.port point the dnspython resolver at the challenge server rather than the system resolver. The visited set prevents infinite loops in cyclic mazes. rdata.strings is a list of byte chunks that need joining before decoding — a minor but crash-causing gotcha if skipped.
What I Learned#
DNS is a general-purpose data transport, not just a name-resolution protocol. Any record type can carry arbitrary data — TXT records hold strings, CNAME records can chain to arbitrary names. Building a DFS over CNAME-connected hostnames is straightforward once you see the graph structure the server is implementing.