From f0d9070733357ec868beb860a6b2570af8ea20e7 Mon Sep 17 00:00:00 2001 From: Ricky Diaz Gomez Date: Mon, 17 Jun 2019 15:23:56 -0400 Subject: [PATCH] Redis: Takes JSON/YAML file that allows for renaming of commands --- .../redis/container/extra-commands.json | 4 + .../redis/container/extra-commands.yaml | 2 + .../redis/container/mappings.json | 6 + .../redis/container/mappings.yaml | 4 + .../redis/container/renamed.conf | 2 +- integration_tests/redis/setup.sh | 6 +- integration_tests/redis/test.sh | 3 + modules/redis/scanner.go | 104 +++++++++++++++--- 8 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 integration_tests/redis/container/extra-commands.json create mode 100644 integration_tests/redis/container/extra-commands.yaml create mode 100644 integration_tests/redis/container/mappings.json create mode 100644 integration_tests/redis/container/mappings.yaml diff --git a/integration_tests/redis/container/extra-commands.json b/integration_tests/redis/container/extra-commands.json new file mode 100644 index 0000000..e5639a7 --- /dev/null +++ b/integration_tests/redis/container/extra-commands.json @@ -0,0 +1,4 @@ +[ + "PING", + "AUTH password!" +] \ No newline at end of file diff --git a/integration_tests/redis/container/extra-commands.yaml b/integration_tests/redis/container/extra-commands.yaml new file mode 100644 index 0000000..3a2cbf7 --- /dev/null +++ b/integration_tests/redis/container/extra-commands.yaml @@ -0,0 +1,2 @@ +- PING +- AUTH password! \ No newline at end of file diff --git a/integration_tests/redis/container/mappings.json b/integration_tests/redis/container/mappings.json new file mode 100644 index 0000000..6dc14d3 --- /dev/null +++ b/integration_tests/redis/container/mappings.json @@ -0,0 +1,6 @@ +{ + "PING": "RENAMED-PING", + "ECHO": "RENAMED-ECHO", + "AUTH": "RENAMED-AUTH", + "INFO": "RENAMED-INFO" +} \ No newline at end of file diff --git a/integration_tests/redis/container/mappings.yaml b/integration_tests/redis/container/mappings.yaml new file mode 100644 index 0000000..bd40cb7 --- /dev/null +++ b/integration_tests/redis/container/mappings.yaml @@ -0,0 +1,4 @@ +PING: RENAMED-PING +ECHO: RENAMED-ECHO +AUTH: RENAMED-AUTH +INFO: RENAMED-INFO diff --git a/integration_tests/redis/container/renamed.conf b/integration_tests/redis/container/renamed.conf index fb33ae1..3a8e692 100644 --- a/integration_tests/redis/container/renamed.conf +++ b/integration_tests/redis/container/renamed.conf @@ -1,5 +1,5 @@ rename-command PING "" rename-command DEBUG "RENAMED-DEBUG" rename-command ECHO "RENAMED-ECHO" -rename-command INFO "" +rename-command INFO "RENAMED-INFO" rename-command CLIENT "RENAMED-CLIENT" diff --git a/integration_tests/redis/setup.sh b/integration_tests/redis/setup.sh index e3c5ce4..7cb60ec 100755 --- a/integration_tests/redis/setup.sh +++ b/integration_tests/redis/setup.sh @@ -6,11 +6,11 @@ echo "redis/setup: Tests setup for redis" CONTAINER_TAG="zgrab_redis" -configs="password renamed default" +configs="default password renamed" for cfg in $configs; do CONTAINER_NAME="zgrab_redis_$cfg" - + RUN_ARGS="--rm --name $CONTAINER_NAME -td $CONTAINER_TAG redis-server //usr/local/etc/redis/${cfg}.conf" # If the container is already running, use it. if docker ps --filter "name=$CONTAINER_NAME" | grep -q $CONTAINER_NAME; then @@ -18,7 +18,7 @@ for cfg in $configs; do exit 0 fi - # If it is not running, try launching it -- on success, use that. + # If it is not running, try launching it -- on success, use that. echo "redis/setup: Trying to launch $CONTAINER_NAME..." if ! docker run $RUN_ARGS; then echo "redis/setup: Building docker image $CONTAINER_TAG..." diff --git a/integration_tests/redis/test.sh b/integration_tests/redis/test.sh index 9aae871..bdb48b9 100755 --- a/integration_tests/redis/test.sh +++ b/integration_tests/redis/test.sh @@ -4,6 +4,7 @@ set -e MODULE_DIR=$(dirname $0) ZGRAB_ROOT=$MODULE_DIR/../.. ZGRAB_OUTPUT=$ZGRAB_ROOT/zgrab-output +CONTAINER_DIR="./integration_tests/redis/container" mkdir -p $ZGRAB_OUTPUT/redis @@ -16,6 +17,8 @@ for cfg in $configs; do echo "redis/test: Testing $CONTAINER_NAME" CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh redis > "$ZGRAB_OUTPUT/redis/${cfg}-normal.json" CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh redis --inline > "$ZGRAB_OUTPUT/redis/${cfg}-inline.json" + CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh redis --mappings "$CONTAINER_DIR/mappings.json" > "$ZGRAB_OUTPUT/redis/${cfg}-normal-mappings.json" + CONTAINER_NAME=$CONTAINER_NAME $ZGRAB_ROOT/docker-runner/docker-run.sh redis --inline --mappings "$CONTAINER_DIR/mappings.json" > "$ZGRAB_OUTPUT/redis/${cfg}-inline-mappings.json" done for cfg in $configs; do diff --git a/modules/redis/scanner.go b/modules/redis/scanner.go index a445c1f..2a035c8 100644 --- a/modules/redis/scanner.go +++ b/modules/redis/scanner.go @@ -13,24 +13,28 @@ package redis import ( + "encoding/json" "fmt" "io" + "io/ioutil" + "path/filepath" "strings" log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" + "gopkg.in/yaml.v2" ) // Flags contains redis-specific command-line flags. type Flags struct { zgrab2.BaseFlags - // TODO: Take a JSON/YAML file with a list of custom commands to execute? - // TODO: Take a JSON/YAML file with mappings for command names? - AuthCommand string `long:"auth-command" default:"AUTH" description:"Override the command used to authenticate. Ignored if no password is set."` - Password string `long:"password" description:"Set a password to use to authenticate to the server. WARNING: This is sent in the clear."` - DoInline bool `long:"inline" description:"Send commands using the inline syntax"` - Verbose bool `long:"verbose" description:"More verbose logging, include debug fields in the scan results"` + // TODO: Take a JSON/YAML file with a list of custom commands to execute? array? + CustomCommands string `long:"custom-commands" description:"Pathname for JSON/YAML file that contains extra commands to execute."` + Mappings string `long:"mappings" description:"Pathname for JSON/YAML file that contains mappings for command names."` + Password string `long:"password" description:"Set a password to use to authenticate to the server. WARNING: This is sent in the clear."` + DoInline bool `long:"inline" description:"Send commands using the inline syntax"` + Verbose bool `long:"verbose" description:"More verbose logging, include debug fields in the scan results"` } // Module implements the zgrab2.Module interface @@ -39,7 +43,9 @@ type Module struct { // Scanner implements the zgrab2.Scanner interface type Scanner struct { - config *Flags + config *Flags + commandMappings map[string]interface{} + customCommands []string } // scan holds the state for the scan of an individual target @@ -124,6 +130,10 @@ func (flags *Flags) Help() string { func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error { f, _ := flags.(*Flags) scanner.config = f + err := scanner.initCommands() + if err != nil { + log.Fatal(err) + } return nil } @@ -152,6 +162,71 @@ func (scan *scan) Close() { defer scan.close() } +func getUnmarshaler(file string) (func([]byte, interface{}) error, error) { + var unmarshaler func([]byte, interface{}) error + switch ext := filepath.Ext(file); ext { + case ".json": + unmarshaler = json.Unmarshal + case ".yaml", ".yml": + unmarshaler = yaml.Unmarshal + default: + err := fmt.Errorf("File type %s not valid.", ext) + return nil, err + } + return unmarshaler, nil +} + +func getFileContents(file string, output interface{}) error { + unmarshaler, err := getUnmarshaler(file) + if err != nil { + return err + } + fileContent, err := ioutil.ReadFile(file) + if err != nil { + return err + } + err = unmarshaler([]byte(fileContent), output) + if err != nil { + return err + } + + return nil +} + +// Initializes the command mappings +func (scanner *Scanner) initCommands() error { + scanner.commandMappings = map[string]interface{}{ + "PING": "PING", + "AUTH": "AUTH", + "INFO": "INFO", + "NONEXISTENT": "NONEXISTENT", + "QUIT": "QUIT", + } + + if scanner.config.CustomCommands != "" { + var customCommands []string + err := getFileContents(scanner.config.CustomCommands, &customCommands) + if err != nil { + return err + } + scanner.customCommands = customCommands + } + + // User supplied a file for updated command mappings + if scanner.config.Mappings != "" { + var mappings map[string]string + err := getFileContents(scanner.config.Mappings, &mappings) + if err != nil { + return err + } + for origCommand, newCommand := range mappings { + scanner.commandMappings[strings.ToUpper(origCommand)] = strings.ToUpper(newCommand) + } + } + + return nil +} + // SendCommand sends the given command/args to the server, using the scanner's // configuration, and drop the command/output into the result. func (scan *scan) SendCommand(cmd string, args ...string) (RedisValue, error) { @@ -228,9 +303,9 @@ func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, inter } defer scan.Close() result := scan.result - pingResponse, err := scan.SendCommand("PING") + pingResponse, err := scan.SendCommand(scanner.commandMappings["PING"].(string)) if err != nil { - // if the first command fails (as opposed to succeeding but returning an + // If the first command fails (as opposed to succeeding but returning an // ErrorMessage response), then flag the probe as having failed. return zgrab2.TryGetScanStatus(err), nil, err } @@ -238,19 +313,18 @@ func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, inter // we have positively identified that a redis service is present. result.PingResponse = forceToString(pingResponse) if scanner.config.Password != "" { - authResponse, err := scan.SendCommand(scanner.config.AuthCommand, scanner.config.Password) + authResponse, err := scan.SendCommand(scanner.commandMappings["AUTH"].(string), scanner.config.Password) if err != nil { return zgrab2.TryGetScanStatus(err), result, err } result.AuthResponse = forceToString(authResponse) } - infoResponse, err := scan.SendCommand("INFO") + infoResponse, err := scan.SendCommand(scanner.commandMappings["INFO"].(string)) if err != nil { return zgrab2.TryGetScanStatus(err), result, err } result.InfoResponse = forceToString(infoResponse) - infoResponseBulk, ok := infoResponse.(BulkString) - if ok { + if infoResponseBulk, ok := infoResponse.(BulkString); ok { for _, line := range strings.Split(string(infoResponseBulk), "\r\n") { if strings.HasPrefix(line, "redis_version:") { result.Version = strings.SplitN(line, ":", 2)[1] @@ -258,12 +332,12 @@ func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, inter } } } - bogusResponse, err := scan.SendCommand("NONEXISTENT") + bogusResponse, err := scan.SendCommand(scanner.commandMappings["NONEXISTENT"].(string)) if err != nil { return zgrab2.TryGetScanStatus(err), result, err } result.NonexistentResponse = forceToString(bogusResponse) - quitResponse, err := scan.SendCommand("QUIT") + quitResponse, err := scan.SendCommand(scanner.commandMappings["QUIT"].(string)) if err != nil && err != io.EOF { return zgrab2.TryGetScanStatus(err), result, err }