● 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 / common / config.go
← Back to directory
458 lines · 15.0 KB
Raw
1package common
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "net/url"
8 "os"
9 "path/filepath"
10 "strings"
11 "time"
12
13 "gopkg.in/yaml.v3"
14)
15
16// ProfileConfig encapsulates full assessment configurations for the Contraband client engine.
17type ProfileConfig struct {
18 Target TargetConfig `yaml:"target" json:"target"`
19 DLP DLPConfig `yaml:"dlp" json:"dlp"`
20 Evasion EvasionConfig `yaml:"evasion" json:"evasion"`
21 Traffic TrafficConfig `yaml:"traffic" json:"traffic"`
22 Proxy ProxyConfig `yaml:"proxy" json:"proxy"`
23 Sweep SweepConfig `yaml:"sweep" json:"sweep"`
24 Output OutputConfig `yaml:"output" json:"output"`
25 Destroy bool `yaml:"destroy" json:"destroy"`
26}
27
28type TargetConfig struct {
29 Server string `yaml:"server" json:"server"`
30 Ports string `yaml:"ports" json:"ports"` // 'quick', 'standard', 'full', or comma/range
31 Protocols []string `yaml:"protocols" json:"protocols"` // 'all', 'web', 'dns', etc.
32}
33
34type DLPConfig struct {
35 Payloads []string `yaml:"payloads" json:"payloads"` // 'all', 'pci', 'pii', 'hipaa', 'secrets'
36 CarrierType string `yaml:"carrier_type" json:"carrier_type"` // 'csv', 'json', 'sql', 'email', 'syslog', 'noise'
37 Size string `yaml:"size" json:"size"` // '100KB', '50MB', '2GB'
38 ChunkSize string `yaml:"chunk_size" json:"chunk_size"` // '64KB', '1MB', '10MB'
39}
40
41type EvasionConfig struct {
42 Transforms []string `yaml:"transforms" json:"transforms"` // 'raw', 'base64', 'hex', 'gzip', 'zstd', 'aes_gcm'
43 Stego string `yaml:"stego" json:"stego"` // 'none', 'png', 'jpeg', 'pdf', 'zip'
44 TLSFingerprint string `yaml:"tls_fingerprint" json:"tls_fingerprint"` // 'chrome', 'edge', 'safari', 'firefox', 'curl', 'default'
45}
46
47type TrafficConfig struct {
48 Threads int `yaml:"threads" json:"threads"` // 1–64
49 Throughput string `yaml:"throughput" json:"throughput"` // '100KB/s', '5MB/s', 'unlimited'
50 Cadence string `yaml:"cadence" json:"cadence"` // 'burst', 'steady', 'jittered', 'office-hours', 'low-and-slow'
51 IntervalMin string `yaml:"interval_min" json:"interval_min"` // e.g. '500ms'
52 IntervalMax string `yaml:"interval_max" json:"interval_max"` // e.g. '4s'
53 Jitter float64 `yaml:"jitter,omitempty" json:"jitter,omitempty"` // 0.0–1.0 (ratio) or 0–100 (percentage)
54}
55
56type ProxyConfig struct {
57 Mode string `yaml:"mode" json:"mode"` // 'auto', 'direct', or URL
58 AuthType string `yaml:"auth_type" json:"auth_type"` // 'basic', 'ntlm', 'negotiate'
59}
60
61type SweepConfig struct {
62 Concurrency int `yaml:"concurrency" json:"concurrency"` // 1–1000
63 RateLimit int `yaml:"rate_limit" json:"rate_limit"` // packets/sec
64}
65
66type OutputConfig struct {
67 JSONPath string `yaml:"json_path" json:"json_path"`
68 HTMLPath string `yaml:"html_path" json:"html_path"`
69}
70
71// NewDefaultProfileConfig initializes a ProfileConfig with safe, production-grade defaults.
72func NewDefaultProfileConfig() *ProfileConfig {
73 return &ProfileConfig{
74 Target: TargetConfig{
75 Server: "127.0.0.1",
76 Ports: PortPresetQuick,
77 Protocols: []string{CategoryAll},
78 },
79 DLP: DLPConfig{
80 Payloads: []string{DLPTypeAll},
81 CarrierType: CarrierCSV,
82 Size: "1MB",
83 ChunkSize: "1MB",
84 },
85 Evasion: EvasionConfig{
86 Transforms: []string{TransformRaw},
87 Stego: StegoNone,
88 TLSFingerprint: TLSFingerprintChrome,
89 },
90 Traffic: TrafficConfig{
91 Threads: DefaultThreads,
92 Throughput: "unlimited",
93 Cadence: CadenceSteady,
94 IntervalMin: "500ms",
95 IntervalMax: "4s",
96 },
97 Proxy: ProxyConfig{
98 Mode: "auto",
99 AuthType: "basic",
100 },
101 Sweep: SweepConfig{
102 Concurrency: DefaultConcurrency,
103 RateLimit: DefaultRateLimit,
104 },
105 Output: OutputConfig{
106 JSONPath: "results.json",
107 HTMLPath: "report.html",
108 },
109 Destroy: false,
110 }
111}
112
113// NewStealthProfileConfig creates a low-and-slow, obfuscated profile.
114func NewStealthProfileConfig() *ProfileConfig {
115 cfg := NewDefaultProfileConfig()
116 cfg.Traffic.Threads = 1
117 cfg.Traffic.Throughput = "50KB/s"
118 cfg.Traffic.Cadence = CadenceLowAndSlow
119 cfg.Traffic.IntervalMin = "2s"
120 cfg.Traffic.IntervalMax = "10s"
121 cfg.Evasion.Transforms = []string{TransformGzip, TransformAESGCM}
122 cfg.Evasion.Stego = StegoPNG
123 cfg.Evasion.TLSFingerprint = TLSFingerprintChrome
124 cfg.Sweep.Concurrency = 10
125 cfg.Sweep.RateLimit = 20
126 return cfg
127}
128
129// NewAggressiveProfileConfig creates a high-throughput, maximum-concurrency profile.
130func NewAggressiveProfileConfig() *ProfileConfig {
131 cfg := NewDefaultProfileConfig()
132 cfg.Target.Ports = PortPresetStandard
133 cfg.Traffic.Threads = 32
134 cfg.Traffic.Throughput = "unlimited"
135 cfg.Traffic.Cadence = CadenceBurst
136 cfg.Sweep.Concurrency = 500
137 cfg.Sweep.RateLimit = 1000
138 return cfg
139}
140
141// NewComplianceProfileConfig creates a multi-DLP comprehensive audit profile.
142func NewComplianceProfileConfig() *ProfileConfig {
143 cfg := NewDefaultProfileConfig()
144 cfg.DLP.Payloads = []string{DLPTypePCI, DLPTypePII, DLPTypeHIPAA, DLPTypeSecrets}
145 cfg.DLP.Size = "10MB"
146 cfg.DLP.CarrierType = CarrierJSON
147 return cfg
148}
149
150// LoadProfileConfig reads, deserializes, and validates a YAML or JSON profile file.
151func LoadProfileConfig(path string) (*ProfileConfig, error) {
152 data, err := os.ReadFile(path)
153 if err != nil {
154 return nil, fmt.Errorf("failed to read profile config file: %w", err)
155 }
156
157 cfg := NewDefaultProfileConfig()
158 ext := strings.ToLower(filepath.Ext(path))
159
160 if ext == ".json" {
161 if err := json.Unmarshal(data, cfg); err != nil {
162 return nil, fmt.Errorf("failed to parse JSON profile config: %w", err)
163 }
164 } else {
165 if err := yaml.Unmarshal(data, cfg); err != nil {
166 return nil, fmt.Errorf("failed to parse YAML profile config: %w", err)
167 }
168 }
169
170 if err := cfg.Validate(); err != nil {
171 return nil, fmt.Errorf("profile config validation error: %w", err)
172 }
173
174 return cfg, nil
175}
176
177// Validate executes strict semantic validation against all profile configuration fields.
178func (c *ProfileConfig) Validate() error {
179 // Validate Ports
180 if err := validatePortsField(c.Target.Ports); err != nil {
181 return fmt.Errorf("invalid target ports: %w", err)
182 }
183
184 // Validate Protocols
185 if len(c.Target.Protocols) == 0 {
186 return errors.New("at least one protocol must be specified")
187 }
188 validProtocols := map[string]bool{
189 "all": true, "web": true, "dns": true, "file": true, "mail": true, "smb": true, "cloud": true, "covert": true,
190 ProtoHTTP1: true, ProtoHTTP2: true, ProtoHTTP3: true, ProtoWebSocket: true, ProtoGRPC: true, ProtoWebDAV: true,
191 ProtoDNSA: true, ProtoDNSTXT: true, ProtoDoH: true, ProtoDoT: true,
192 ProtoFTP: true, ProtoFTPS: true, ProtoSFTP: true, ProtoSMTP: true,
193 ProtoSMBFile: true, ProtoSMBHash: true, ProtoSMBQUIC: true,
194 ProtoAWSS3: true, ProtoGCPGCS: true, ProtoAzureBlob: true,
195 ProtoICMPEcho: true, ProtoNTPTime: true, ProtoSyslog: true, ProtoMQTT: true,
196 }
197 for _, p := range c.Target.Protocols {
198 if !validProtocols[strings.ToLower(p)] {
199 return fmt.Errorf("unsupported protocol or category: '%s'", p)
200 }
201 }
202
203 // Validate DLP Payloads
204 validDLP := map[string]bool{"all": true, DLPTypePCI: true, DLPTypePII: true, DLPTypeHIPAA: true, DLPTypeSecrets: true}
205 for _, d := range c.DLP.Payloads {
206 if !validDLP[strings.ToLower(d)] {
207 return fmt.Errorf("unsupported DLP payload type: '%s'", d)
208 }
209 }
210
211 // Validate Carrier Type
212 validCarriers := map[string]bool{
213 CarrierCSV: true, CarrierJSON: true, CarrierSQL: true, CarrierEmail: true, CarrierSyslog: true, CarrierNoise: true,
214 }
215 if !validCarriers[strings.ToLower(c.DLP.CarrierType)] {
216 return fmt.Errorf("unsupported carrier type: '%s'", c.DLP.CarrierType)
217 }
218
219 // Validate Transforms
220 validTransforms := map[string]bool{
221 TransformRaw: true, TransformBase64: true, TransformHex: true,
222 TransformGzip: true, TransformZlib: true, TransformZstd: true, TransformAESGCM: true,
223 }
224 for _, t := range c.Evasion.Transforms {
225 if !validTransforms[strings.ToLower(t)] {
226 return fmt.Errorf("unsupported evasion transform: '%s'", t)
227 }
228 }
229
230 // Validate Stego
231 validStego := map[string]bool{
232 StegoNone: true, StegoPNG: true, StegoJPEG: true, StegoPDF: true, StegoZIP: true,
233 }
234 if !validStego[strings.ToLower(c.Evasion.Stego)] {
235 return fmt.Errorf("unsupported stego embedding: '%s'", c.Evasion.Stego)
236 }
237
238 // Validate TLS Fingerprint
239 validTLS := map[string]bool{
240 TLSFingerprintChrome: true, TLSFingerprintEdge: true, TLSFingerprintSafari: true,
241 TLSFingerprintFirefox: true, TLSFingerprintCurl: true, TLSFingerprintDefault: true,
242 }
243 if !validTLS[strings.ToLower(c.Evasion.TLSFingerprint)] {
244 return fmt.Errorf("unsupported uTLS fingerprint preset: '%s'", c.Evasion.TLSFingerprint)
245 }
246
247 // Validate Threads
248 if c.Traffic.Threads < 1 || c.Traffic.Threads > MaxThreads {
249 return fmt.Errorf("threads must be between 1 and %d (got %d)", MaxThreads, c.Traffic.Threads)
250 }
251
252 // Validate Cadence
253 validCadence := map[string]bool{
254 CadenceBurst: true, CadenceSteady: true, CadenceJittered: true,
255 CadenceOfficeHours: true, CadenceLowAndSlow: true,
256 }
257 if !validCadence[strings.ToLower(c.Traffic.Cadence)] {
258 return fmt.Errorf("unsupported cadence profile: '%s'", c.Traffic.Cadence)
259 }
260
261 // Validate Cadence Intervals
262 var minDur, maxDur time.Duration
263 var hasMin, hasMax bool
264
265 if c.Traffic.IntervalMin != "" {
266 d, err := ParseDuration(c.Traffic.IntervalMin)
267 if err != nil {
268 return fmt.Errorf("invalid interval_min %q: %w", c.Traffic.IntervalMin, err)
269 }
270 if d <= 0 {
271 return fmt.Errorf("interval_min must be greater than zero: %s", c.Traffic.IntervalMin)
272 }
273 minDur = d
274 hasMin = true
275 }
276
277 if c.Traffic.IntervalMax != "" {
278 d, err := ParseDuration(c.Traffic.IntervalMax)
279 if err != nil {
280 return fmt.Errorf("invalid interval_max %q: %w", c.Traffic.IntervalMax, err)
281 }
282 if d <= 0 {
283 return fmt.Errorf("interval_max must be greater than zero: %s", c.Traffic.IntervalMax)
284 }
285 maxDur = d
286 hasMax = true
287 }
288
289 if hasMin && hasMax && minDur > maxDur {
290 return fmt.Errorf("interval_min (%v) cannot be greater than interval_max (%v)", minDur, maxDur)
291 }
292
293 // Validate Jitter percentage bounds (0.0 <= Jitter <= 1.0 or 0 <= Jitter <= 100)
294 if c.Traffic.Jitter < 0.0 || (c.Traffic.Jitter > 1.0 && c.Traffic.Jitter > 100.0) {
295 return fmt.Errorf("jitter percentage must be between 0.0 and 1.0 or 0 and 100 (got %v)", c.Traffic.Jitter)
296 }
297
298 // Validate Proxy Mode
299 if c.Proxy.Mode != "auto" && c.Proxy.Mode != "direct" {
300 if _, err := url.Parse(c.Proxy.Mode); err != nil {
301 return fmt.Errorf("invalid proxy URL '%s': %w", c.Proxy.Mode, err)
302 }
303 }
304
305 // Validate Sweep
306 if c.Sweep.Concurrency < 1 || c.Sweep.Concurrency > MaxConcurrency {
307 return fmt.Errorf("concurrency must be between 1 and %d (got %d)", MaxConcurrency, c.Sweep.Concurrency)
308 }
309 if c.Sweep.RateLimit < 1 {
310 return fmt.Errorf("rate_limit must be >= 1 (got %d)", c.Sweep.RateLimit)
311 }
312
313 return nil
314}
315
316func validatePortsField(ports string) error {
317 _, err := ParsePortsField(ports)
318 return err
319}
320
321// ClientFlags captures CLI flag values provided by the Cobra command hierarchy.
322type ClientFlags struct {
323 Server string
324 Ports string
325 Protocols []string
326 DLP []string
327 CarrierType string
328 Size string
329 ChunkSize string
330 Threads int
331 Throughput string
332 Cadence string
333 IntervalMin string
334 IntervalMax string
335 Jitter float64
336 Transforms []string
337 Stego string
338 TLSFingerprint string
339 Proxy string
340 Concurrency int
341 RateLimit int
342 OutputJSON string
343 OutputHTML string
344 Destroy bool
345}
346
347// MergeWithFlags overlays explicitly provided CLI flags onto ProfileConfig.
348// The isChanged function wraps Cobra's cmd.Flags().Changed(flagName).
349func (c *ProfileConfig) MergeWithFlags(flags ClientFlags, isChanged func(string) bool) {
350 if isChanged("server") && flags.Server != "" {
351 c.Target.Server = flags.Server
352 }
353 if isChanged("ports") && flags.Ports != "" {
354 c.Target.Ports = flags.Ports
355 }
356 if isChanged("protocols") && len(flags.Protocols) > 0 {
357 c.Target.Protocols = flags.Protocols
358 }
359 if isChanged("dlp") && len(flags.DLP) > 0 {
360 c.DLP.Payloads = flags.DLP
361 }
362 if isChanged("carrier-type") && flags.CarrierType != "" {
363 c.DLP.CarrierType = flags.CarrierType
364 }
365 if isChanged("size") && flags.Size != "" {
366 c.DLP.Size = flags.Size
367 }
368 if isChanged("chunk-size") && flags.ChunkSize != "" {
369 c.DLP.ChunkSize = flags.ChunkSize
370 }
371 if isChanged("threads") && flags.Threads > 0 {
372 c.Traffic.Threads = flags.Threads
373 }
374 if isChanged("throughput") && flags.Throughput != "" {
375 c.Traffic.Throughput = flags.Throughput
376 }
377 if isChanged("cadence") && flags.Cadence != "" {
378 c.Traffic.Cadence = flags.Cadence
379 }
380 if isChanged("interval-min") && flags.IntervalMin != "" {
381 c.Traffic.IntervalMin = flags.IntervalMin
382 }
383 if isChanged("interval-max") && flags.IntervalMax != "" {
384 c.Traffic.IntervalMax = flags.IntervalMax
385 }
386 if isChanged("jitter") {
387 c.Traffic.Jitter = flags.Jitter
388 }
389 if isChanged("transforms") && len(flags.Transforms) > 0 {
390 c.Evasion.Transforms = flags.Transforms
391 }
392 if isChanged("stego") && flags.Stego != "" {
393 c.Evasion.Stego = flags.Stego
394 }
395 if isChanged("tls-fingerprint") && flags.TLSFingerprint != "" {
396 c.Evasion.TLSFingerprint = flags.TLSFingerprint
397 }
398 if isChanged("proxy") && flags.Proxy != "" {
399 c.Proxy.Mode = flags.Proxy
400 }
401 if isChanged("concurrency") && flags.Concurrency > 0 {
402 c.Sweep.Concurrency = flags.Concurrency
403 }
404 if isChanged("rate-limit") && flags.RateLimit > 0 {
405 c.Sweep.RateLimit = flags.RateLimit
406 }
407 if isChanged("output-json") && flags.OutputJSON != "" {
408 c.Output.JSONPath = flags.OutputJSON
409 }
410 if isChanged("output-html") && flags.OutputHTML != "" {
411 c.Output.HTMLPath = flags.OutputHTML
412 }
413 if isChanged("destroy") {
414 c.Destroy = flags.Destroy
415 }
416}
417
418// ServerConfig models server listener configuration parameters for 'contraband server'.
419type ServerConfig struct {
420 Bind string `yaml:"bind" json:"bind"`
421 Domain string `yaml:"domain" json:"domain"`
422 Ports string `yaml:"ports" json:"ports"`
423 Protocols []string `yaml:"protocols" json:"protocols"`
424 SMBMode string `yaml:"smb_mode" json:"smb_mode"` // 'file-transfer' or 'hash-capture'
425 CertPath string `yaml:"cert" json:"cert"`
426 KeyPath string `yaml:"key" json:"key"`
427 EnableEBPF bool `yaml:"enable_ebpf" json:"enable_ebpf"`
428 Database string `yaml:"db" json:"db"`
429}
430
431// NewDefaultServerConfig returns standard defaults for the server engine.
432func NewDefaultServerConfig() *ServerConfig {
433 return &ServerConfig{
434 Bind: DefaultServerBind,
435 Domain: "contraband.example.com",
436 Ports: PortPresetQuick,
437 Protocols: []string{CategoryAll},
438 SMBMode: "hash-capture",
439 Database: "", // in-memory by default
440 }
441}
442
443// Validate checks ServerConfig parameters.
444func (s *ServerConfig) Validate() error {
445 if s.Bind == "" {
446 return errors.New("server bind address cannot be empty")
447 }
448 if err := validatePortsField(s.Ports); err != nil {
449 return fmt.Errorf("invalid server ports: %w", err)
450 }
451 if len(s.Protocols) == 0 {
452 return errors.New("at least one protocol must be specified")
453 }
454 if s.SMBMode != "file-transfer" && s.SMBMode != "hash-capture" {
455 return fmt.Errorf("invalid smb_mode %q (must be 'file-transfer' or 'hash-capture')", s.SMBMode)
456 }
457 return nil
458}