From edead7d9219441707c9f458ea65401d36f5d91c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=A4umer?= Date: Wed, 20 Dec 2023 16:07:07 +0100 Subject: [PATCH 1/4] feat: Add option to output report as json --- main.go | 68 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/main.go b/main.go index 0b21757..9bc1c8a 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "bufio" "encoding/binary" + "encoding/json" "flag" "fmt" "io" @@ -62,6 +63,16 @@ func (report *TerrapinVulnerabilityReport) IsVulnerable() bool { return (report.SupportsChaCha20 || report.SupportsCbcEtm) && !report.SupportsStrictKex } +func (report *TerrapinVulnerabilityReport) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + TerrapinVulnerabilityReport + Vulnerable bool + }{ + *report, + report.IsVulnerable(), + }) +} + // Reads a single incoming, unencrypted binary packet from the provided connection. // Does not support reading encrypted binary packets. func readSinglePacket(connrw *bufio.ReadWriter) (*BinaryPacket, error) { @@ -200,7 +211,7 @@ func performVulnerabilityScan(address string, scanMode ScanMode) (*TerrapinVulne return nil, err } defer listener.Close() - fmt.Println("Listening for incoming client connection on", address) + fmt.Fprintln(os.Stderr, "Listening for incoming client connection on", address) if conn, err = listener.Accept(); err != nil { return nil, err @@ -261,25 +272,34 @@ func formatAddress(address string, mode ScanMode) string { } // Prints the report to stdout -func printReport(report *TerrapinVulnerabilityReport) { - fmt.Println("================================================================================") - fmt.Println("==================================== Report ====================================") - fmt.Println("================================================================================") - fmt.Println() - fmt.Printf("Remote Banner: %s\n", report.Banner) - fmt.Println() - fmt.Printf("ChaCha20-Poly1305 support: %t\n", report.SupportsChaCha20) - fmt.Printf("CBC-EtM support: %t\n", report.SupportsCbcEtm) - fmt.Println() - fmt.Printf("Strict key exchange support: %t\n", report.SupportsStrictKex) - fmt.Println() - if report.IsVulnerable() { - fmt.Println("==> The scanned peer is VULNERABLE to Terrapin.") +func printReport(report *TerrapinVulnerabilityReport, outputJson bool) error { + if !outputJson { + fmt.Println("================================================================================") + fmt.Println("==================================== Report ====================================") + fmt.Println("================================================================================") + fmt.Println() + fmt.Printf("Remote Banner: %s\n", report.Banner) + fmt.Println() + fmt.Printf("ChaCha20-Poly1305 support: %t\n", report.SupportsChaCha20) + fmt.Printf("CBC-EtM support: %t\n", report.SupportsCbcEtm) + fmt.Println() + fmt.Printf("Strict key exchange support: %t\n", report.SupportsStrictKex) + fmt.Println() + if report.IsVulnerable() { + fmt.Println("==> The scanned peer is VULNERABLE to Terrapin.") + } else { + fmt.Println("==> The scanned peer supports Terrapin mitigations and can establish") + fmt.Println(" connections that are NOT VULNERABLE to Terrapin. Glad to see this.") + fmt.Println(" For strict key exchange to take effect, both peers must support it.") + } } else { - fmt.Println("==> The scanned peer supports Terrapin mitigations and can establish") - fmt.Println(" connections that are NOT VULNERABLE to Terrapin. Glad to see this.") - fmt.Println(" For strict key exchange to take effect, both peers must support it.") + marshalledReport, err := json.MarshalIndent(report, "", " ") + if err != nil { + return err + } + fmt.Println(string(marshalledReport)) } + return nil } // Prints a short disclaimer to stdout @@ -303,6 +323,10 @@ func main() { "listen", "", "Address to bind to for client-side scans. Format: [host:]") + jsonPtr := flag.Bool( + "json", + false, + "Outputs the scan result as json. Can be useful when calling the scanner from a script.") helpPtr := flag.Bool( "help", false, @@ -330,6 +354,10 @@ func main() { panic(err) } } - printReport(report) - printDisclaimer() + if err := printReport(report, *jsonPtr); err != nil { + panic(err) + } + if !*jsonPtr { + printDisclaimer() + } } From e1668de6341a3547fd62de10f64ff130b8cdc5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=A4umer?= Date: Wed, 20 Dec 2023 16:15:15 +0100 Subject: [PATCH 2/4] docs: Add --json flag to README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 5c53846..ef53d01 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,15 @@ This will download, compile, and install the Go package for your local system. T # The following command will listen for incoming connections on 127.0.0.1:2222 ./Terrapin-Scanner --listen 2222 ``` + +The scanner supports outputting the scan result as json. To do so, provide the `--json` flag when calling the scanner. The output is structured as follows: + +```json +{ + "Banner": "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.5", + "SupportsChaCha20": true, + "SupportsCbcEtm": false, + "SupportsStrictKex": true, + "Vulnerable": false +} +``` From 1709b6e46e526dc8f029e19b4353b052bbf6bdf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=A4umer?= Date: Wed, 20 Dec 2023 16:49:53 +0100 Subject: [PATCH 3/4] feat: Add support for colored output --- go.mod | 8 ++++++++ go.sum | 11 +++++++++++ main.go | 34 +++++++++++++++++++++++++++------- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 24ccead..16af75b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module github.com/RUB-NDS/Terrapin-Scanner go 1.21 + +require github.com/fatih/color v1.16.0 + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.14.0 // indirect +) diff --git a/go.sum b/go.sum index e69de29..7f13de9 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,11 @@ +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/main.go b/main.go index 9bc1c8a..32ab3fb 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "encoding/json" "flag" "fmt" + "github.com/fatih/color" "io" "net" "os" @@ -271,26 +272,45 @@ func formatAddress(address string, mode ScanMode) string { return formatted } +func printColoredBoolean(value bool, ifTrue color.Attribute, ifFalse color.Attribute) { + if value { + color.Set(ifTrue) + } else { + color.Set(ifFalse) + } + fmt.Printf("%t\n", value) + color.Unset() +} + // Prints the report to stdout func printReport(report *TerrapinVulnerabilityReport, outputJson bool) error { if !outputJson { + color.Set(color.FgBlue) fmt.Println("================================================================================") fmt.Println("==================================== Report ====================================") fmt.Println("================================================================================") + color.Unset() fmt.Println() fmt.Printf("Remote Banner: %s\n", report.Banner) fmt.Println() - fmt.Printf("ChaCha20-Poly1305 support: %t\n", report.SupportsChaCha20) - fmt.Printf("CBC-EtM support: %t\n", report.SupportsCbcEtm) + fmt.Print("ChaCha20-Poly1305 support: ") + printColoredBoolean(report.SupportsChaCha20, color.FgYellow, color.FgGreen) + fmt.Print("CBC-EtM support: ") + printColoredBoolean(report.SupportsCbcEtm, color.FgYellow, color.FgGreen) fmt.Println() - fmt.Printf("Strict key exchange support: %t\n", report.SupportsStrictKex) + fmt.Print("Strict key exchange support: ") + printColoredBoolean(report.SupportsStrictKex, color.FgGreen, color.FgRed) fmt.Println() if report.IsVulnerable() { - fmt.Println("==> The scanned peer is VULNERABLE to Terrapin.") + color.Set(color.FgRed) + fmt.Println("The scanned peer is VULNERABLE to Terrapin.") + color.Unset() } else { - fmt.Println("==> The scanned peer supports Terrapin mitigations and can establish") - fmt.Println(" connections that are NOT VULNERABLE to Terrapin. Glad to see this.") - fmt.Println(" For strict key exchange to take effect, both peers must support it.") + color.Set(color.FgGreen) + fmt.Println("The scanned peer supports Terrapin mitigations and can establish") + fmt.Println("connections that are NOT VULNERABLE to Terrapin. Glad to see this.") + fmt.Println("For strict key exchange to take effect, both peers must support it.") + color.Unset() } } else { marshalledReport, err := json.MarshalIndent(report, "", " ") From 26ee2a82faa7af4384ff346f097031ba93a6d52f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=A4umer?= Date: Wed, 20 Dec 2023 16:51:22 +0100 Subject: [PATCH 4/4] feat: Add flag to disable colored output --- main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.go b/main.go index 32ab3fb..4562ccf 100644 --- a/main.go +++ b/main.go @@ -347,11 +347,16 @@ func main() { "json", false, "Outputs the scan result as json. Can be useful when calling the scanner from a script.") + noColor := flag.Bool( + "no-color", + false, + "Disables colored output.") helpPtr := flag.Bool( "help", false, "Prints this usage help to the user.") flag.Parse() + color.NoColor = *noColor if (*connectPtr == "" && *listenPtr == "") || *helpPtr { flag.Usage() printDisclaimer()