Yuanchieh's Blog

Yuanchieh's Blog

生命是長期而持續的累積

22 Sep 2020

RFC 5389 - STUN 協定介紹

STUN 應用於處理 NAT 穿越技術 (如 ICE ) 下的一種工具,本身並非 NAT 穿越的解決方案,主要功能為確認兩個在 NAT 背後節點的 IP / Port,本篇為 RFC 5389 的閱讀心得,分享 STUN 背後的設計原理與結構

STUN 之上還有擴增一個 TURN 協定,提供 relay 連線的功能,一般的 TURN Server 會同時提供 STUN 的服務,如果想知道 TRUN Server 架設,可以參考另一篇文章 AWS Coturn server架設教學

如果你有以下疑惑,那這篇文章應該可以幫助到你

  1. STUN Server 究竟是如何運作
  2. STUN Server 是否有驗證機制
  3. 想知道 Coturn config 中的參數含義,如 Fingerprint / Realm / Nonce

與舊版 STUN 的差異

先前有一版 STUN 的定義 RFC-3489,當時的 STUN 被定義成完整的 NAT 穿越解決方案,但是遭遇了以下問題才改用這一版 5389 取代了舊版

  1. 無法正確區分 NAT 類型,導致可能連線有問題
    NAT 共有四種類型,其中 symmetric NATs 是每次通訊沒有固定的 Public IP / Port,所以只能透過 TURN 來解決雙節點的連線問題
    但是舊版 STUN 演算法設計無法區分 NAT 類型,所以有可能造成部分的連線異常
  2. 支援 TCP / DTLS:
    舊版只支援 UDP
  3. Security 考量

新版 STUN 不再是完整的 NAT traversal 解決方案,只專注於找出節點外層 NAT 對外的 Public IP/Port,完整的解決方案如 ICE / SIP Outbound 等等

也因此 STUN SERVER 預設 port 是 3489 / TLS port 是 5398

架構介紹


可以看到 STUN Client 躲在兩層 NAT 之後,而 STUN Server 就是要通知 STUN Client 最外層 NAT 所對應的 Public IP / Port

STUN 採用 client/server 架構,支援兩種溝通方式(transaction)

  1. request / response
  2. indicate (送出去不等回覆)

每個 transaction 都有 96bit random id

傳送機制

每個 Transaction 會定義類型 (Action),目前 Spec 僅定義 Binding action

運作機制如下

  1. client 送出 request
  2. NAT 會修改 client package 封包的 IP,並自主管理 client private ip port 與 NAT 對應出去的 ip / port
  3. 一路到 server 手上只會拿到 NAT 的 public ip / port,稱之為 server reflexive transport address (srflx),如果有用過 WebRTC 應該有看過 srflx ,這就是代表用戶走 STUN
  4. 接著 server 把這個 public ip / port 當作內容 XOR-MAPPED-ADDRESS 傳回去給 client
  5. NAT 接著會一路改 ip port,但因為內文不改所以 client 會知道最外層 NAT 的 public ip + port

Packet Format

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0|     STUN Message Type     |         Message Length        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Magic Cookie                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                     Transaction ID (96 bits)                  |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

封包格式介紹

  1. 最前方兩個 bit 固定為 0 ,主要是用來區分是不是 STUN 的 packet
  2. Message Type 包含 Transaction 類型 / response 成功或失敗
  3. magic cookie 固定為 0x2112A442
  4. Transaction ID 用來區分 STUN transaction
  5. STUN message 不可以超過 MTO
  6. 如果傳輸透過 UDP:
    Client 要自己處理 retransmit,預設 timeout 500ms ~ 3000 ms 間,之後每次 retry Timeout double
  7. 如果傳輸透過 TCP:
    不要增加而外的 framing / demultiplexing 已經保障資料可靠性,預設 Timeout 為 39.5s Server 應該等 Client 主動斷線,除非遇到 timeout

驗證訊息

