본문 바로가기

VLAN과 2계층 보안

하나의 스위치를 여러 논리적 LAN으로 나누는 VLAN과 802.1Q 태그의 구조를 바이트로 만들어 보고, VLAN 사이의 통신이 라우팅과 정책 지점을 거치는 이유를 정리합니다. MAC 플러딩, ARP 스푸핑, 가짜 DHCP 서버, VLAN 호핑 같은 2계층 공격과 포트 보안, DHCP 스누핑, DAI 같은 방어를 모델과 함께 살펴봅니다.

앞 글에서 스위치가 MAC 주소를 배워 프레임을 필요한 포트로 보내는 방법을 보았습니다. 실제 회사의 네트워크는 여기서 그치지 않습니다. 부서나 용도에 따라 네트워크를 나누어야 하고, 같은 LAN 안의 누군가가 다른 사람의 트래픽을 가로채지 못하게 막아야 합니다. 이 글에서는 네트워크를 논리적으로 나누는 VLAN과, 데이터 링크 계층에서 일어나는 공격과 그 방어를 다룹니다.

VLAN: 하나의 스위치를 여러 LAN으로

VLAN(virtual LAN)은 물리적인 스위치 하나를 여러 개의 논리적인 2계층 네트워크로 나누는 기술입니다. 같은 스위치에 꽂혀 있어도 VLAN이 다르면 서로 다른 LAN처럼 동작해서, 한 VLAN의 브로드캐스트는 다른 VLAN으로 퍼지지 않습니다.

VLAN BOUNDARY · L2 SEPARATION / L3 POLICY

브로드캐스트 분리와 VLAN 사이 접근 정책은 다른 역할이다

VLAN 10과 20은 독립된 L2 범위입니다. 사이의 IP 통신은 L3 게이트웨이를 거치며, 관리자가 적용한 ACL·방화벽 정책이 허용한 트래픽을 전달하도록 구성합니다.

VLAN의 L2 경계와 VLAN 사이의 정책 지점두 VLAN은 서로 다른 브로드캐스트 도메인입니다. VLAN 사이의 IP 통신은 L3 게이트웨이를 거치며 ACL 또는 방화벽 정책이 허용한 트래픽만 전달하도록 구성합니다.VLAN 10독립된 L2 범위L3 게이트웨이ACL · 방화벽 정책VLAN 20독립된 L2 범위L3 처리정책 허용 시
VLAN의 L2 경계와 VLAN 사이의 정책 지점두 VLAN은 서로 다른 브로드캐스트 도메인입니다. VLAN 사이의 IP 통신은 L3 게이트웨이를 거치며 ACL 또는 방화벽 정책이 허용한 트래픽만 전달하도록 구성합니다.VLAN 10독립된 L2브로드캐스트 범위L3 게이트웨이ACL · 방화벽 정책VLAN 20독립된 L2브로드캐스트 범위L3 처리정책 허용 시

VLAN만 나누어서는 접근 금지 정책이 완성되지 않는다

VLAN: ARP·DHCP 등 브로드캐스트를 같은 VLAN 안에 제한

L3 정책: 개발팀→재무팀 서버 같은 접근의 허용·차단을 결정

트렁크는 같은 VLAN을 여러 스위치로 확장할 수 있습니다. VLAN 간 L3 라우팅과 접근 정책은 별도로 설정합니다.

VLAN으로 나누면 ARP와 DHCP 같은 브로드캐스트가 VLAN 안에만 머물러 장치마다 받는 부담이 줄고, 물리적인 위치와 상관없이 부서나 서비스, 보안 구역별로 장치를 묶을 수 있습니다. 가장 흔한 방법은 스위치의 포트마다 VLAN 번호를 정해 두는 포트 기반 VLAN이며, 장치의 MAC 주소로 VLAN을 정하는 장비도 있습니다.

다만 VLAN은 방화벽이 아닙니다. 그림처럼 VLAN 사이의 IP 통신은 L3 게이트웨이를 거치는데, 게이트웨이가 라우팅만 하고 아무 정책도 두지 않으면 VLAN이 달라도 IP 통신은 그대로 됩니다. "개발팀은 재무팀 서버에 접근할 수 없다" 같은 규칙은 VLAN으로 나눈 뒤 그 게이트웨이에 ACL이나 방화벽 정책을 두어야 완성됩니다.

802.1Q 태그와 트렁크

