Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add JSON and colored output #7

Merged
merged 4 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
```
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
93 changes: 73 additions & 20 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package main
import (
"bufio"
"encoding/binary"
"encoding/json"
"flag"
"fmt"
"github.com/fatih/color"
"io"
"net"
"os"
Expand Down Expand Up @@ -62,6 +64,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) {
Expand Down Expand Up @@ -200,7 +212,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
Expand Down Expand Up @@ -260,26 +272,54 @@ 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) {
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 {
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.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.Print("Strict key exchange support: ")
printColoredBoolean(report.SupportsStrictKex, color.FgGreen, color.FgRed)
fmt.Println()
if report.IsVulnerable() {
color.Set(color.FgRed)
fmt.Println("The scanned peer is VULNERABLE to Terrapin.")
color.Unset()
} else {
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 {
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
Expand All @@ -303,11 +343,20 @@ func main() {
"listen",
"",
"Address to bind to for client-side scans. Format: [host:]<port>")
jsonPtr := flag.Bool(
"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()
Expand All @@ -330,6 +379,10 @@ func main() {
panic(err)
}
}
printReport(report)
printDisclaimer()
if err := printReport(report, *jsonPtr); err != nil {
panic(err)
}
if !*jsonPtr {
printDisclaimer()
}
}