Server 收到訊息後,會先做基本的驗證,如果有開啟 fingerprint extension 則驗證,如果發現有不支援的屬性則回傳錯誤; Server 回傳錯誤時,必須指定 error code,並挾帶 error code attribute,例如說有不支援的屬性則回傳 420 + UNKNOWN-ATTRIBUTES,如果是 401 驗證失敗則必須對應回傳驗證方式,這邊的 error code 參考 HTTP 所以會有種似曾相似的分類;
如果訊息成功則夾帶 XOR-MAPPED-ADDRESS 回傳

FINGERPRINT

再Multiplexing 下,會有多個不同 protocol 的 packet 傳送到相同位址的,例如說 RTP,加上 fingerprint 可以方便 STUN Server 區分 STUN message

DNS

透過 SRV 紀錄回傳 STUN Server 相關的服務,SRV 格式如下

_service._proto.name. TTL class SRV priority weight port target.

如果是以 STUN Server 開放 TCP / UDP ,假設 STUN Server domain name 是 stun.example.com的話

_stun._udp.example.com 86400 IN SRV 0 5 3489 stun.example.com.
_stun._tcp.example.com 86400 IN SRV 0 5 3489 stun.example.com.

使用 SRV 好處是可以改變預設的 Port;
如果不用 SRV,也可以用 A / AAAA 紀錄返回 IP List,但 Port 就只能用預設的

身份驗證與訊息完整性檢查

共有兩種身份驗證的方式,

短期

Client / Server 會預先共享同一個 secret,後續 Client 產生一個有時間限制的 credential (password),Server 收到後會用相同的 secret 做驗證;
確保訊息完整性則透過 MESSAGE-INTEGRITY 欄位,此欄位產生的方式是把 STUN Message 做 HMAC_SHA1;

Server 收到STUN Message 後的驗證流程如下

  1. 沒有 MESSAGE-INTEGRITY 和 USERNAME 欄位則回傳 401
  2. 檢查 USERNAME 是否合法
  3. 用 passwrod 與 username 計算出 message intergrity 的值並比對
  4. 都通過則產生 response,response 同樣要包含 MESSAGE-INTEGRITY

Client 收到 response 也需要檢查 MESSAGE-INTEGRITY

因為 credential 有時間限制,所以不會遇到回放(replay)攻擊

長期

Server / Client 固定長期使用相同的 username / password

步驟

  1. Client 不帶任何 credential 發起
  2. Server reject,並分配 realm (指引 client 選擇 credential) 與 nonce (類似於 cookie,指定 duration / client identity 等) 多一層保護
  3. Client retry ,戴上 credential 與 nonce + message-integrity (對整個 request 做 HMAC)
  4. Server 檢查 auth 與 integrity

Server 收到訊息時會依序檢查

  1. MESSAGE-INTEGRITY: 沒有回傳 401
  2. 如果沒有 username / password / realm / nonce ,回傳 400
  3. Nonce 過期了,438
  4. Username 無效,401
  5. Message-integrity 錯誤,回傳 401
  6. ALTERNATE-SERVER Mechanism 如果 Server 希望 Redirect Client 去別的 Server,可以回傳 error code 300 並指定 ALTERNATE-SERVER Client 收到後,用相同的 transport protocol / credential 對新的 Server 重啟 transaction

之所以開頭要產生一次 auth failed 的 request是要了去跟 Server 拿 Nonce,主要是為了避免回放攻擊
回放攻擊主要是 如果有中間人,他拿到 Client request 記錄下來,把同樣的 request 往 Server 送,因為 credential 是長期有效所以中間人也能夠通過驗證,即使是有 TLS 保護 / 中間人不知道 username, password 回放攻擊都有用;
所以才需要 Nonce 一個由 Server 核發一次性的 Token,包裝在 STUN Message 中,超出時間就會被認定無效,從而避免回放攻擊

STUN Attributes

再 STUN Header 之後,可以接零至多個 attribute,attribute 採用 TLV encode

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Type                  |            Length             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Value (variable)                ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