같은 VLAN을 여러 스위치에 걸쳐 쓰려면, 스위치 사이의 링크가 여러 VLAN의 프레임을 함께 나르면서도 어느 프레임이 어느 VLAN의 것인지 구분해야 합니다. 이런 링크의 포트를 트렁크 포트(trunk port)라고 하고, 단말 하나가 연결되어 한 VLAN에만 속하는 포트를 액세스 포트(access port)라고 합니다. 트렁크 위의 프레임에는 IEEE 802.1Q 태그가 붙습니다.

802.1Q 태그는 이더넷 프레임의 출발지 MAC 주소 바로 뒤에 끼워 넣는 4바이트입니다. 앞의 2바이트 TPID는 0x8100으로 태그가 있다는 표시이고, 뒤의 2바이트 TCI에는 우선순위(PCP) 3비트, DEI 1비트, VLAN ID 12비트가 들어갑니다.

import struct

def vlan_tag(vid, pcp=0, dei=0):
    tci = (pcp << 13) | (dei << 12) | vid           # 우선순위 3비트, DEI 1비트, VLAN ID 12비트
    return struct.pack("!HH", 0x8100, tci)          # TPID 0x8100 다음에 TCI

def describe(frame):
    tpid, tci = struct.unpack("!HH", frame[12:16])
    if tpid != 0x8100:
        return f"태그 없음, EtherType 0x{tpid:04x}"
    inner = struct.unpack("!H", frame[16:18])[0]
    return f"VLAN {tci & 0x0FFF}, 우선순위 {tci >> 13}, 안의 EtherType 0x{inner:04x}"

untagged = bytes(12) + b"\x08\x00" + b"IPv4 packet..."
tagged = untagged[:12] + vlan_tag(20, pcp=5) + untagged[12:]   # 출발지 MAC 뒤에 4바이트를 끼운다
print(f"트렁크로 나가기 전: {describe(untagged)}, {len(untagged)}바이트")
print(f"트렁크 위에서:     {describe(tagged)}, {len(tagged)}바이트")
print(f"쓸 수 있는 VLAN ID: 1부터 {2**12 - 2}까지")
트렁크로 나가기 전: 태그 없음, EtherType 0x0800, 28바이트
트렁크 위에서:     VLAN 20, 우선순위 5, 안의 EtherType 0x0800, 32바이트
쓸 수 있는 VLAN ID: 1부터 4094까지

태그가 끼워지면 원래 EtherType이 있던 자리에 0x8100이 오므로, 받는 쪽은 그것을 보고 태그를 읽은 뒤 그 뒤의 진짜 EtherType을 봅니다. 프레임은 4바이트 길어져, 태그가 붙은 프레임의 최대 크기는 1522바이트입니다. VLAN ID는 12비트라 0부터 4095까지 나타낼 수 있지만, 0은 VLAN 없이 우선순위만 나타낼 때 쓰고 4095는 예약되어 있어 실제로는 1부터 4094까지를 씁니다.

트렁크에서 태그를 붙이지 않고 보내는 VLAN을 네이티브 VLAN(native VLAN)이라고 합니다. 많은 장비가 VLAN 1을 기본 네이티브 VLAN으로 쓰는데, 뒤에서 볼 VLAN 호핑을 막으려고 운영망에서는 네이티브 VLAN을 아무도 쓰지 않는 VLAN으로 바꾸거나 네이티브 VLAN에도 태그를 붙이도록 설정합니다.

더 깊이: VLAN 4094개로 모자랄 때의 VXLAN

VLAN ID가 12비트라는 점은 고객마다 네트워크를 따로 나누어 주는 클라우드 데이터센터에서 한계가 됩니다. 이 문제를 풀려고 나온 것이 VXLAN(virtual extensible LAN)이며, RFC 7348은 4094개라는 VLAN의 한도로는 부족하다는 문제에서 출발합니다. VXLAN은 이더넷 프레임 전체를 UDP 데이터그램에 담아 IP 망 위로 보내고, VLAN ID 대신 24비트의 VNI(VXLAN network identifier)로 구획을 나누므로 약 1,600만 개의 구획을 만들 수 있습니다. 목적지 UDP 포트는 IANA가 정한 4789번입니다.

이렇게 2계층 프레임을 3계층 망 위로 실어 나르는 방식을 오버레이(overlay)라고 합니다. 바깥은 평범한 IP 망이므로 STP로 링크를 막아 두는 대신 라우팅으로 여러 경로를 함께 쓸 수 있습니다. RFC는 바깥 UDP의 출발지 포트를 안쪽 프레임의 헤더를 해시해 정하라고 권하는데, 그래야 앞 글의 LAG처럼 흐름을 해시로 나누는 장비들이 VXLAN 트래픽도 여러 경로에 퍼뜨릴 수 있기 때문입니다. 대신 프레임마다 바깥 헤더가 붙어 커지므로, 끝의 장비는 VXLAN 패킷을 단편화하지 않아야 하고 바깥 망의 MTU를 넉넉히 잡아야 합니다.

