● Notice //GitHub (@shehzade) is temporarily suspended pending an automated false-positive review. All tools are hosted on this Code Mirror and available on request (contact); GitHub links will return once reinstated.
PUBLIC MIRRORGo v1.0

Multi-protocol network egress assessment and DLP verification framework featuring automated L4 sweeps, corporate proxy traversal, and covert protocol smuggling across 20+ channels.

$ git clone https://shehzade.io/repos/contraband.git Copy
Source ZIP (1.93 MB)
Code 174 Commits 1
contraband / pkg / report / remediation.go
← Back to directory
365 lines · 16.1 KB
Raw
1package report
2
3import (
4 "fmt"
5 "sort"
6 "strings"
7 "time"
8
9 "github.com/shehzade/contraband/pkg/common"
10)
11
12// RemediationRuleSet contains vendor-synthesized firewall blocking rules.
13type RemediationRuleSet struct {
14 GeneratedAt time.Time `json:"generated_at"`
15 OpenTCPPorts []int `json:"open_tcp_ports"`
16 OpenUDPPorts []int `json:"open_udp_ports"`
17 OpenProtocols []string `json:"open_protocols"`
18 TargetHost string `json:"target_host,omitempty"`
19 IPTablesRules string `json:"iptables"`
20 NFTablesRules string `json:"nftables"`
21 PANOSRules string `json:"panos"`
22 FortiGateRules string `json:"fortigate"`
23 CiscoASARules string `json:"cisco_asa"`
24}
25
26// Ergonomic accessors for templates and scripts
27func (r RemediationRuleSet) IPTables() string { return r.IPTablesRules }
28func (r RemediationRuleSet) NFTables() string { return r.NFTablesRules }
29func (r RemediationRuleSet) PANOS() string { return r.PANOSRules }
30func (r RemediationRuleSet) FortiGate() string { return r.FortiGateRules }
31func (r RemediationRuleSet) CiscoASA() string { return r.CiscoASARules }
32
33// FormatAll produces a consolidated multi-vendor remediation configuration document.
34func (r *RemediationRuleSet) FormatAll() string {
35 var b strings.Builder
36 b.WriteString("### Linux iptables Rules\n\n")
37 b.WriteString(r.IPTablesRules)
38 b.WriteString("\n\n### Linux nftables Rules\n\n")
39 b.WriteString(r.NFTablesRules)
40 b.WriteString("\n\n### Palo Alto Networks PAN-OS Rules\n\n")
41 b.WriteString(r.PANOSRules)
42 b.WriteString("\n\n### Fortinet FortiGate CLI Rules\n\n")
43 b.WriteString(r.FortiGateRules)
44 b.WriteString("\n\n### Cisco ASA Rules\n\n")
45 b.WriteString(r.CiscoASARules)
46 return b.String()
47}
48
49// GenerateRemediation produces vendor-accurate firewall configurations for all successful probes.
50// Only successful channels (VerdictSuccess) generate blocking rules.
51func GenerateRemediation(probes []common.ProbeResult, targetHost string) *RemediationRuleSet {
52 ruleSet := &RemediationRuleSet{
53 GeneratedAt: time.Now().UTC(),
54 TargetHost: targetHost,
55 }
56
57 tcpPortMap := make(map[int]bool)
58 udpPortMap := make(map[int]bool)
59 protoMap := make(map[string]bool)
60
61 // Filter strictly for Verdict == VerdictSuccess
62 for _, p := range probes {
63 if p.Verdict != common.VerdictSuccess {
64 continue
65 }
66
67 if p.TargetPort > 0 {
68 switch strings.ToLower(p.Protocol) {
69 case "udp", "ntp_timestamp", "syslog_udp", "icmp_echo":
70 udpPortMap[p.TargetPort] = true
71 default:
72 tcpPortMap[p.TargetPort] = true
73 }
74 }
75
76 if p.Protocol != "" && p.Protocol != "tcp" && p.Protocol != "udp" {
77 protoMap[p.Protocol] = true
78 }
79 }
80
81 // Sort ports for deterministic output
82 for port := range tcpPortMap {
83 ruleSet.OpenTCPPorts = append(ruleSet.OpenTCPPorts, port)
84 }
85 sort.Ints(ruleSet.OpenTCPPorts)
86
87 for port := range udpPortMap {
88 ruleSet.OpenUDPPorts = append(ruleSet.OpenUDPPorts, port)
89 }
90 sort.Ints(ruleSet.OpenUDPPorts)
91
92 for proto := range protoMap {
93 ruleSet.OpenProtocols = append(ruleSet.OpenProtocols, proto)
94 }
95 sort.Strings(ruleSet.OpenProtocols)
96
97 // Generate rule sets
98 ruleSet.IPTablesRules = generateIPTables(ruleSet.OpenTCPPorts, ruleSet.OpenUDPPorts, targetHost)
99 ruleSet.NFTablesRules = generateNFTables(ruleSet.OpenTCPPorts, ruleSet.OpenUDPPorts, targetHost)
100 ruleSet.PANOSRules = generatePANOS(ruleSet.OpenTCPPorts, ruleSet.OpenUDPPorts, ruleSet.OpenProtocols, targetHost)
101 ruleSet.FortiGateRules = generateFortiGate(ruleSet.OpenTCPPorts, ruleSet.OpenUDPPorts, targetHost)
102 ruleSet.CiscoASARules = generateCiscoASA(ruleSet.OpenTCPPorts, ruleSet.OpenUDPPorts, targetHost)
103
104 return ruleSet
105}
106
107func generateIPTables(tcpPorts, udpPorts []int, targetHost string) string {
108 if len(tcpPorts) == 0 && len(udpPorts) == 0 {
109 return "# Contraband Remediation: No open egress ports detected (perimeter is hardened).\n"
110 }
111
112 var b strings.Builder
113 b.WriteString("#!/bin/sh\n")
114 b.WriteString("# =============================================================================\n")
115 b.WriteString("# Contraband Automated Remediation: Linux iptables\n")
116 b.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().UTC().Format(time.RFC3339)))
117 b.WriteString("# =============================================================================\n\n")
118
119 b.WriteString("# 1. Allow stateful established and related outbound connections\n")
120 b.WriteString("iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT\n\n")
121
122 b.WriteString("# 2. Allow local loopback traffic\n")
123 b.WriteString("iptables -A OUTPUT -o lo -j ACCEPT\n\n")
124
125 b.WriteString("# 3. Block discovered unauthorized egress ports\n")
126 for _, port := range tcpPorts {
127 if targetHost != "" {
128 b.WriteString(fmt.Sprintf("iptables -A OUTPUT -d %s -p tcp --dport %d -j REJECT --reject-with tcp-reset -m comment --comment \"CTRB: Block %s:%d/tcp\"\n", targetHost, port, targetHost, port))
129 }
130 b.WriteString(fmt.Sprintf("iptables -A OUTPUT -p tcp --dport %d -j REJECT --reject-with tcp-reset -m comment --comment \"CTRB: Block egress port %d/tcp\"\n", port, port))
131 }
132 for _, port := range udpPorts {
133 if targetHost != "" {
134 b.WriteString(fmt.Sprintf("iptables -A OUTPUT -d %s -p udp --dport %d -j DROP -m comment --comment \"CTRB: Block %s:%d/udp\"\n", targetHost, port, targetHost, port))
135 }
136 b.WriteString(fmt.Sprintf("iptables -A OUTPUT -p udp --dport %d -j DROP -m comment --comment \"CTRB: Block egress port %d/udp\"\n", port, port))
137 }
138
139 b.WriteString("\n# 4. Whitelist legitimate outbound services (DNS and HTTPS only)\n")
140 b.WriteString("iptables -A OUTPUT -p udp --dport 53 -j ACCEPT -m comment --comment \"Allow outbound DNS\"\n")
141 b.WriteString("iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT -m comment --comment \"Allow outbound DNS TCP\"\n")
142 b.WriteString("iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT -m comment --comment \"Allow outbound HTTPS\"\n\n")
143
144 b.WriteString("# 5. Default Deny Policy on OUTPUT chain\n")
145 b.WriteString("iptables -P OUTPUT DROP\n")
146
147 return b.String()
148}
149
150func generateNFTables(tcpPorts, udpPorts []int, targetHost string) string {
151 if len(tcpPorts) == 0 && len(udpPorts) == 0 {
152 return "# Contraband Remediation: No open egress ports detected (perimeter is hardened).\n"
153 }
154
155 var b strings.Builder
156 b.WriteString("# =============================================================================\n")
157 b.WriteString("# Contraband Automated Remediation: Linux nftables\n")
158 b.WriteString("# =============================================================================\n\n")
159
160 b.WriteString("table inet filter {\n")
161 b.WriteString(" chain output {\n")
162 b.WriteString(" type filter hook output priority 0; policy drop;\n\n")
163
164 b.WriteString(" # Accept established and related connections\n")
165 b.WriteString(" ct state established,related accept\n\n")
166
167 b.WriteString(" # Accept loopback\n")
168 b.WriteString(" oifname \"lo\" accept\n\n")
169
170 if len(tcpPorts) > 0 {
171 var portStrs []string
172 for _, p := range tcpPorts {
173 portStrs = append(portStrs, fmt.Sprintf("%d", p))
174 }
175 b.WriteString(fmt.Sprintf(" # Block open egress TCP ports\n tcp dport { %s } reject with tcp reset comment \"CTRB: Block open egress TCP ports\"\n\n", strings.Join(portStrs, ", ")))
176 }
177
178 if len(udpPorts) > 0 {
179 var portStrs []string
180 for _, p := range udpPorts {
181 portStrs = append(portStrs, fmt.Sprintf("%d", p))
182 }
183 b.WriteString(fmt.Sprintf(" # Block open egress UDP ports\n udp dport { %s } drop comment \"CTRB: Block open egress UDP ports\"\n\n", strings.Join(portStrs, ", ")))
184 }
185
186 b.WriteString(" # Allow authorized administrative outbound ports\n")
187 b.WriteString(" udp dport 53 accept comment \"Allow outbound DNS\"\n")
188 b.WriteString(" tcp dport 443 accept comment \"Allow outbound HTTPS\"\n")
189 b.WriteString(" }\n")
190 b.WriteString("}\n")
191
192 return b.String()
193}
194
195func mapPANOSAppID(proto string) string {
196 switch proto {
197 case "dns_a", "dns_txt":
198 return "dns-tunneling"
199 case "dns_over_https":
200 return "dns-over-https"
201 case "dns_over_tls":
202 return "dns-over-tls"
203 case "smb_file", "smb_hash_capture":
204 return "ms-smb"
205 case "smb_quic":
206 return "ms-smb-over-quic"
207 case "sftp":
208 return "sftp"
209 case "ftp", "ftps":
210 return "ftp"
211 case "smtp":
212 return "smtp"
213 case "icmp_echo":
214 return "icmp"
215 case "ntp_timestamp":
216 return "ntp"
217 case "syslog":
218 return "syslog"
219 case "mqtt":
220 return "mqtt"
221 case "websocket_binary":
222 return "websocket"
223 case "grpc_protobuf":
224 return "grpc"
225 case "webdav":
226 return "webdav"
227 case "aws_s3":
228 return "amazon-s3-upload"
229 case "gcp_gcs":
230 return "google-cloud-storage"
231 case "azure_blob":
232 return "azure-blob-storage"
233 default:
234 return proto
235 }
236}
237
238func generatePANOS(tcpPorts, udpPorts []int, protocols []string, targetHost string) string {
239 if len(tcpPorts) == 0 && len(udpPorts) == 0 && len(protocols) == 0 {
240 return "# Contraband Remediation: No open egress vectors detected.\n"
241 }
242
243 var b strings.Builder
244 b.WriteString("# =============================================================================\n")
245 b.WriteString("# Contraband Automated Remediation: Palo Alto Networks PAN-OS CLI\n")
246 b.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().UTC().Format(time.RFC3339)))
247 b.WriteString("# Paste into PAN-OS configuration mode: 'configure'\n")
248 b.WriteString("# =============================================================================\n\n")
249
250 // Service objects
251 for _, port := range tcpPorts {
252 b.WriteString(fmt.Sprintf("set service service-ctrb-tcp-%d protocol tcp port %d\n", port, port))
253 }
254 for _, port := range udpPorts {
255 b.WriteString(fmt.Sprintf("set service service-ctrb-udp-%d protocol udp port %d\n", port, port))
256 }
257 b.WriteString("\n")
258
259 // Security rules for ports
260 for _, port := range tcpPorts {
261 ruleName := fmt.Sprintf("CTRB-BLOCK-TCP-%d", port)
262 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" to untrust\n", ruleName))
263 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" from trust\n", ruleName))
264 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" source any\n", ruleName))
265 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" destination any\n", ruleName))
266 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" service service-ctrb-tcp-%d\n", ruleName, port))
267 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" application any\n", ruleName))
268 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" action deny\n", ruleName))
269 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" log-end yes\n", ruleName))
270 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" description \"CTRB: Block egress port %d/tcp\"\n", ruleName, port))
271 b.WriteString(fmt.Sprintf("move rulebase security rules \"%s\" top\n\n", ruleName))
272 }
273
274 // App-ID strict rules
275 for _, proto := range protocols {
276 appID := mapPANOSAppID(proto)
277 ruleName := fmt.Sprintf("CTRB-DENY-APP-%s", proto)
278 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" to untrust\n", ruleName))
279 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" from trust\n", ruleName))
280 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" source any\n", ruleName))
281 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" destination any\n", ruleName))
282 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" application %s\n", ruleName, appID))
283 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" action deny\n", ruleName))
284 b.WriteString(fmt.Sprintf("set rulebase security rules \"%s\" log-end yes\n", ruleName))
285 b.WriteString(fmt.Sprintf("move rulebase security rules \"%s\" top\n\n", ruleName))
286 }
287
288 b.WriteString("commit description \"Contraband egress remediation rules applied\"\n")
289 return b.String()
290}
291
292func generateFortiGate(tcpPorts, udpPorts []int, targetHost string) string {
293 if len(tcpPorts) == 0 && len(udpPorts) == 0 {
294 return "# Contraband Remediation: No open egress ports detected.\n"
295 }
296
297 var b strings.Builder
298 b.WriteString("# =============================================================================\n")
299 b.WriteString("# Contraband Automated Remediation: Fortinet FortiGate CLI\n")
300 b.WriteString(fmt.Sprintf("# Generated: %s\n", time.Now().UTC().Format(time.RFC3339)))
301 b.WriteString("# =============================================================================\n\n")
302
303 b.WriteString("config firewall service custom\n")
304 for _, port := range tcpPorts {
305 b.WriteString(fmt.Sprintf(" edit \"CTRB_SVC_TCP_%d\"\n set tcp-portrange %d\n set comment \"Contraband egress port %d/tcp\"\n next\n", port, port, port))
306 }
307 for _, port := range udpPorts {
308 b.WriteString(fmt.Sprintf(" edit \"CTRB_SVC_UDP_%d\"\n set udp-portrange %d\n set comment \"Contraband egress port %d/udp\"\n next\n", port, port, port))
309 }
310 b.WriteString("end\n\n")
311
312 b.WriteString("config firewall policy\n")
313 for _, port := range tcpPorts {
314 b.WriteString(fmt.Sprintf(" edit 0\n set name \"CTRB_BLOCK_TCP_%d\"\n set srcintf \"lan\"\n set dstintf \"wan\"\n set srcaddr \"all\"\n set dstaddr \"all\"\n set action deny\n set schedule \"always\"\n set service \"CTRB_SVC_TCP_%d\"\n set logtraffic all\n set comments \"CTRB: Block unauthorized port %d/tcp\"\n next\n", port, port, port))
315 }
316 for _, port := range udpPorts {
317 b.WriteString(fmt.Sprintf(" edit 0\n set name \"CTRB_BLOCK_UDP_%d\"\n set srcintf \"lan\"\n set dstintf \"wan\"\n set srcaddr \"all\"\n set dstaddr \"all\"\n set action deny\n set schedule \"always\"\n set service \"CTRB_SVC_UDP_%d\"\n set logtraffic all\n set comments \"CTRB: Block unauthorized port %d/udp\"\n next\n", port, port, port))
318 }
319
320 // Enable Strict Outbound Deep Packet & DLP Inspection
321 b.WriteString(" edit 0\n set name \"CTRB_DEEP_INSPECT\"\n set srcintf \"lan\"\n set dstintf \"wan\"\n set srcaddr \"all\"\n set dstaddr \"all\"\n set action accept\n set schedule \"always\"\n set service \"HTTPS\" \"HTTP\" \"DNS\"\n set utm-status enable\n set ssl-ssh-profile \"deep-inspection\"\n set dlp-profile \"default\"\n set av-profile \"default\"\n set logtraffic all\n set comments \"CTRB: Enforce deep SSL and DLP inspection\"\n next\n")
322 b.WriteString("end\n")
323
324 return b.String()
325}
326
327func generateCiscoASA(tcpPorts, udpPorts []int, targetHost string) string {
328 if len(tcpPorts) == 0 && len(udpPorts) == 0 {
329 return "! Contraband Remediation: No open egress ports detected.\n"
330 }
331
332 var b strings.Builder
333 b.WriteString("! =============================================================================\n")
334 b.WriteString("! Contraband Automated Remediation: Cisco ASA / Firepower CLI\n")
335 b.WriteString(fmt.Sprintf("! Generated: %s\n", time.Now().UTC().Format(time.RFC3339)))
336 b.WriteString("! =============================================================================\n\n")
337
338 for _, port := range tcpPorts {
339 if targetHost != "" {
340 b.WriteString(fmt.Sprintf("access-list OUTBOUND_FILTER extended deny tcp any host %s eq %d\n", targetHost, port))
341 }
342 b.WriteString(fmt.Sprintf("access-list OUTBOUND_FILTER extended deny tcp any any eq %d\n", port))
343 }
344 for _, port := range udpPorts {
345 if targetHost != "" {
346 b.WriteString(fmt.Sprintf("access-list OUTBOUND_FILTER extended deny udp any host %s eq %d\n", targetHost, port))
347 }
348 b.WriteString(fmt.Sprintf("access-list OUTBOUND_FILTER extended deny udp any any eq %d\n", port))
349 }
350
351 b.WriteString("\n! Whitelist authorized inspection outbound traffic\n")
352 b.WriteString("access-list OUTBOUND_FILTER extended permit udp any any eq 53\n")
353 b.WriteString("access-list OUTBOUND_FILTER extended permit tcp any any eq 53\n")
354 b.WriteString("access-list OUTBOUND_FILTER extended permit tcp any any eq 443\n")
355 b.WriteString("access-list OUTBOUND_FILTER extended deny ip any any\n\n")
356
357 b.WriteString("! Apply access-group to inside interface\n")
358 b.WriteString("access-group OUTBOUND_FILTER in interface inside\n\n")
359
360 b.WriteString("! Modular Policy Framework (MPF) Protocol Inspection\n")
361 b.WriteString("class-map CTRB_INSPECT_TRAFFIC\n match default-inspection-traffic\n")
362 b.WriteString("policy-map global_policy\n class CTRB_INSPECT_TRAFFIC\n inspect dns\n inspect ftp\n inspect http\n")
363
364 return b.String()
365}