如果 response 中有重複的 attribute,只有 第一個 會被考慮,其餘會被捨棄 Type 再 0x0000 and 0x7FFF 是必須要被處理的,如果 STUN agent 無法處理則會失敗 0x8000~0xFFFF 則是 optional 以下舉幾個常見的 attribute

MAPPED-ADDRESS

主要是為了兼容舊版 STUN,顯示 Client 最後一個 Public NAT 的 ip / port

XOR-MAPPED-ADDRESS

雷同於上者,但是 ip / port 都是跟 magic-cookie 前 16 bits 做 XOR 儲存 (ipv4 / ipv6 xor 方式不同),原因是發現有些 NAT 如果看到 payload 是自己的 public IP 會去修改,xor 之後就沒這個問題

原文解釋 deployment experience found that some NATs rewrite the 32-bit binary payloads containing the NAT’s public IP address, such as STUN’s MAPPED-ADDRESS attribute, in the well-meaning but misguided attempt at providing a generic ALG function.

USERNAME

用來驗證用的,必須是用 utf-8 encode 並小於 513 bets SASLprep

MESSAGE-INTEGRITY

對 STUN Message 取 HMAC-SHA1,固定長度為 20 bytes (因為 sha1 ) HMAC key 會因為 credential 不同而有所不同

  1. Long term: key = MD5(username “:” realm “:” SASLprep(password))
  2. Short term: key = SASLprep(password)

需注意 hash 包含整個 STUN Message,同時也包含了 Message length,所以在產生 hash 前,MESSAGE-INTEGRITY 也必須先安插進去 STUM Message 中並帶 dummy content,其他在之後的屬性則被排除在外 驗證時也必須遵守相同的流程

Fingerprint

對整個 STUN Message (排除自己) 取 CRC-32,接著與 0x5354554e 做 XOR ( 避免其他 packet 也用 CRC-32 Fingerprint 必須是最後一個屬性

the FINGERPRINT attribute MUST be the last attribute in the message, and thus will appear after MESSAGE-INTEGRITY.

Error Code

包含數值的 error code 從 300~699,保持與 SIP / HTTP 相似的羽翼 再加上 rease: utf-8的文字描述 ,主要是讓用戶可以理解

REALM

如果 STUN Server 同時支援多個 domain,透過 REALM 可以區分不同 domain 使用的設定

NONCE

用於每次連線避免 replay 問題的方式,類似於 web 的 cookie

UNKNOWN-ATTRIBUTES

再 error code 420 時出現

SOFTWARE

描述軟體的版本 / 製造商等資訊

ALTERNATE-SERVER

Server 要求轉換時

Security

以下條列幾種被攻擊的可能與防範措施

  1. Attacker 可能竄改 STUN 訊息:
    但可以用 message integrity 驗證防止
  2. Attacker 可以居中回傳 error response
    在某些如驗證失敗的訊息,這個就不好防堵,除非使用 TLS 才能杜絕問題
  3. HMAC可能遭受字典攻擊:
    因為 STUN 利用 HMAC,可能遭受字典攻擊,請確保 password 足夠複雜,或是用 TLS 防止問題;不排除 SHA1 之後被攻破,未來可能增加新的欄位與新的 hash 機制
  4. DoS 部分:
    STUN server 是 stateless,所以比較不會被 DoS 打垮;
    Attacker 可能假冒 source IP,讓 STUN server 去攻擊受害者,攻擊不會被跨大,要從 ingress 去過濾 ip;
  5. SOFTWARE 揭露版本資訊,可能變成潛藏的落點
    STUN Server 應該要有對應的設定去關閉此選項
  6. 修改 source ip
    Attack 居中的話,可以攔截client 的 source ip 並修改,server 收到錯的 ip 就會用 XOR-MAPPED-ADDRESS 回傳回,這幾乎不可能阻擋,因為正常的 NAT 也會去修改 source ip;
    只能在更上層的協議,例如 ICE 去驗證 address 的正確性