VLAN 사이의 라우팅

VLAN끼리 통신하려면 3계층의 라우팅이 필요합니다. VLAN마다 라우터의 물리 포트를 하나씩 연결하는 방법은 단순하지만 포트가 많이 들고, 트렁크 하나로 라우터에 연결해 VLAN마다 서브인터페이스를 두는 라우터 온 어 스틱(router-on-a-stick)은 경제적이지만 그 트렁크가 병목이 될 수 있습니다. 회사의 캠퍼스망에서는 스위치 안에 VLAN마다 가상 인터페이스(SVI)를 두고 스위치가 직접 라우팅하는 L3 스위치가 흔합니다.

어느 방법이든 VLAN 10의 PC가 VLAN 20의 서버에 보낼 때, PC는 ARP로 자기 VLAN의 게이트웨이 MAC 주소를 알아내 프레임을 보내고, 게이트웨이가 패킷을 VLAN 20으로 라우팅합니다. PC의 ARP 브로드캐스트가 VLAN 20까지 넘어가는 일은 없습니다. 접근을 막거나 허락하는 정책은 바로 이 게이트웨이에서 적용합니다.

2계층의 공격과 방어

같은 LAN, 같은 VLAN 안에서는 장치들이 서로를 믿는다는 가정 위에서 프로토콜이 동작합니다. ARP는 응답이 진짜인지 확인하지 않고, 스위치는 출발지 주소가 진짜인지 묻지 않으며, DHCP 클라이언트는 먼저 응답한 서버를 믿습니다. 2계층의 공격은 이 믿음을 파고듭니다.

MAC 플러딩

MAC 플러딩(MAC flooding)은 출발지 MAC 주소를 계속 바꾼 가짜 프레임을 쏟아부어 스위치의 MAC 주소 표를 가득 채우는 공격입니다. 표가 가득 차면 새로 배울 자리가 없어 목적지를 모르는 프레임이 늘어나고, 스위치가 이를 모든 포트로 퍼뜨리면 공격자는 남의 트래픽을 엿볼 수 있게 됩니다. 실제로 어떻게 동작하는지는 장비마다 다릅니다.

방어는 포트마다 배울 수 있는 MAC 주소의 수를 제한하는 포트 보안(port security)입니다. Cisco IOS 스위치라면 다음처럼 설정합니다.

interface GigabitEthernet0/1
 switchport mode access
 switchport port-security
 switchport port-security maximum 5
 switchport port-security violation shutdown
 switchport port-security aging time 60

이 포트에서는 MAC 주소를 다섯 개까지만 배우고, 그보다 많은 주소가 나타나면 포트를 오류 상태로 내려 차단하며, 배운 주소는 60분 뒤에 잊습니다. 위반했을 때 포트를 내리는 shutdown 대신, 위반한 프레임만 버리고 알림을 남기는 restrict나 알림 없이 버리기만 하는 protect를 고를 수도 있습니다. 명령의 이름과 동작은 장비 제조사마다 다르므로 해당 장비의 설명서를 따라야 합니다.

ARP 스푸핑과 가짜 DHCP 서버

ARP 스푸핑(ARP spoofing)은 "게이트웨이의 IP 주소는 내 MAC 주소"라는 가짜 ARP 응답을 보내 다른 장치들의 ARP 캐시를 오염시키는 공격입니다. 속은 장치들은 게이트웨이로 보내야 할 프레임을 공격자에게 보내고, 공격자는 그것을 엿보거나 고친 뒤 진짜 게이트웨이로 넘기는 중간자 공격(man-in-the-middle)을 할 수 있습니다.

가짜 DHCP 서버도 비슷한 효과를 냅니다. 공격자가 자기 DHCP 서버를 띄워 진짜 서버보다 먼저 응답하면, 클라이언트는 공격자가 정한 게이트웨이와 DNS 서버를 받아 씁니다.

이 둘은 DHCP 스누핑(DHCP snooping)과 DAI(dynamic ARP inspection)를 함께 써서 막습니다. DHCP 스누핑은 진짜 DHCP 서버가 있는 쪽의 포트만 신뢰하고, 그 밖의 포트에서 들어온 DHCP 서버의 응답은 버립니다. 그러면서 주소가 할당되는 순간을 지켜보아 어느 포트의 어느 MAC 주소가 어떤 IP 주소를 받았는지 바인딩 표에 기록합니다. DAI는 ARP 메시지를 받을 때마다 그 발신자의 IP와 MAC이 이 바인딩 표와 맞는지 대조해, 맞지 않으면 버립니다. 다음 코드는 이 과정을 흉내 냅니다.

