Discovering Network Vulnerabilities with Invariant
Invariant can be used to rapidly and comprehensively assess your network’s exposure to malicious traffic. This article will demonstrate how to assess an example network’s exposure to the critical linux CUPS vulnerability (CVE-2024-47176).
For background, CVE-2024-47176 is a widely discussed high-severity vulnerability involving malicious UDP traffic reaching vulnerable linux hosts on port 631. Blocking UDP traffic on port 631, especially from untrusted sources like the internet, is part of a recommended remediation strategy for this issue.
This article will show how to use Invariant to perform the following steps:
- Determine whether traffic originating from the internet can reach any network subnets, and if so, how.
- Given a known vulnerable machine, determine if there is any exposure to malicious traffic from less-trusted parts of our network.
Internet Exposure
Invariant works by evaluating whether user-defined rules are true or false. We will create a policy which asserts that no traffic originating from the public internet can reach any internal subnet on UDP port 631.
The way we will do that is by using egress-deny rules which apply to all traffic egressing from the public internet to our network. The source-address will be set to ANY, excluding RFC1918 space and our DMZ address space (defined elsewhere). One rule will then refer to traffic with destination-address in RFC1918 space and the other to traffic that can reach the DMZ. The reasoning behind this split is based on our own expectation and knowledge of our network: it seems more likely that the DMZ has some exposure than the private IP space, so the first rule is expected to pass, while the second one we are not totally sure of.
access-policy:
- name: deny-malicious-external-traffic
egress-network:
source-address: ANY
source-address-exclude: RFC1918 DMZ
rules:
- type: egress-deny
destination-address: RFC1918
destination-port: IPP
protocol: udp
- type: egress-deny
destination-address: DMZ
destination-port: IPP
protocol: udp
The egress-deny rules have the destination port and protocol set to IPP (port 631) over UDP. When we run Invariant, it will verify that there is no possibility of a successful IPP connection over UDP between the internet and the two destination subnets.
What makes Invariant especially strong is it will take into consideration not just ACLs but also NAT, BGP, and null routes. So it could be the case that the RFC1918 subnet is protected not by an ACL, but through the absence of viable routes. Moreover, Invariant will give answers very quickly - it can search all possible cases in seconds.
After placing this policy in invariant/policies/cups-check.yaml
we will run it in Invariant.
$ invariant run --condensed
snapshot: e929babf-34de-407b-92cc-9cbec4badb04
outcome: All rules passed
No violations were found in this case. We are now confident that no internet traffic can connect to any part of our network on port 631 over UDP.
Internal Exposure
Our network contains a print server. This is a known higher risk target as the vulnerable cupsd service is more likely to be enabled on print servers. We know it cannot be reached from the internet, but what about exposure to untrusted VLANs? Of special concern are the guest wireless network and the external HVAC vendor with access to our network.
The next step is to make a similar rule to look at traffic originating in the GUEST_WIRELESS and HVAC_VLAN subnets. We will set the destination-address to PRINT_SERVER, the IP of the print server.
Invariant is especially useful here because these source VLANs are present in multiple physical locations. We would have had to manually check each distribution switch and the paths each physical location would take. Instead, Invariant will automatically find and consider all possible start locations and all possible paths.
access-policy:
- name: deny-malicious-ipp-traffic
egress-network:
source-address: GUEST_WIRELESS HVAC_VLAN
rules:
- type: egress-deny
comment: Check that CVE-2024-47176 is denied to our print server.
destination-address: PRINT_SERVER
destination-port: IPP
protocol: udp
Looking good, let’s run it in Invariant.
$ invariant run --condensed
snapshot: 1d8df64b-b220-4808-901b-f7de0f21f9b4
outcome: Rule violations found
Invariant found a violation. When an egress-deny rule fails, Invariant will produce one or more virtual traceroutes showing how the target network is exposed. The next step is to access those violations.
First, use the “invariant show” command to get a listing of all files generated during the last “run” command. We want to check the “Access Policy” section.
$ invariant show
… (skipped for this article)
╭──────────────────────────────────────┬────────╮
│ Access Policy │ Rows │
├──────────────────────────────────────┼────────┤
│ critical_flows_ok │ 0 │
│ critical_flows_violations │ 0 │
│ critical_flows_violations_unenforced │ 0 │
│ critical_flows_skipped │ 0 │
│ critical_flows_details │ 0 │
│ critical_flows_logs │ 0 │
│ policy_ok │ 2 │
│ policy_violations │ 1 │
│ policy_violations_unenforced │ 0 │
│ policy_skipped │ 0 │
│ policy_details │ 7 │
│ policy_logs │ 3 │
╰──────────────────────────────────────┴────────╯
Our egress-deny rule counts as a “policy” rule. The virtual traceroutes will be found in the policy_details file. This policy_details file is fairly large but we can jump right to the ‘traces’ section. We can see four different virtual trace routes in that section showing how offending packets might traverse the network and reach the print server.
The first traceroute shows how a packet could originate on dist-1 and pass through core-2, asa, and dc-1 to reach the print server. The packet would traverse two ACLs, vlan10 and internal-to-external, which we might want to consider as potential policy enforcement points.
$ invariant show policy_details --json
"traces": [
{
"disposition": "DELIVERED_TO_SUBNET",
"hops": [
{
"node": "dist-1",
"steps": [
{
"action": "RECEIVED",
"detail": {...}
},
{
"action": "PERMITTED",
"detail": {
"filter": "vlan10",
"filterType": "INGRESS_FILTER",
"flow": {
"dscp": 0,
"dstIp": "172.16.50.20",
"dstPort": 631,
"ecn": 0,
"fragmentOffset": 0,
"icmpCode": null,
"icmpVar": null,
"ingressInterface": "Vlan10",
"ingressNode": "dist-1",
"ingressVrf": null,
"ipProtocol": "UDP",
"packetLength": 512,
"srcIp": "192.168.10.252",
"srcPort": 49152,
"tcpFlagsAck": 0,
"tcpFlagsCwr": 0,
"tcpFlagsEce": 0,
"tcpFlagsFin": 0,
"tcpFlagsPsh": 0,
"tcpFlagsRst": 0,
"tcpFlagsSyn": 0,
"tcpFlagsUrg": 0
},
}
},
{
"action": "FORWARDED",
"detail": {...}
},
{
"action": "TRANSMITTED",
"detail": {...}
}
]
},
{
"node": "core-2",
"steps": [
{
"action": "RECEIVED",
"detail": {...}
},
{
"action": "FORWARDED",
"detail": {...}
},
{
"action": "TRANSMITTED",
"detail": {...}
}
]
},
{
"node": "asa",
"steps": [
{
"action": "RECEIVED",
"detail": {...}
},
{
"action": "PERMITTED",
"detail": {
"arpIp": null,
"filter": "internal-to-external",
"filterType": "POST_TRANSFORMATION_INGRESS_FILTER",
"flow": {
"dscp": 0,
"dstIp": "172.16.50.20",
"dstPort": 631,
"ecn": 0,
"fragmentOffset": 0,
"icmpCode": null,
"icmpVar": null,
"ingressInterface": "Vlan10",
"ingressNode": "dist-1",
"ingressVrf": null,
"ipProtocol": "UDP",
"packetLength": 512,
"srcIp": "192.168.10.252",
"srcPort": 49152,
"tcpFlagsAck": 0,
"tcpFlagsCwr": 0,
"tcpFlagsEce": 0,
"tcpFlagsFin": 0,
"tcpFlagsPsh": 0,
"tcpFlagsRst": 0,
"tcpFlagsSyn": 0,
"tcpFlagsUrg": 0
},
"inputInterface": "INSIDE0",
}
},
{
"action": "FORWARDED",
"detail": {...}
},
{
"action": "PERMITTED",
"detail": {
"arpIp": null,
"filter": "~COMBINED_OUTGOING_ACL~SERVER0~",
"filterType": "PRE_TRANSFORMATION_EGRESS_FILTER",
"flow": {
"dscp": 0,
"dstIp": "172.16.50.20",
"dstPort": 631,
"ecn": 0,
"fragmentOffset": 0,
"icmpCode": null,
"icmpVar": null,
"ingressInterface": "Vlan10",
"ingressNode": "dist-1",
"ingressVrf": null,
"ipProtocol": "UDP",
"packetLength": 512,
"srcIp": "192.168.10.252",
"srcPort": 49152,
"tcpFlagsAck": 0,
"tcpFlagsCwr": 0,
"tcpFlagsEce": 0,
"tcpFlagsFin": 0,
"tcpFlagsPsh": 0,
"tcpFlagsRst": 0,
"tcpFlagsSyn": 0,
"tcpFlagsUrg": 0
},
"inputInterface": "INSIDE0",
}
},
{
"action": "SETUP_SESSION",
"detail": {...}
},
{
"action": "TRANSMITTED",
"detail": {...}
}
]
},
{
"node": "dc-1",
"steps": [
{
"action": "RECEIVED",
"detail": {...}
},
{
"action": "FORWARDED",
"detail": {...}
},
{
"action": "TRANSMITTED",
"detail": {...}
},
{
"action": "DELIVERED_TO_SUBNET",
"detail": {...}
}
]
}
]
},
],
Conclusion
In today's rapidly evolving cybersecurity landscape, new vulnerabilities are discovered daily, leaving organizations constantly racing to protect their networks. One of the latest threats, CVE-2024-47176, has raised significant concern due to its potential impact on critical network systems. Understanding how this vulnerability affects their infrastructure is paramount for network engineers and security teams, but this is often easier said than done.
CVE-2024-47176 is part of a series of vulnerabilities released in September related to CUPS (Common UNIX Printing System), an application included in Linux and Mac operating systems. Exploiting these vulnerabilities could allow an attacker to execute arbitrary code on your server. The potential consequences of such an attack are severe—an attacker could gain a critical foothold into your network, potentially leading to ransomware and other severe security breaches. Invariant can help you identify if you are vulnerable, address the vulnerability, and ensure it never occurs again.
Validating whether a company's network is affected by this vulnerability would be complex. It would require checking each network configuration to determine the routability of a malicious packet and the ACLs permitting traffic. However, with Invariant, you can assess the situation definitively, providing a reliable and efficient solution to this complex problem. You can do this by focusing on where the malicious traffic originates and validating traffic towards vulnerable subnets.
With a single rule and a few minutes, we exhaustively tested our entire network and found that we were vulnerable, saving us time on troubleshooting and evaluating every configuration. The information provided gives us a couple of ACLs that would be good candidates to filter the traffic on. Leaving this rule in place would ensure that we never accidentally reintroduce this vulnerability to our network again.
Vulnerabilities arise daily, and manually checking your network for each is time-consuming. Furthermore, preventing any regression to an unsafe state can be challenging. With Invariant, both of these tasks become simple. You can easily create rules to validate your security posture, reducing the time spent on tedious tasks.
Try It Yourself
You can create a free, limited Invariant account which you can use to try out this scenario or invent your own.