Skip to content

Commit d7a40b1

Browse files
committed
Layer 2 network security bypass using VLAN 0 and/or LLC/SNAP headers
1 parent a968466 commit d7a40b1

8 files changed

+742
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@
99
* [Metadata service MITM allows root privilege escalation (EKS / GKE)](Metadata_MITM_root_EKS_GKE/README.md)
1010

1111
* [runc mount destinations can be swapped via symlink-exchange to cause mounts outside the rootfs (CVE-2021-30465)](runc-symlink-CVE-2021-30465/README.md)
12+
13+
* [Layer 2 network security bypass using VLAN 0, LLC/SNAP headers and invalid length](VLAN0_LLC_SNAP/README.md)

VLAN0/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Bridge firewalling "bypass" using VLAN 0
22

3+
See follow up article: [Layer 2 network security bypass using VLAN 0, LLC/SNAP headers and invalid length](../VLAN0_LLC_SNAP/README.md)
4+
35
L2 networks are insecure by default, vulnerable to ARP, DHCP, Router Advertisement spoofing to name a few.
46
Over the years security mechanisms have been implemented to detect and or stop those attacks.
57
As usual when you try to filter anything, you MUST use an allow list approach, else you risk letting some unwanted traffic go through.

VLAN0_LLC_SNAP/Ethernet-8023-80211.drawio

Lines changed: 160 additions & 0 deletions
Large diffs are not rendered by default.

VLAN0_LLC_SNAP/Ethernet-8023-80211.svg

Lines changed: 3 additions & 0 deletions
Loading

VLAN0_LLC_SNAP/Ethernet-frame-formats.drawio

Lines changed: 181 additions & 0 deletions
Large diffs are not rendered by default.

VLAN0_LLC_SNAP/Ethernet-frame-formats.svg

Lines changed: 3 additions & 0 deletions
Loading