TRUSTED = {"uplink"}                       # 진짜 DHCP 서버가 있는 쪽의 포트
SERVER_MESSAGES = {"OFFER", "ACK"}
bindings = {}                              # DHCP 스누핑이 만든 바인딩: 포트 -> (IP, MAC)

def dhcp(port, message, client=None):
    if message in SERVER_MESSAGES and port not in TRUSTED:
        return "버림(신뢰하지 않는 포트의 서버 응답)"
    if message == "ACK":
        client_port, ip, mac = client
        bindings[client_port] = (ip, mac)  # 주소가 할당되는 순간을 기록해 둔다
        return f"통과, 바인딩 {client_port}: {ip} / {mac}"
    return "통과"

def arp(port, sender_ip, sender_mac):      # DAI: ARP의 발신자 정보를 바인딩과 대조한다
    return "통과" if bindings.get(port) == (sender_ip, sender_mac) else "버림"

print("pc1    DISCOVER ->", dhcp("pc1", "DISCOVER"))
print("pc2    OFFER    ->", dhcp("pc2", "OFFER"))
print("uplink ACK      ->", dhcp("uplink", "ACK", client=("pc1", "192.168.1.50", "MAC-1")))
print("pc1의 ARP: 192.168.1.50은 MAC-1 ->", arp("pc1", "192.168.1.50", "MAC-1"))
print("pc2의 ARP: 192.168.1.1은 MAC-2  ->", arp("pc2", "192.168.1.1", "MAC-2"))
pc1    DISCOVER -> 통과
pc2    OFFER    -> 버림(신뢰하지 않는 포트의 서버 응답)
uplink ACK      -> 통과, 바인딩 pc1: 192.168.1.50 / MAC-1
pc1의 ARP: 192.168.1.50은 MAC-1 -> 통과
pc2의 ARP: 192.168.1.1은 MAC-2  -> 버림

pc2가 띄운 가짜 DHCP 서버의 응답은 신뢰하지 않는 포트에서 왔으므로 버려졌고, pc2가 게이트웨이 192.168.1.1을 사칭한 ARP도 바인딩 표에 없는 짝이라 버려졌습니다. 고정 IP를 쓰는 서버처럼 DHCP로 주소를 받지 않는 장치는 바인딩 표에 없으므로, 그런 장치는 정적인 바인딩을 따로 등록해 주어야 DAI에 막히지 않습니다. Cisco IOS라면 VLAN 10에 두 기능을 켜고 업링크 포트만 신뢰하도록 다음처럼 설정합니다.

ip dhcp snooping
ip dhcp snooping vlan 10
ip arp inspection vlan 10
interface GigabitEthernet0/24
 ip dhcp snooping trust
 ip arp inspection trust

중요한 서버에는 IP와 MAC의 짝을 정적 ARP로 고정해 둘 수도 있고, 포트에 장치를 꽂을 때 인증을 요구하는 802.1X로 아예 모르는 장치가 LAN에 들어오지 못하게 할 수도 있습니다. HTTPS 같은 암호화는 ARP 스푸핑 자체를 막지는 못하지만, 중간에 끼어든 공격자가 내용을 읽거나 몰래 고치지 못하게 해서 피해를 줄입니다.

VLAN 호핑

VLAN 호핑(VLAN hopping)은 자기 VLAN이 아닌 다른 VLAN으로 프레임을 보내는 공격입니다. 대표적인 이중 태그(double tagging) 공격에서 공격자는 트렁크의 네이티브 VLAN에 속한 포트에서, 태그를 두 겹 붙인 프레임을 보냅니다. 첫 스위치는 바깥 태그가 네이티브 VLAN이므로 그것을 떼고 트렁크로 내보내는데, 남은 안쪽 태그 때문에 다음 스위치는 이 프레임을 공격 대상의 VLAN 것으로 여깁니다. 응답은 돌아오지 않는 한 방향의 공격이지만, 사용자 포트를 네이티브 VLAN에 두지 않거나 네이티브 VLAN에도 태그를 붙이면 막을 수 있습니다. 또 액세스 포트가 상대와 협상해 스스로 트렁크가 되는 기능을 꺼 두어, 공격자가 스위치 행세를 하며 트렁크를 맺지 못하게 해야 합니다.

