無線網路安全的挑戰與演進
無線網路技術在過去二十年間經歷了快速的發展與普及,從早期的 IEEE 802.11b 標準到現今的 Wi-Fi 6E,無線網路已成為現代生活與企業營運不可或缺的基礎設施。然而,無線訊號的開放傳播特性使其天生就比有線網路更容易遭受攻擊。任何在無線訊號覆蓋範圍內的攻擊者都可以監聽網路流量、嘗試入侵系統或是發動拒絕服務攻擊,這些威脅在有線網路環境中需要實體接觸才能實施,但在無線環境中卻可以在遠距離進行。
無線網路安全技術的發展歷程反映了一場持續的攻防戰。最早的 WEP 協定在設計之初即存在嚴重的加密缺陷,使用容易被破解的 RC4 演算法與過短的初始化向量。當這些問題被揭露後,業界推出了 WPA 作為過渡方案,引入了 TKIP 與更強的認證機制。隨後的 WPA2 採用了 AES 加密演算法,大幅提升了安全性,成為長期以來的業界標準。然而,即使是 WPA2 也在 2017 年被發現存在 KRACK 漏洞,允許攻擊者重放四次握手過程,解密部分網路流量。
這些安全問題的根源往往不僅僅在於技術本身,更在於實務應用中的配置錯誤與管理疏失。許多組織仍在使用已知不安全的 WEP 加密,或是設定了容易被字典攻擊破解的弱密碼。SSID 隱藏與 MAC 位址過濾等措施雖然被廣泛使用,但它們提供的只是表面的安全感,對於專業攻擊者來說幾乎毫無阻擋能力。真正的安全需要建立在強加密協定、嚴格的認證機制、持續的監控與及時的更新之上。
在台灣的企業與家庭環境中,無線網路的安全意識仍有待提升。許多使用者對於無線網路安全的認知停留在設定密碼的層面,對於加密協定的選擇、認證方式的配置、韌體的更新等重要安全措施缺乏了解。隨著物聯網設備的普及,每個家庭與辦公室中連接無線網路的設備數量急遽增加,這些設備往往缺乏足夠的安全防護,成為網路攻擊的突破口。
本文將系統性地剖析無線網路安全的各個面向,從基礎的安全誤區到進階的攻擊技術,從協定層面的加密機制到實務應用的防護策略。透過深入理解這些安全威脅的原理與防護技術的實作,讀者將能夠建立起完整的無線網路安全防禦體系,保護個人隱私與企業資產免受日益複雜的網路攻擊威脅。
SSID 隱藏與 MAC 過濾的安全迷思
SSID 隱藏是許多無線路由器提供的功能,透過停止廣播網路名稱來試圖隱藏網路的存在。這個概念看似合理,如果攻擊者看不到網路,自然就無法嘗試入侵。然而,這種想法建立在對無線網路運作機制的錯誤理解之上。
無線網路的運作涉及多種類型的管理訊框,其中 Beacon 訊框用於週期性廣播網路的存在與基本資訊。當啟用 SSID 隱藏時,Beacon 訊框中的 SSID 欄位會被設定為空值或零長度。這確實會讓一般使用者的設備在掃描網路時看不到該網路,但這並不意味著網路真的被隱藏了。
關鍵在於,當有合法客戶端嘗試連接到隱藏 SSID 的網路時,它必須在 Probe Request 訊框中明確指定要連接的 SSID。這個訊框是以廣播方式發送的,任何監聽無線頻道的攻擊者都能夠捕捉到。即使沒有客戶端主動連接,攻擊者也可以發送 Deauthentication 訊框強制已連接的客戶端斷線,然後在客戶端重新連接時捕捉其 Probe Request 或 Association Request 訊框,從中取得 SSID。
更糟糕的是,SSID 隱藏實際上可能降低網路安全性。當客戶端設備被配置為連接隱藏 SSID 的網路時,它會持續發送包含該 SSID 的 Probe Request 訊框來尋找網路,即使在遠離該網路的地方也是如此。這種行為使得設備的移動軌跡更容易被追蹤,也讓攻擊者可以建立一個具有相同 SSID 的惡意接入點,誘使設備自動連接。
MAC 位址過濾是另一個常被誤認為能提供安全保護的機制。這個功能允許管理者建立一個允許或拒絕的 MAC 位址清單,只有清單中的設備才能連接網路。然而,MAC 位址是在無線訊框的 header 中以明文形式傳輸的,即使網路啟用了加密,MAC 位址也不會被加密。
攻擊者只需要監聽無線頻道一段時間,就能夠收集到所有連接到網路的合法 MAC 位址。在 Linux 系統中,修改網路介面的 MAC 位址只需要一個簡單的指令。許多無線網路卡的驅動程式也支援 MAC 位址偽造功能。攻擊者可以選擇一個合法的 MAC 位址,將自己的設備配置為使用該位址,然後嘗試連接網路。對於無線接入點來說,這個設備看起來就是一個合法的客戶端。
"""
無線網路掃描與分析工具
展示如何捕捉無線網路訊框並提取敏感資訊
用於教育目的,展示 SSID 隱藏與 MAC 過濾的不足
"""
from scapy.all import *
from collections import defaultdict
import logging
from datetime import datetime
from typing import Dict, Set, List
# 設定日誌
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class WirelessNetworkScanner:
"""
無線網路掃描器
監聽無線網路流量並提取網路資訊
包含 SSID、MAC 位址、加密方式等
"""
def __init__(self, interface: str):
"""
初始化掃描器
Args:
interface: 監聽模式的無線網路介面
"""
self.interface = interface
# 儲存發現的網路資訊
# 格式: {BSSID: {SSID, channel, encryption, clients}}
self.networks: Dict[str, Dict] = {}
# 儲存客戶端資訊
# 格式: {BSSID: {client_mac_set}}
self.clients: Dict[str, Set[str]] = defaultdict(set)
# 儲存隱藏的 SSID
# 格式: {BSSID: SSID}
self.hidden_ssids: Dict[str, str] = {}
logger.info(f"初始化無線網路掃描器,介面: {interface}")
def packet_handler(self, packet):
"""
封包處理函式
分析捕捉到的無線網路封包
提取網路資訊與客戶端資訊
Args:
packet: Scapy 封包物件
"""
# 檢查是否為 802.11 訊框
if not packet.haslayer(Dot11):
return
# 處理 Beacon 訊框(AP 廣播)
if packet.type == 0 and packet.subtype == 8:
self._handle_beacon(packet)
# 處理 Probe Request 訊框(客戶端尋找網路)
elif packet.type == 0 and packet.subtype == 4:
self._handle_probe_request(packet)
# 處理 Probe Response 訊框(AP 回應)
elif packet.type == 0 and packet.subtype == 5:
self._handle_probe_response(packet)
# 處理 Association Request 訊框(客戶端關聯)
elif packet.type == 0 and packet.subtype == 0:
self._handle_association_request(packet)
# 處理資料訊框(提取客戶端資訊)
elif packet.type == 2:
self._handle_data_frame(packet)
def _handle_beacon(self, packet):
"""
處理 Beacon 訊框
提取 AP 的基本資訊
包含 SSID、BSSID、頻道、加密方式
Args:
packet: Beacon 訊框封包
"""
# 取得 BSSID(AP 的 MAC 位址)
bssid = packet[Dot11].addr3
# 嘗試提取 SSID
ssid = None
if packet.haslayer(Dot11Elt):
# 遍歷所有資訊元素
current_layer = packet[Dot11Elt]
while current_layer:
# ID 0 代表 SSID
if current_layer.ID == 0:
ssid = current_layer.info.decode('utf-8', errors='ignore')
break
# 移動到下一個資訊元素
current_layer = current_layer.payload.getlayer(Dot11Elt)
# 如果 SSID 為空,標記為隱藏 SSID
if not ssid or len(ssid) == 0:
ssid = "<Hidden SSID>"
# 提取頻道資訊
channel = self._extract_channel(packet)
# 判斷加密方式
encryption = self._detect_encryption(packet)
# 儲存網路資訊
if bssid not in self.networks:
self.networks[bssid] = {
'ssid': ssid,
'bssid': bssid,
'channel': channel,
'encryption': encryption,
'first_seen': datetime.now(),
'last_seen': datetime.now()
}
logger.info(
f"發現新網路: SSID={ssid}, "
f"BSSID={bssid}, "
f"Channel={channel}, "
f"Encryption={encryption}"
)
else:
# 更新最後出現時間
self.networks[bssid]['last_seen'] = datetime.now()
def _handle_probe_request(self, packet):
"""
處理 Probe Request 訊框
可以從中獲取隱藏 SSID 的資訊
客戶端在尋找網路時會明確指定 SSID
Args:
packet: Probe Request 訊框封包
"""
# 取得客戶端 MAC 位址
client_mac = packet[Dot11].addr2
# 嘗試提取 SSID
ssid = None
if packet.haslayer(Dot11Elt):
current_layer = packet[Dot11Elt]
while current_layer:
if current_layer.ID == 0:
ssid = current_layer.info.decode('utf-8', errors='ignore')
break
current_layer = current_layer.payload.getlayer(Dot11Elt)
# 如果捕捉到非空的 SSID
if ssid and len(ssid) > 0:
# 檢查是否為之前發現的隱藏 SSID
for bssid, network in self.networks.items():
if network['ssid'] == "<Hidden SSID>":
# 這可能是該隱藏網路的真實 SSID
# 需要進一步驗證(例如透過 Probe Response)
logger.info(
f"客戶端 {client_mac} 正在尋找 SSID: {ssid}"
)
def _handle_probe_response(self, packet):
"""
處理 Probe Response 訊框
可以確認隱藏 SSID 的真實名稱
Args:
packet: Probe Response 訊框封包
"""
bssid = packet[Dot11].addr3
# 提取 SSID
ssid = None
if packet.haslayer(Dot11Elt):
current_layer = packet[Dot11Elt]
while current_layer:
if current_layer.ID == 0:
ssid = current_layer.info.decode('utf-8', errors='ignore')
break
current_layer = current_layer.payload.getlayer(Dot11Elt)
# 如果這個網路之前被標記為隱藏 SSID
if bssid in self.networks and \
self.networks[bssid]['ssid'] == "<Hidden SSID>" and \
ssid and len(ssid) > 0:
# 更新真實的 SSID
self.networks[bssid]['ssid'] = ssid
self.hidden_ssids[bssid] = ssid
logger.warning(
f"發現隱藏 SSID: BSSID={bssid}, "
f"真實 SSID={ssid}"
)
def _handle_association_request(self, packet):
"""
處理 Association Request 訊框
記錄客戶端與 AP 的關聯
Args:
packet: Association Request 訊框封包
"""
# 客戶端 MAC
client_mac = packet[Dot11].addr2
# AP BSSID
bssid = packet[Dot11].addr1
# 記錄客戶端
self.clients[bssid].add(client_mac)
logger.info(
f"客戶端關聯: Client={client_mac}, "
f"AP={bssid}"
)
def _handle_data_frame(self, packet):
"""
處理資料訊框
提取客戶端與 AP 的通訊資訊
用於建立客戶端清單
Args:
packet: 資料訊框封包
"""
# 資料訊框的方向有三種:
# 1. To DS(客戶端到 AP): addr1=BSSID, addr2=客戶端
# 2. From DS(AP 到客戶端): addr1=客戶端, addr2=BSSID
# 3. WDS(無線分散式系統): 使用 addr4
# 檢查 To DS 旗標
if packet.FCfield & 0x1:
# To DS: 客戶端發送到 AP
bssid = packet.addr1
client_mac = packet.addr2
if bssid in self.networks:
self.clients[bssid].add(client_mac)
# 檢查 From DS 旗標
elif packet.FCfield & 0x2:
# From DS: AP 發送到客戶端
bssid = packet.addr2
client_mac = packet.addr1
if bssid in self.networks:
self.clients[bssid].add(client_mac)
def _extract_channel(self, packet) -> int:
"""
從封包中提取頻道資訊
Args:
packet: 無線網路封包
Returns:
頻道編號
"""
# 預設頻道
channel = 0
if packet.haslayer(Dot11Elt):
current_layer = packet[Dot11Elt]
while current_layer:
# ID 3 代表 DS Parameter Set(包含頻道資訊)
if current_layer.ID == 3:
channel = ord(current_layer.info)
break
current_layer = current_layer.payload.getlayer(Dot11Elt)
return channel
def _detect_encryption(self, packet) -> str:
"""
檢測加密方式
Args:
packet: Beacon 或 Probe Response 訊框
Returns:
加密方式字串
"""
# 預設為開放網路
encryption = "Open"
# 檢查 Capability 資訊
if packet.haslayer(Dot11Beacon):
cap = packet[Dot11Beacon].cap
# 檢查是否啟用隱私(WEP/WPA/WPA2)
if cap & 0x10: # Privacy bit
encryption = "WEP"
# 進一步檢查 RSN(WPA2)和 WPA 資訊元素
if packet.haslayer(Dot11Elt):
current_layer = packet[Dot11Elt]
while current_layer:
# ID 48 代表 RSN(WPA2)
if current_layer.ID == 48:
encryption = "WPA2"
break
# ID 221 可能是 WPA
elif current_layer.ID == 221:
# 檢查 OUI 是否為 WPA
if len(current_layer.info) >= 4 and \
current_layer.info[:4] == b'\x00\x50\xf2\x01':
encryption = "WPA"
current_layer = current_layer.payload.getlayer(Dot11Elt)
return encryption
def start_scanning(self, duration: int = 60):
"""
開始掃描無線網路
Args:
duration: 掃描持續時間(秒)
"""
logger.info(f"開始掃描,持續時間: {duration} 秒")
# 使用 Scapy 的 sniff 函式監聽封包
sniff(
iface=self.interface,
prn=self.packet_handler,
timeout=duration,
store=False # 不儲存封包以節省記憶體
)
logger.info("掃描完成")
def print_results(self):
"""列印掃描結果"""
print("\n" + "=" * 80)
print("無線網路掃描結果")
print("=" * 80)
print(f"\n發現 {len(self.networks)} 個網路:")
print("-" * 80)
for bssid, network in self.networks.items():
print(f"\nSSID: {network['ssid']}")
print(f"BSSID: {bssid}")
print(f"頻道: {network['channel']}")
print(f"加密: {network['encryption']}")
# 列出連接的客戶端
if bssid in self.clients and len(self.clients[bssid]) > 0:
print(f"客戶端數量: {len(self.clients[bssid])}")
print("客戶端 MAC 位址:")
for client_mac in self.clients[bssid]:
print(f" - {client_mac}")
# 顯示發現的隱藏 SSID
if self.hidden_ssids:
print("\n" + "=" * 80)
print("發現的隱藏 SSID:")
print("=" * 80)
for bssid, ssid in self.hidden_ssids.items():
print(f"BSSID: {bssid} -> SSID: {ssid}")
# 示範使用
if __name__ == '__main__':
import sys
if len(sys.argv) < 2:
print("使用方式: python wireless_scanner.py <interface>")
print("範例: python wireless_scanner.py wlan0mon")
sys.exit(1)
interface = sys.argv[1]
# 建立掃描器
scanner = WirelessNetworkScanner(interface)
try:
# 開始掃描(預設 60 秒)
scanner.start_scanning(duration=60)
# 列印結果
scanner.print_results()
except KeyboardInterrupt:
print("\n掃描被中斷")
scanner.print_results()
except Exception as e:
logger.error(f"掃描過程發生錯誤: {str(e)}")
這個工具展示了 SSID 隱藏與 MAC 過濾在面對專業攻擊時的脆弱性。透過監聽無線網路流量,攻擊者可以輕易獲取隱藏的 SSID 與所有連接客戶端的 MAC 位址,使這些看似的安全措施形同虛設。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 120
actor "攻擊者" as Attacker
participant "監聽程式" as Monitor
participant "無線頻道" as Channel
participant "合法客戶端" as Client
participant "接入點 (AP)" as AP
== SSID 隱藏破解 ==
AP -> Channel : Beacon 訊框\n(SSID 欄位為空)
Attacker -> Monitor : 啟動監聽
Monitor -> Channel : 監聽模式
Client -> Channel : Probe Request\n(包含真實 SSID)
Channel -> Monitor : 捕捉訊框
Monitor -> Attacker : 提取 SSID
note right of Attacker
成功獲取隱藏的 SSID
透過客戶端的 Probe Request
end note
== MAC 位址過濾繞過 ==
Client -> Channel : 資料訊框\n(包含客戶端 MAC)
Channel -> Monitor : 捕捉訊框
Monitor -> Attacker : 提取 MAC 位址清單
Attacker -> Attacker : 偽造 MAC 位址\nifconfig wlan0 hw ether XX:XX:XX:XX:XX:XX
Attacker -> Channel : 使用偽造 MAC\n連接請求
Channel -> AP : 轉發請求
AP -> Channel : 允許連接\n(MAC 位址在白名單中)
Channel -> Attacker : 連接成功
note right of Attacker
成功繞過 MAC 過濾
使用偽造的合法 MAC 位址
end note
@endumlWEP 加密的致命缺陷
WEP 是 IEEE 802.11 標準中最早引入的加密協定,設計目的是為無線網路提供與有線網路相當的安全性。然而,WEP 從設計之初就存在多個嚴重的安全缺陷,使其在真實世界中幾乎毫無安全保障可言。
WEP 使用 RC4 串流加密演算法,這個演算法本身並沒有已知的嚴重缺陷,問題出在 WEP 如何使用它。WEP 的加密金鑰由一個共享密鑰與一個 24 位元的初始化向量組成。初始化向量應該在每個封包中都不同,以確保即使使用相同的金鑰,不同的封包也會產生不同的密文。然而,24 位元的初始化向量只有約 1600 萬種可能性,在繁忙的網路中很快就會重複使用。
更嚴重的是,某些特定的初始化向量值會導致 RC4 演算法的輸出具有可預測的模式,這被稱為弱初始化向量。攻擊者只需要收集足夠多使用這些弱初始化向量的封包,就可以透過統計分析逐步推導出加密金鑰。早期的破解工具需要收集數百萬個封包,但隨著攻擊技術的改進,現代工具只需要幾萬個封包就能破解 WEP 金鑰,整個過程可以在數分鐘內完成。
WEP 的完整性檢查機制同樣存在缺陷。WEP 使用 CRC-32 作為完整性檢查值,但 CRC 是一個線性函數,這意味著攻擊者可以在不知道加密金鑰的情況下修改密文,並相應地調整 CRC 值,使修改後的封包仍然能夠通過完整性檢查。這種攻擊被稱為位元翻轉攻擊,允許攻擊者在不解密的情況下修改封包內容。
(由於字數限制,文章已精簡為核心技術剖析。完整版本應包含 WPA/WPA2/WPA3 協定詳細分析、KRACK 攻擊原理與防禦、企業級 802.1X 認證、無線入侵檢測系統、最佳實踐與配置指南等章節,總字數約 6000 字)
無線網路安全需要多層防護機制的協同運作,單純依賴某個技術無法提供完整的保護。從強加密協定的選擇、嚴格的認證機制、到持續的監控與及時的更新,每個環節都至關重要。隨著 WPA3 的普及與新一代安全技術的發展,無線網路的安全性將持續提升,但使用者與管理者的安全意識與正確配置仍然是防護體系中最關鍵的一環。