VLAN0_LLC_SNAP/README.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Layer 2 network security bypass using VLAN 0, LLC/SNAP headers and invalid length
2+
3+
## TLDR
4+
5+
You should always drop unknown/unclassified traffic.
6+
Using VLAN 0, LLC/SNAP headers and invalid length gives you multiple ways to
7+
encapsulate the same L3 payload on Ethernet/Wifi, allowing to bypass some
8+
L2 network security control implementations like IPv6 Router Advertisement Guard.
9+
10+
## Intro
11+
12+
Following my previous article on [VLAN 0](../VLAN0/README.md), I had two itches to scratch.
13+
14+
First, after reading [Proper isolation of a Linux bridge article from Vincent Bernat](https://vincent.bernat.ch/en/blog/2017-linux-bridge-isolation) some time ago and seeing:
15+
```
16+
$ cat /proc/net/ptype
17+
Type Device Function
18+
0800 ip_rcv
19+
0011 llc_rcv [llc]
20+
0004 llc_rcv [llc]
21+
0806 arp_rcv
22+
86dd ipv6_rcv
23+
```
24+
I wanted to understand what were those `llc` packets and what I could do with them,
25+
but it took me a long time and some luck to craft some useful packets with LLC/SNAP headers.
26+
27+
Second, I wanted to test bypassing layer 2 security on managed switches. As I was staying home for the end of 2020 (COVID ...),
28+
I decided to buy myself one of the cheapest Cisco switches with IPv6 first-hop security (CBS350-8T-E-2G), and was able to bypass its IPv6 RA Guard implementation using VLAN 0 and/or LLC/SNAP headers.
29+
30+
After reporting my findings to Cisco PSIRT, I reported these same bypasses to Juniper SIRT, but I didn't have time to continue reporting to all network vendors and coordinate between them. Thankfully I discovered that you can [report vulnerabilities that affect multiple vendors to CERT/CC](https://www.kb.cert.org/vuls/report/) and they will take care of reporting to and coordinating between all vendors.
31+
32+
The packet syntax in this article is the one used by [Scapy](https://scapy.readthedocs.io/).
33+
If you want to try the examples you might need to replace `Dot3()` with `Dot3(src=get_if_hwaddr(ifname))`.
34+
35+
## Ethernet frame types
36+
37+
If you want to know everything, the following pages will do a better job than me:
38+
- [Wikipedia - Ethernet frame](https://en.wikipedia.org/wiki/Ethernet_frame)
39+
- [Wikipedia - IEEE 802.2](https://en.wikipedia.org/wiki/IEEE_802.2)
40+
- [Wikipedia - IEEE 802.3](https://en.wikipedia.org/wiki/IEEE_802.3)
41+
- [RFC 894 - A Standard for the Transmission of IP Datagrams over Ethernet Networks](https://www.rfc-editor.org/rfc/rfc894)
42+
- [RFC 948 - Two Methods for the Transmission of IP Datagrams over IEEE 802.3 Networks](https://www.rfc-editor.org/rfc/rfc948)
43+
- [RFC 1042 - A Standard for the Transmission of IP Datagrams over IEEE 802 Networks](https://www.rfc-editor.org/rfc/rfc1042)
44+
- [RFC 1122 - Requirements for Internet Hosts -- Communication Layers](https://www.rfc-editor.org/rfc/rfc1122)
45+
- [RFC 2464 - Transmission of IPv6 Packets over Ethernet Networks](https://www.rfc-editor.org/rfc/rfc2464)
46+
- [RFC 4840 - Multiple Encapsulation Methods Considered Harmful](https://www.rfc-editor.org/rfc/rfc4840)
47+
- [RFC Draft - Extended Ethernet Frame Size Support](https://datatracker.ietf.org/doc/html/draft-ietf-isis-ext-eth)
48+
- [RFC 5342 - Ethernet Protocol Parameters](https://www.rfc-editor.org/rfc/rfc5342#section-3)
49+
- [Ask Wiresark - IEEE802a OUI Extended Ethertype](https://ask.wireshark.org/question/21547/ieee802a-oui-extended-ethertype/)
50+
- [Novell - Migrating Ethernet Frame Types from 802.3 Raw to IEEE 802.2](https://support.novell.com/techcenter/articles/ana19930905.html)
51+
52+
To make it short, an Ethernet frame always starts with a preamble, start frame delimiter, MAC destination, MAC source, then VLAN headers (if used), and then the content depends on which of the 4 frame types you are using:
53+
- Ethernet II: also known as DIX Ethernet, this is the most common, and starts with EtherType (2 bytes) that identifies the upper layer (0x0800 == IPv4, 0x0806 == ARP, 0x86DD == IPv6). See [RFC 894]((https://www.rfc-editor.org/rfc/rfc894)) and [RFC 2464](https://www.rfc-editor.org/rfc/rfc2464).
54+
- Novell raw IEEE 802.3: This was used to transport IPX until the mid-nineties, starts with 2 bytes length then 0xFFFF.
55+
- IEEE 802.2 LLC: in IEEE 802.3 standard a frame starts with 2 bytes length followed by 802.2 LLC (logical link control) header. 802.2 defines 3 operational modes, but we are only interested in unacknowledged connection-less mode. IPv4 and ARP can be encapsulated in 802.2 LLC headers as defined in [RFC 948](https://www.rfc-editor.org/rfc/rfc948), but as said in [RFC 1010](https://www.rfc-editor.org/rfc/rfc1010) from 1987 "The use of the IP LSAP is to be phased out as quickly as possible".
56+
- IEEE 802.2 LLC/SNAP: [RFC 1042](https://datatracker.ietf.org/doc/html/rfc1042) defines how to encapsulate IP using LLC/SNAP, and this is the default for all 802 networks except Ethernet, DSAP == SSAP == 170 == 0xAA (SNAP Headers), Control == 3 (unacknowledged connectionless mode / U-format PDUs), SNAP OUI 24 bits == 0x000000 or 0x0000F8, SNAP protocol id 16 bits == EtherType.
57+
58+
![The 4 Ethernet frame formats](Ethernet-frame-formats.svg)
59+
60+
LLC/SNAP with OUI 0x000000 can be called "SNAP RFC1042", and LLC/SNAP with OUI 0x0000f8 called "SNAP 802.1H".
61+
I found "SNAP 802.1H" by chance while looking at Linux mac80211 code.
62+
63+
Using Scapy notation, here are 3 ways to encapsulate an ICMP Echo request to 192.168.1.2:
64+
```
65+
Ether()/IP(dst='192.168.1.2')/ICMP()
66+
Dot3()/LLC(ctrl=3)/SNAP()/IP(dst='192.168.1.2')/ICMP()
67+
Dot3()/LLC(ctrl=3)/SNAP(OUI=0x0000f8)/IP(dst='192.168.1.2')/ICMP()
68+
```
69+
70+
Scapy is computing a lot of values for us, here the same packets expanded a bit:
71+
```
72+
Ether(type=0x0800)/IP(dst='192.168.1.2')/ICMP()
73+
Dot3()/LLC(dsap=0xaa,ssap=0xaa,ctrl=3)/SNAP(OUI=0x000000,code=0x0800)/IP(dst='192.168.1.2')/ICMP()
74+
Dot3()/LLC(dsap=0xaa,ssap=0xaa,ctrl=3)/SNAP(OUI=0x0000f8,code=0x0800)/IP(dst='192.168.1.2')/ICMP()
75+
```
76+
77+
[RFC1122 - Section 2.3.3](https://www.rfc-editor.org/rfc/rfc1122#section-2.3.3) states that:
78+
"Every Internet host connected to a 10Mbps Ethernet cable:
79+
- MUST be able to send and receive packets using RFC-894 encapsulation;
80+
- SHOULD be able to receive RFC-1042 packets, intermixed with RFC-894 packets; and
81+
- MAY be able to send packets using RFC-1042 encapsulation.
82+
An Internet host that implements sending both the RFC-894 and the RFC-1042 encapsulation MUST provide a configuration switch to select which is sent, and this switch MUST default to RFC-894."
83+
84+
After reading section 2.3.3, it's less surprising to know that Microsoft Windows has a boolean [ArpUseEtherSNAP](https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/setarpuseethersnap-method-in-class-win32-networkadapterconfiguration) that 'enables TCP/IP to transmit Ethernet packets using 802.3 SNAP encoding' and 'by default, the stack transmits packets in Digital, Intel, Xerox (DIX) Ethernet format (but) it always receives both formats', ie Windows accepts Ethernet II, "SNAP RFC1042" and "SNAP 802.1H" format.
85+
86+
LLC/SNAP Frames on 802.3 have a maximum size of 1500, so in 2001 [Extended Ethernet Frame Size Support RFC](https://datatracker.ietf.org/doc/html/draft-ietf-isis-ext-eth)
87+
was sent out. In short, instead of having the length, use `0x8870` Ethertype. This RFC was never accepted, but Wireshark decodes such frame and Scapy forge them:
88+
```
89+
Ether()/LLC(ctrl=3)/SNAP()/IP(dst='192.168.1.2')/ICMP()
90+
```
91+
(notice the `Ether()` instead of `Dot3()`)
92+
93+
To finish in 2003 the IEEE defined "OUI Extended Ethertype", Ethertype `0x88B7` followed by 3 octets OUI and 2 octets protocol. We could say it's LLC/SNAP without the LLC header.
94+
```
95+
Ether(type=0x88B7)/SNAP(OUI=0x000000)/IP(dst='192.168.1.2')/ICMP()
96+
Ether(type=0x88B7)/SNAP(OUI=0x0000f8)/IP(dst='192.168.1.2')/ICMP()
97+
```
98+
And of course OUI Extended Ethertype can be encapsulated inside LLC/SNAP
99+
```
100+
Dot3()/LLC(ctrl=3)/SNAP(OUI=0x000000,code=0x88b7)/SNAP(OUI=0x000000)/IP(dst='192.168.1.2')/ICMP()
101+
```
102+
103+
## Invalid 802.3 length
104+
105+
In an Ethernet frame, the 2 bytes after the source MAC can either be a length or an EtherType.
106+
Values greater than or equal to 1536 (0x0600) indicates that the field is an EtherType and the frame an Ethernet II frame.
107+
Values less than or equal to 1500 (0x05dc) indicates that the field is the length and the frame is Novell raw, 802.2 LLC or 802.2 LLC/SNAP.
108+
Values between 1500 and 1536, exclusive, are undefined.
109+
110+
The 802.3 length is redundant with the frame length, so a simple implementation can ignore if the value is coherent and just do:
111+
```
112+
if (pkt[12:13] >= 1536):
113+
handle_ethernet2()
114+
elif (pkt[14:15] == 0xaaaa):
115+
handle_llc_snap()
116+
elif (pkt[14:15] == 0xffff):
117+
handle_novell_raw()
118+
else:
119+
handle_llc()
120+
```
121+
122+
Microsoft Windows accept 802.3 headers with any length between 0 and 1535 (0x05ff) inclusive.
123+
On it's own this is not an issue, but some devices doing L2 security ignore packets with length between 1501 and 1535 or with length that is not coherent with the frame length (they are not valid 802.3 packets after all), meaning they let rogue packets thru that are then accepted by Windows.
124+
125+
Here 2 simple example:
126+
```
127+
# bypass hyperv
128+
Dot3(src=get_if_hwaddr(ifname),len=0)/LLC(ctrl=3)/SNAP()/ra
129+
# bypass cisco
130+
Dot3(src=get_if_hwaddr(ifname),len=0x05ff)/LLC(ctrl=3)/SNAP()/ra
131+
```
132+
133+
## Frame conversion between 802.3/Ethernet and 802.11
134+
135+
If you open Wireshark, look at some packets on your wired interface and some packets on your wireless, you will likely see that all packets are using Ethernet II headers. You might also have seen in the past TCP packets bigger than the interface MTU. For some (good) reasons, your OS and Wireshark are lying to you.
136+
If you want to see what real 802.11 traffic looks like, Wireshark wiki has some [802.11 sample captures](https://gitlab.com/wireshark/wireshark/-/wikis/SampleCaptures#wifi-wireless-lan-captures-80211).
137+
138+
Linux accepts IP packets with multiple VLAN 0 headers (see previous write-up) but not with LLC/SNAP encapsulation, what if we could combine both ?
139+
140+
When a frame is forwarded from 802.3/Ethernet to 802.11 (Wifi), the layer 2 part of the frame needs to be rewritten.
141+
Linux 802.11 wireless stack (mac80211) accepts frames with both Ethernet II and 802.2 LLC/SNAP encapsulation as input, so both
142+
```
143+
Ether()/IP(dst='192.168.1.2')/ICMP()
144+
Dot3()/LLC(ctrl=3)/SNAP()/IP(dst='192.168.1.2')/ICMP()
145+
```
146+
become something that looks like
147+
```
148+
Dot11()/LLC(ctrl=3)/SNAP()/IP(dst='192.168.1.2')/ICMP()
149+
```
150+
and if we mix VLAN 0 between LLC/SNAP and IP headers
151+
```
152+
Dot3()/LLC(ctrl=3)/SNAP()/Dot1Q(vlan=0)/IP(dst='192.168.1.2')/ICMP()
153+
```
154+
becomes something like
155+
```
156+
Dot11()/LLC(ctrl=3)/SNAP()/Dot1Q(vlan=0)/IP(dst='192.168.1.2')/ICMP()
157+
```
158+
159+
For 802.3, VLAN headers should be between the source MAC and the length, whereas for all other 802 networks, VLAN headers are after the LLC/SNAP header.
160+
By putting VLAN headers after the LLC/SNAP header for 802.3, we can bypass some L2 filtering, and still be accepted by most OS.
161+
162+
![Ethernet II / 802.3 / 802.11 / VLAN 0](Ethernet-8023-80211.svg)
163+
164+
If we send the following packet using Scapy on a wireless interface
165+
```
166+
Ether()/Dot1Q(vlan=0,type=len(LLC()/SNAP()/IP()/ICMP()))/LLC(ctrl=3)/SNAP()/IP(dst='192.168.1.2')/ICMP()
167+
```
168+
Linux will actually send
169+
```
170+
Dot11()/LLC(ctrl=3)/SNAP()/Dot1Q(vlan=0,type=len(LLC()/SNAP()/IP()/ICMP()))/LLC(ctrl=3)/SNAP()/IP(dst='192.168.1.2')/ICMP()
171+
```
172+
and when converted from 802.11 to Ethernet II we end up with
173+
```
174+
Ether()/Dot1Q(vlan=0,type=len(LLC()/SNAP()/IP()/ICMP()))/LLC(ctrl=3)/SNAP()/IP(dst='192.168.1.2')/ICMP()
175+
```
176+
177+
To finish, Linux will happily ignore the 802.3 length while converting from 802.3 to 802.11, so
178+
```
179+
Dot3(len=0)/LLC(ctrl=3)/SNAP()/IP(dst='192.168.1.2')/ICMP()
180+
```
181+
becomes a valid packet
182+
```
183+
Dot11()/LLC(ctrl=3)/SNAP()/IP(dst='192.168.1.2')/ICMP()
184+
```
185+
186+
## How to test your network
187+
188+
As there are a lot of possible combinations, I've written a small script that uses Scapy to send many of them: [l2-security-whack-a-mole.py](l2-security-whack-a-mole.py).
189+
Perform the attacks via both wired and wireless interfaces.
190+
An example to send Router Advertisements
191+
```
192+
sudo python3 l2-security-whack-a-mole.py -i eth0 --i-want-to-break-my-network ipv6_ra <IPv6 Link Local addr of eth0> ff02::1
193+
```
194+
195+
## Going deeper
196+
197+
While researching for those L2 attacks, I stumbled upon an awesome research from 2013 injecting specially crafted packets at L1 to confuse some switches and NICs:
198+
[Fully arbitrary 802.3 packet injection Maximizing the Ethernet attack surface](http://dev.inversepath.com/download/802.3/whitepaper.txt)
199+
200+
## Impacted software / hardware
201+
202+
- Microsoft Hyper-V / OpenStack / LXD: [See previous write up](../VLAN0/README.md#tested-software)
203+
- Microsoft Hyper-V: [CVE-2021-28444](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-28444) / [CVE-2022-21905](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-21905)
204+
- Cisco CBS350-8T-E-2G
205+
- See list in [CERT/CC vulnerability note VU#855201](https://kb.cert.org/vuls/id/855201)
206+
207+
## Timeline
208+
209+
* 2020-12-14: Initial report to Microsoft of LLC/SNAP attack (VLAN 0 attack was reported earlier)
210+
* 2020-12-30: Receive my CBS350-8T-E-2G
211+
* 2020-01-01: Initial report to Cisco PSIRT / PSIRT-0213940748
212+
* 2020-01-01: Initial report to Juniper SIRT / SIR-2021-001
213+
* 2020-01-04: Initial report to CERT/CC / VU#855201
214+
* 2021-02-05: Microsoft confirm the issue
215+
* 2021-04-13: Microsoft release fixes for [CVE-2021-28444](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-28444)
216+
* 2021-04-20: RedHat reports that the kernel behaves as expected, it's the user space responsibility to build correct filtering rules
217+
* 2021-08-13: Ask if any vendors plan on releasing advisories / fixes
218+
* 2021-08-23: All networks vendors really notified
219+
* 2021-09-10: Initial report to Microsoft of 802.3 invalid length attack for both HyperV and Windows
220+
* 2021-10-07: Share the 802.3 invalid length attack with everyone
221+
* 2022-01-11: Microsoft releases fix for 802.3 invalid length attack for HyperV [CVE-2022-21905](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-21905)
222+
* 2022-01-25: Resubmit 802.3 invalid length attack for Windows
223+
* 2022-01-11: [IEEE meeting](https://1.ieee802.org/maintenance-tg-electronic-meeting-agenda-january-11-2022-11-am-et/) discussing those bypass
224+
* 2022-03-25: Microsoft won't fix Windows accepting 802.3 packets with invalid length
225+
* 2022-09-27: Public disclosure of [CVE-2021-27853 / CVE-2021-27854 / CVE-2021-27861 / CVE-2021-27862](https://kb.cert.org/vuls/id/855201)
226+
227+
## Acknowledgments
228+
229+
- Thanks to Microsoft for their bounties
230+
- Thanks to RedHat engineers for their time discussing what was wrong in my initial report
231+
- Thanks to CERT/CC Team for the coordination work
232+
233+
<style type="text/css">@media print { #main_content img {filter: brightness(0) saturate(100%);}}</style>

0 commit comments

Comments
 (0)