정리

  • VLAN은 하나의 스위치를 여러 논리적 LAN으로 나누어 브로드캐스트의 범위를 줄이지만, VLAN 사이의 접근을 막는 것은 게이트웨이의 정책입니다.
  • 트렁크 위의 프레임에는 출발지 MAC 주소 뒤에 TPID 0x8100과 VLAN ID 12비트를 담은 4바이트의 802.1Q 태그가 붙으며, 쓸 수 있는 VLAN ID는 1부터 4094까지입니다.
  • VLAN 사이의 통신은 라우터나 L3 스위치가 라우팅하며, 단말은 자기 VLAN의 게이트웨이 MAC 주소로 프레임을 보냅니다.
  • 포트 보안은 MAC 플러딩을, DHCP 스누핑은 가짜 DHCP 서버를, DHCP 스누핑의 바인딩을 쓰는 DAI는 ARP 스푸핑을 막습니다.
  • 사용자 포트를 네이티브 VLAN에 두지 않고 트렁크 자동 협상을 끄면 VLAN 호핑을 막을 수 있습니다.

연습 문제

1. 정책을 지우면

개발 PC는 VLAN 10, 재무 서버는 VLAN 20에 있고, 두 VLAN 사이의 게이트웨이에는 개발 PC에서 재무 서버로 가는 TCP 443만 허락하는 정책이 있습니다. 누군가 이 정책을 지우고 라우팅은 그대로 두었습니다. "VLAN이 다르니 개발 PC는 여전히 재무 서버에 접근할 수 없다"는 말은 맞나요? 이 정책이 같은 VLAN 안의 ARP 스푸핑도 막아 주나요?

해설 보기

맞지 않습니다. VLAN이 나누는 것은 브로드캐스트가 퍼지는 2계층의 범위이고, 게이트웨이가 라우팅을 하는 한 VLAN 사이의 IP 통신은 됩니다. 정책을 지웠다면 막는 장치가 없어진 것이므로, 개발 PC는 TCP 22를 포함해 모든 포트로 재무 서버에 접근할 수 있습니다. 또 이 정책은 VLAN 사이를 지나는 패킷에만 적용되므로, 같은 VLAN 안에서 주고받는 ARP는 처음부터 이 게이트웨이의 정책을 거치지 않습니다. ARP 스푸핑은 DAI처럼 2계층에서 동작하는 방어가 따로 있어야 막을 수 있습니다.

2. DHCP가 갑자기 안 된다

DHCP 스누핑을 켠 뒤로 모든 PC가 IP 주소를 받지 못합니다. PC들의 DHCP 요청은 스위치를 지나 서버까지 가는 것으로 보입니다. 무엇을 먼저 확인해야 하나요? 모든 포트를 신뢰하도록 바꾸는 것이 좋은 해결책인가요?

해설 보기

진짜 DHCP 서버의 응답이 들어오는 업링크 포트가 신뢰하는 포트로 설정되어 있는지 먼저 확인해야 합니다. 업링크까지 신뢰하지 않는 포트로 남아 있으면, 요청은 서버에 닿지만 서버의 OFFER와 ACK가 스위치에서 버려져 PC가 주소를 받지 못합니다. 모든 포트를 신뢰하도록 바꾸면 문제는 사라지지만 가짜 DHCP 서버를 막는 장치도 함께 사라지므로 좋은 해결책이 아닙니다. 서버 쪽 업링크만 신뢰하도록 고치고, DHCP 서버가 다른 스위치 너머에 있다면 그 경로의 업링크도 확인합니다.

3. DAI에 막힌 프린터

DAI를 켠 뒤로, 고정 IP 주소를 설정해 둔 네트워크 프린터에 아무도 인쇄를 보내지 못합니다. DHCP로 주소를 받는 PC들은 잘 동작합니다. 왜 이런 일이 생기고, 어떻게 고쳐야 하나요?

해설 보기

DAI는 ARP의 발신자 정보를 DHCP 스누핑의 바인딩 표와 대조하는데, 고정 IP를 쓰는 프린터는 DHCP로 주소를 받지 않았으므로 바인딩 표에 항목이 없습니다. 그래서 프린터가 보내는 ARP 응답이 모두 버려져, 다른 장치들이 프린터의 MAC 주소를 알지 못해 프레임을 보낼 수 없습니다. 프린터의 IP, MAC, 포트를 정적인 바인딩으로 등록하거나, 장비가 지원하는 ARP 접근 목록으로 그 짝을 허락해 주어야 합니다. DAI를 끄는 것은 ARP 스푸핑의 방어를 포기하는 것이므로 해결책이 아닙니다.

다음 장의 IPv4 주소 체계에서는 한 계층 위로 올라가, 서로 다른 네트워크를 이어 주는 IP 주소의 구조를 살펴봅니다.