![justinbastress](/assets/img/avatar_default.png)
* remove unnecessary indirection on net.Conn * Ignore *.pyc * fix NPE on nil handshake * refactoring -- move status to status.go; add Open() methods for ScanTarget * cherry-pick .gitignore fix * pull in TLS fix * status.go comments * trim over-generalizations * use /usr/bin/env bash instead of absolute path * remove debug tcpwrap * add integration tests for postgres * hack for cleanup.sh to work on mingw -- use //var/lib instead of /var/lib * cleanup should actually stop the process though * comments / rearrange * Bump up timeout in postgres tests; only pass user if explicitly requested to do so * add schema stubs to new.sh * Integration test fixes -- use /usr/bin/env bash; log all validation failures * add postgres schemas * fill out zcrypto.client_hello schema * handle early get of TLSLog * postgres: return SCAN_SUCCESS on success * cleanup * fix new.sh * fix typo * postgres container cleanup * build.sh docs * standardize container/image names * add not to check for success * shift mysql's connection management to ScanTarget.Open(); wrap Read/Write methods returned by ScanTarget.Open() to enforce timeouts * catch schematically-valid but non-successful scans * postgres: clean up output format; more scanning * cleanup; better error handling; get detailed protocol version error * refactor modules * clean up dangling connections * split gigantic postgres.go * remove unused * ServerParams gets its own type * refactor integration tests: run zgrab2 in its own container, which is linked to the service containers, so that we don't need to keep track of unique ports on the host any more * rename entrypoint; remove duplicate postgres tests * comments for postgres schema * port over ftp support from the original zgrab; add schema / integration tests for FTP; fix log line in ssh test * Use param expansion to check for env variable [minor] This is a *very* minor change to `docker-runner/docker-run.sh` checks to see if the environment variable required to run the script has been set to a non-empty string. If not, the script exits with a non-zero status code and displays a default message: ``` ❯ docker-runner/docker-run.sh docker-runner/docker-run.sh: line 7: CONTAINER_NAME: parameter null or not set ``` This was the behavior before, but just uses a one-liner declarative bash idiom. For further reading on parameter expansion, see https://stackoverflow.com/a/307735. @justinbastress can tell me if I did something wrong and broke the intent of the script :-) * Add integration_test targets to makefile; use makefile instead of directly calling go build everywhere; run postgres schema through PEP8 linter * use make in docker-runner entrypoint * add .integration_test_setup to .gitignore * cleanup * add ftp schema * more .gitignore items * Makefile updates: Windows support; add docker-runner target; better cleanup. * docker-runner Dockerfile: start from zgrab2_runner_base image * cleanup postgres setup * make travis use make * add .gitattributes, try to prevent it from overriding lfs with crlfs in shell scripts at least * fix folder name in Makefile * update go (one of our dependencies now works only with >= 1.9) * From travis: `I don't have any idea what to do with '1.9.0'.` * explicit clean make * fix dep order * fix build.sh location * popd * use make to ensure zgrab2_runner exists * Make docker-runner an order-dependency for integration-test-cleanup; don't do a cleanup after each integration test * use explicit tag name for zgrab2_runner * Add container-clean target to Makefile, to remove cyclic dependency on docker; use .id files to track docker images; add servce-base image; use Make to build / track images * use LF in Makefiles; update .gitignore; use zgrab_service_base image in ssh container; fix line endings (?) * remove overzealous cleanup * let setup continue even if some containers are already running * ftp: use zgrab2_service_base for ftp container; restart container if already running * FTP: remove redundant prefix; add non-authtls test * zgrab depends on *.go * docker-runner depends on zgrab2 binary * clean output before running integration tests * address Zakir's comments: rename auth_*_resp -> auth_*, 'a FTP banner' -> 'an FTP banner' * fix schema * update/rebuild when necessary in container
190 lines
5.9 KiB
Go
190 lines
5.9 KiB
Go
package modules
|
|
|
|
import (
|
|
"net"
|
|
"regexp"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/zmap/zgrab2"
|
|
)
|
|
|
|
// FTPScanResults is the output of the scan. Identical to the original from zgrab, with the addition of TLSLog.
|
|
type FTPScanResults struct {
|
|
Banner string `json:"banner,omitempty"`
|
|
AuthTLSResp string `json:"auth_tls,omitempty"`
|
|
AuthSSLResp string `json:"auth_ssl,omitempty"`
|
|
TLSLog *zgrab2.TLSLog `json:"tls,omitempty"`
|
|
}
|
|
|
|
// FTP-specific command-line flags. Taken from the original zgrab (TODO: should FTPAuthTLS be on by default?).
|
|
type FTPFlags struct {
|
|
zgrab2.BaseFlags
|
|
zgrab2.TLSFlags
|
|
Verbose bool `long:"verbose" description:"More verbose logging, include debug fields in the scan results"`
|
|
FTPAuthTLS bool `long:"authtls" description:"Collect FTPS certificates in addition to FTP banners"`
|
|
}
|
|
|
|
// FTPModule implements the zgrab2.Module interface
|
|
type FTPModule struct {
|
|
}
|
|
|
|
// FTPScanner implements the zgrab2.Scanner interface, and holds the state for a single scan instance
|
|
type FTPScanner struct {
|
|
config *FTPFlags
|
|
}
|
|
|
|
// FTPConnection holds the state for a single connection to the FTP server.
|
|
type FTPConnection struct {
|
|
buffer [1024]byte // temp buffer for sending commands -- so, never interleave sendCommand calls on a given connection
|
|
config *FTPFlags
|
|
results FTPScanResults
|
|
conn net.Conn
|
|
}
|
|
|
|
// ftp.init() registers the ftp zgrab2 module
|
|
func init() {
|
|
var module FTPModule
|
|
_, err := zgrab2.AddCommand("ftp", "FTP", "Grab an FTP banner", 21, &module)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (m *FTPModule) NewFlags() interface{} {
|
|
return new(FTPFlags)
|
|
}
|
|
|
|
func (m *FTPModule) NewScanner() zgrab2.Scanner {
|
|
return new(FTPScanner)
|
|
}
|
|
|
|
func (f *FTPFlags) Validate(args []string) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *FTPFlags) Help() string {
|
|
return ""
|
|
}
|
|
|
|
func (s *FTPScanner) Init(flags zgrab2.ScanFlags) error {
|
|
f, _ := flags.(*FTPFlags)
|
|
s.config = f
|
|
return nil
|
|
}
|
|
|
|
func (s *FTPScanner) InitPerSender(senderID int) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *FTPScanner) GetName() string {
|
|
return s.config.Name
|
|
}
|
|
|
|
func (s *FTPScanner) GetPort() uint {
|
|
return s.config.Port
|
|
}
|
|
|
|
// ftpEndRegex matches zero or more lines followed by a numeric FTP status code and linebreak, e.g. "200 OK\r\n"
|
|
var ftpEndRegex = regexp.MustCompile(`^(?:.*\r?\n)*([0-9]{3})( [^\r\n]*)?\r?\n$`)
|
|
|
|
// FTPConnection.isOKResponse() returns true iff the given response code indicates success (e.g. 2XX)
|
|
func (ftp *FTPConnection) isOKResponse(retCode string) bool {
|
|
// TODO: This is the current behavior; should it check that it isn't garbage that happens to start with 2 (e.g. it's only ASCII chars, the prefix is 2[0-9]+, etc)?
|
|
return strings.HasPrefix(retCode, "2")
|
|
}
|
|
|
|
// FTPConnection.readResponse() reads an FTP response chunk from the server. It returns the full response, as well as the status code alone.
|
|
func (ftp *FTPConnection) readResponse() (string, string, error) {
|
|
respLen, err := zgrab2.ReadUntilRegex(ftp.conn, ftp.buffer[:], ftpEndRegex)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
ret := string(ftp.buffer[0:respLen])
|
|
retCode := ftpEndRegex.FindStringSubmatch(ret)[1]
|
|
return ret, retCode, nil
|
|
}
|
|
|
|
// FTPConnection.GetFTPBanner() was taken over from the original zgrab. Read the banner sent by the server immediately after connecting. Returns true iff the server returns a succesful status code.
|
|
func (ftp *FTPConnection) GetFTPBanner() (bool, error) {
|
|
banner, retCode, err := ftp.readResponse()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
ftp.results.Banner = banner
|
|
return ftp.isOKResponse(retCode), nil
|
|
}
|
|
|
|
// FTPConnection.sendCommand() sends a command to the server and waits for / reads / returns the response.
|
|
func (ftp *FTPConnection) sendCommand(cmd string) (string, string, error) {
|
|
ftp.conn.Write([]byte(cmd + "\r\n"))
|
|
return ftp.readResponse()
|
|
}
|
|
|
|
// FTPConnection.SetupFTPS() was taken over from the original zgrab. Returns true iff the server reported support for FTPS. First attempt AUTH TLS; if that fails, try AUTH SSL.
|
|
func (ftp *FTPConnection) SetupFTPS() (bool, error) {
|
|
ret, retCode, err := ftp.sendCommand("AUTH TLS")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
ftp.results.AuthTLSResp = ret
|
|
if ftp.isOKResponse(retCode) {
|
|
return true, nil
|
|
} else {
|
|
ret, retCode, err = ftp.sendCommand("AUTH SSL")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
ftp.results.AuthSSLResp = ret
|
|
|
|
if ftp.isOKResponse(retCode) {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
// FTPConnection.GetFTPSCertificates() was taken over from the original zgrab. If the server supports TLS/SSL, perform the handshake. The connection's results field is populated with the results.
|
|
func (ftp *FTPConnection) GetFTPSCertificates() error {
|
|
ftpsReady, err := ftp.SetupFTPS()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ftpsReady {
|
|
return nil
|
|
}
|
|
var conn *zgrab2.TLSConnection
|
|
if conn, err = ftp.config.TLSFlags.GetTLSConnection(ftp.conn); err != nil {
|
|
return err
|
|
}
|
|
ftp.results.TLSLog = conn.GetLog()
|
|
|
|
if err = conn.Handshake(); err != nil {
|
|
// NOTE: With the default config of vsftp (without ssl_ciphers=HIGH), AUTH TLS succeeds, but the handshake fails, dumping "error:1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher" to the socket.
|
|
return err
|
|
}
|
|
ftp.conn = conn
|
|
return nil
|
|
}
|
|
|
|
// FTPScanner.Scan() was taken over from the original zgrab. Reads the initial banner, then, if FTPAuthTLS is set, attempt an upgrade to FTPS.
|
|
func (s *FTPScanner) Scan(t zgrab2.ScanTarget) (status zgrab2.ScanStatus, result interface{}, thrown error) {
|
|
var err error
|
|
conn, err := t.Open(&s.config.BaseFlags)
|
|
if err != nil {
|
|
return zgrab2.TryGetScanStatus(err), nil, err
|
|
}
|
|
ftp := FTPConnection{conn: conn, config: s.config, results: FTPScanResults{}}
|
|
is200Banner, err := ftp.GetFTPBanner()
|
|
if err != nil {
|
|
return zgrab2.TryGetScanStatus(err), &ftp.results, err
|
|
}
|
|
if s.config.FTPAuthTLS && is200Banner {
|
|
if err := ftp.GetFTPSCertificates(); err != nil {
|
|
return zgrab2.SCAN_APPLICATION_ERROR, &ftp.results, err
|
|
}
|
|
}
|
|
return zgrab2.SCAN_SUCCESS, &ftp.results, nil
|
|
}
|