Atom
HTB - 使用 Python 進行 DNS 枚舉

HTB - 使用 Python 進行 DNS 枚舉

DNS(Domain Name System)網域名稱系統

它將人們可讀取的網域名稱(例如,www.amazon.com) 轉換為機器可讀取的IP 地址(例如,192.0.2.44)。

傳統DNS作法:主 DNS(Primary/Master)+ 輔助 DNS(Secondary/Slave)

主 DNS:正本(可以編輯區域檔 zone file、改 A/CNAME/MX 等紀錄)
輔助 DNS:影印本(通常唯讀,定期從主 DNS 把 zone 複製過來)
Zone transfer(AXFR/IXFR):影印機動作(把區域資料從主送到輔)

AXFR:把整本「電話簿」整本影印走(完整區域檔
IXFR:只影印「最近改過的那幾頁」(增量更新

在這邊的攻擊面就是使用者可能會錯誤配置
正常情況應該是:

  • 只允許「你的輔助 DNS」那些固定 IP/主機名來做 zone transfer,其他來源一律拒絕
    錯誤情況:
  • 沒有限制來源,或 ACL 寫錯、TSIG(簽章驗證)沒用/沒設好導致任何外部 client 都可以要求 AXFR

DNS 記錄和查詢

DNS 記錄是位於權威 DNS 伺服器上的指令,包含有關網域的資訊。
這些條目以 DNS 語法編寫,為 DNS 伺服器提供適當的指令。
以下是我們在滲透測試中遇到的最常見的 DNS 記錄:

記錄 描述
A IP 版本 4 位址記錄
AAAA IP 版本 6 位址記錄
CNAME 規範名稱記錄
HINFO 主機資訊記錄
ISDN 綜合業務數位網路記錄
MX 郵件交換器記錄
NS 名稱 伺服器記錄
PTR 反向查找指標記錄
SOA 管理局記錄的開始
TXT 文字記錄

我們可以使用許多工具和資源來向 DNS 伺服器發送查詢。例如,我們可以使用以下工具:

  • dig
    使用範例:
    1
    2
    dig NS inlanefreight.com
    dig SOA inlanefreight.com
  • nslookup
    使用範例:
    1
    2
    nslookup -type=SPF inlanefreight.com
    nslookup -type=txt _dmarc.inlanefreight.com

範例題目:

Investigate all records for the domain “inlanefreight.com” with the help of dig or nslookup and submit the one unique record in double quotes as the answer.
透過 dig 或 nslookup 指令,尋找網域名稱「inlanefreight.com」的所有記錄,並將唯一記錄以雙引號括起來作為答案提交。

我們兩種工具都可以使用,這邊使用簡單好記的dig

1
dig TXT inlanefreight.com
解答
HTB{5Fz6UPNUFFzqjdg0AzXyxCjMZ}

Find out the corresponding IPv6 address of the domain “inlanefreight.com” and submit it as the answer.
找出網域名稱「inlanefreight.com」對應的 IPv6 位址,並將其作為答案提交。

一樣使用dig來尋找答案

1
dig AAAA inlanefreight.com
解答
2a03:b0c0:1:e0::32c:b001

DNS 枚舉

我們可以將這些資訊分為以下幾類:

  • DNS Records
  • Subdomains/Hosts
  • DNS Security

枚舉技術介紹:

  1. OSINT(上篇IG跟上上篇暗網都有簡單介紹到OSINT,OSINT就是從公開來源去尋找資訊的意思)
    VirusTotal
    DNSdumpster
    Netcraft
    Google等搜尋引擎
  2. Certificate Transparency (CT)

    CT日誌中包含所有Certificate Authority(CA)SSL/TLS certificates 來自 Web 伺服器包括 domain names, subdomain names, 和 email addresses。)
    CTFR.py

  3. Zone transfer(區域轉移)

    這個過程稱為 Asynchronous Full Transfer Zone (AXFR)。Zone transfer僅涉及文件或記錄的傳輸以及檢測所涉及伺服器資料庫中的差異。
    執行區域轉移:

    1
    dig axfr inlanefreight.com @10.129.2.67

範例題目:

Perform a zone transfer for the “inlanefreight.htb” domain against your target and determine how many nameservers the company has. Submit the total number of nameservers as the answer.
對「inlanefreight.htb」網域名稱執行區域轉移,並確定目標公司擁有多少台網域名稱伺服器。將網域名稱伺服器總數作為答案提交。
尋找AXFR我們可以使用dig,使用方法同樣是簡單的dig加上要檢查的參數以及標的:

1
dig axfr inlanefreight.htb @10.129.20.45
解答
2

Determine the IPv4 address of “ns1.inlanefreight.htb” from your target and submit it as the answer.
從目標中確定「ns1.inlanefreight.htb」的 IPv4 位址,並將其作為答案提交。
從上一題的輸出中我們就看到答案了,這邊不贅述。

解答
10.129.2.55

Perform a zone transfer against the target “inlanefreight.htb” domain and determine the IPv4 address of ns2.inlanefreight.htb and submit it as the answer.
對目標「inlanefreight.htb」網域執行區域轉移,確定ns2.inlanefreight.htb的IPv4位址,並將其作為答案提交。
如上題。

解答
10.129.2.58

Check if a zone transfer against the target “inlanefreight.htb” domain because we could hear from the conversations with the administrators that they are not very familiar with DNS. As a proof of concept, a TXT record was left there to serve as evidence. Submit this TXT record as the answer.
請檢查是否存在針對目標網域「inlanefreight.htb」的區域轉移,因為我們從與管理員的溝通中了解到他們對DNS不太熟悉。為了驗證概念,我們在該網域下保留了一條TXT記錄作為證據。請將此TXT記錄作為答案提交。
如上題。

解答
HTB{mgraRNhvDCPcKpzAXHA6cJUhW}

終於到正題了

我們使用dnspython 這個模組來進行DNS枚舉。
以下進行逐行講解

基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3

# 使用套件: python3-dnspython

# 使用模組:
import dns.zone as dz
import dns.query as dq
import dns.resolver as dr
import argparse

# 建立要解析的紀錄名稱(這邊我們用NS)
NS = dr.Resolver()

# 標的
Domain = 'inlanefreight.com'

# 要解析的DNS名稱
NS.nameservers = ['ns1.inlanefreight.com', 'ns2.inlanefreight.com']

# 將結果儲存起來
Subdomains = []

若是有多台需要解析的話,為了方便出錯比較好除錯,同時讓main更乾淨,所以函數參數能越少越好(最好不要超過三個)。

再來是加上Try-Except,讓程式在失敗的時候不會直接結束,而是會繼續運行(並輸出錯誤)。

If-Else:最好只檢查少量條件,像是判斷是否真的有拿到資料。

For-Loop:for 迴圈越簡單越好,否則迭代次數錯了很難 debug。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<SNIP>
Subdomains = []

def AXFR(domain, nameserver):

# 進行AXFR
try:
axfr = dz.from_xfr(dq.xfr(nameserver, domain))

# 如果成功的話
if axfr:
print('[*] Successful Zone Transfer from {}'.format(nameserver))

# 將找到的子網域加入到全域"Subdomains"這個清單中
for record in axfr:
Subdomains.append('{}.{}'.format(record.to_text(), domain))

# 如果失敗的話
except Exception as error:
print(error)
pass

使用For-Loop進行AXFR:

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

import dns.zone as dz
import dns.query as dq
import dns.resolver as dr
import argparse

NS = dr.Resolver()

Domain = 'inlanefreight.com'

NS.nameservers = ['ns1.inlanefreight.com', 'ns2.inlanefreight.com']

Subdomains = []

def AXFR(domain, nameserver):

try:
axfr = dz.from_xfr(dq.xfr(nameserver, domain))

if axfr:
print('[*] Successful Zone Transfer from {}'.format(nameserver))

# Add found subdomains to global 'Subdomain' list
for record in axfr:
Subdomains.append('{}.{}'.format(record.to_text(), domain))

except Exception as error:
print(error)
pass

if __name__=="__main__":

for nameserver in NS.nameservers:

AXFR(Domain, nameserver)

if Subdomains is not None:
print('-------- Found Subdomains:')

for subdomain in Subdomains:
print('{}'.format(subdomain))

else:
print('No subdomains found.')
exit()

範例題目:

Perform a zone transfer using the DNS-AXFR.py script against your target for the “inlanefreight.htb” domain and submit the total number of unique subdomains found.
使用 DNS-AXFR.py 腳本對目標「inlanefreight.htb」執行DNS-AXFR,並提交找到的唯一子網域總數。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/usr/bin/env python3

# 依賴套件:
# - python3-dnspython(或 pip install dnspython)

# 使用的模組:
import dns.zone as dz
import dns.query as dq
import dns.resolver as dr
import argparse #目前還用不到

# 建立解析器(Resolver)物件,後續可以用它指定要問哪台 DNS
NS = dr.Resolver()

# 目標
Domain = 'inlanefreight.htb'

# 指定要使用的 DNS 伺服器
NS.nameservers = ['10.129.21.49']

# 用來存放的清單
Subdomains = []

# 定義 AXFR 函數:嘗試對指定的 DNS 伺服器進行 Zone Transfer(AXFR)
def AXFR(domain: str, nameserver: str) -> None:
"""對目標網域向指定的 DNS 伺服器發起 AXFR,並把找到的名稱存進 Subdomains。"""
try:
# 發送 AXFR(Zone Transfer)請求,並把回傳資料解析成 Zone 物件
z = dz.from_xfr(dq.xfr(nameserver, domain))

# 如果 z 有內容,通常代表 zone transfer 成功
if z:
print(f"[*] 成功:從 {nameserver} 完成 Zone Transfer")

# 逐一走訪 zone 裡的節點(每個 name 就是一個主機名稱/子網域名稱)
for name, _node in z.nodes.items():
host = name.to_text() # 轉成文字,例如:'www'、'mail'、'@'

# '@' 或空字串通常代表 zone apex(根網域本身)
if host in ("@", ""):
fqdn = domain
else:
# 組合成完整網域名稱(FQDN)
fqdn = f"{host}.{domain}"

# 加入結果清單(後面會再做去重)
Subdomains.append(fqdn)

except Exception as e:
# 失敗時印出錯誤,但不中斷程式
print(f"[-] 失敗:對 {nameserver} 進行 AXFR 時發生錯誤:{e}")

def main():
# 呼叫 AXFR:對你指定的 nameserver(IP)進行一次 AXFR
AXFR(Domain, NS.nameservers[0])

# 去除重複的子網域(set)並排序(sorted),得到唯一清單
unique = sorted(set(Subdomains))

# 輸出「唯一子網域總數」
print(f"\n[+] 唯一子網域總數:{len(unique)}")

# (可選)列出所有唯一子網域,方便你檢查
for s in unique:
print(s)

# Python 腳本入口:直接執行本檔案時會從這裡開始
if __name__ == "__main__":
main()

解答
14

argparse

若是已經有固定功能的話可以使用ArgParser ,就不用一直更改程式碼,類似建立一個標準模組(最近終末地玩多了覺得這東西應該要叫藍圖w)。

這邊附上可調用的參數:

name or flag:參數名稱或旗標(可以是一個名稱,或一組可用的選項字串,例如 -d、–domain)。

action:當命令列遇到這個參數時要執行的基本動作類型(例如存值、計數、設為 True/False 等)。

nargs:這個參數要從命令列「吃掉」幾個值(例如 1 個、可選、或多個)。

const:某些 action 與 nargs 組合會需要的固定常數值。

default:如果命令列沒有提供這個參數,解析後使用的預設值。

type:把命令列字串轉換成的型別(例如 int、float、str)。

choices:允許的值範圍/集合(輸入必須是其中之一)。

required:是否必填(通常用在「可選參數」上,表示能不能省略)。

help:這個參數的簡短說明(會出現在 -h/–help 的幫助訊息)。

metavar:在用法訊息(usage)中顯示的「參數值名稱」占位符(例如顯示成 DOMAIN、IP)。

dest:parse_args() 回傳物件中,這個參數存放的屬性名稱(例如變成 args.domain 的 domain 就是 dest)。

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
50
51
52
53
54
55
56
57
#!/usr/bin/env python3
import argparse
import dns.zone as dz
import dns.query as dq

def AXFR(domain: str, nameserver: str) -> list[str]:
"""對目標網域向指定 DNS 伺服器發起 AXFR,回傳找到的 FQDN 清單(可能含重複)。"""
subdomains: list[str] = []

try:
z = dz.from_xfr(dq.xfr(nameserver, domain))

if z:
print(f"[*] 成功:從 {nameserver} 完成 Zone Transfer")

for name, _node in z.nodes.items():
host = name.to_text()

if host in ("@", ""):
fqdn = domain
else:
fqdn = f"{host}.{domain}"

subdomains.append(fqdn)

except Exception as e:
print(f"[-] 失敗:對 {nameserver} 進行 AXFR 時發生錯誤:{e}")

return subdomains

def parse_args():
parser = argparse.ArgumentParser(
prog="DNS-AXFR.py",
description="DNS AXFR helper (lab use)",
)
parser.add_argument("-d", "--domain", required=True, help="inlanefreight.htb")
parser.add_argument("-n", "--nameserver", required=True, help="10.129.20.45")
parser.add_argument("--show", action="store_true", help="列出所有唯一子網域")
return parser.parse_args()

def main():
args = parse_args()

domain = args.domain.strip().rstrip(".")
nameserver = args.nameserver.strip()

subdomains = AXFR(domain, nameserver)

unique = sorted(set(subdomains))
print(f"\n[+] 唯一子網域總數:{len(unique)}")

if args.show:
for s in unique:
print(s)

if __name__ == "__main__":
main()
本文作者:Atom
本文鏈接:https://d0ngd.github.io/2026/02/06/使用 Python 進行 DNS 枚舉/
版權聲明:本文採用 CC BY-NC-SA 3.0 CN 協議進行許可