From 9bbc2ea8f41350d9514005210505ca9105f62604 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 14 Oct 2017 19:54:37 -0400 Subject: [PATCH] Name changes to library, remove global config --- lib/ssh/client_auth.go | 2 +- lib/ssh/common.go | 8 +++++ lib/ssh/config.go | 71 +++++++++++++++++++++++++++++++++++++++--- lib/ssh/handshake.go | 8 ++--- lib/ssh/kex.go | 20 ++++++------ lib/ssh/kex_gex.go | 34 ++++++++++---------- modules/ssh.go | 52 +++++++++++++++++++++++++++++-- 7 files changed, 157 insertions(+), 38 deletions(-) diff --git a/lib/ssh/client_auth.go b/lib/ssh/client_auth.go index da97538..d70ce9d 100644 --- a/lib/ssh/client_auth.go +++ b/lib/ssh/client_auth.go @@ -13,7 +13,7 @@ import ( // clientAuthenticate authenticates with the remote server. See RFC 4252. func (c *connection) clientAuthenticate(config *ClientConfig) error { - if c.transport.config.ConnLog != nil && !pkgConfig.CollectUserAuth { + if c.transport.config.ConnLog != nil && !config.DontAuthenticate { // Use ConnLog existence to indicate that this is a run and not testing return nil } diff --git a/lib/ssh/common.go b/lib/ssh/common.go index 66e483a..7cd024a 100644 --- a/lib/ssh/common.go +++ b/lib/ssh/common.go @@ -225,6 +225,14 @@ type Config struct { // A pointer to the handshake log IOT allow incremental building ConnLog *HandshakeLog + + // Whether or not the package should operate in verbose mode + // (save more output) + Verbose bool + + GexMinBits uint + GexMaxBits uint + GexPreferredBits uint } // SetDefaults sets sensible values for unset fields in config. This is diff --git a/lib/ssh/config.go b/lib/ssh/config.go index 7f9b7e2..d622d56 100644 --- a/lib/ssh/config.go +++ b/lib/ssh/config.go @@ -1,11 +1,74 @@ package ssh +import ( + "errors" + "fmt" + "strings" +) + func MakeXSSHConfig() *ClientConfig { ret := new(ClientConfig) ret.DontAuthenticate = true // IOT scan ethically, never attempt to authenticate - ret.ClientVersion = pkgConfig.ClientID - ret.HostKeyAlgorithms = pkgConfig.HostKeyAlgorithms.Get() - ret.KeyExchanges = pkgConfig.KexAlgorithms.Get() - ret.Ciphers = pkgConfig.Ciphers.Get() + ret.HostKeyAlgorithms = supportedHostKeyAlgos + ret.KeyExchanges = defaultKexAlgos + ret.Ciphers = defaultCiphers return ret } + +func (c *ClientConfig) SetHostKeyAlgorithms(value string) error { + for _, alg := range strings.Split(value, ",") { + isValid := false + for _, val := range supportedHostKeyAlgos { + if val == alg { + isValid = true + break + } + } + + if !isValid { + return errors.New(fmt.Sprintf(`host key algorithm not supported: "%s"`, alg)) + } + + c.HostKeyAlgorithms = append(c.HostKeyAlgorithms, alg) + } + return nil +} + +func (c *ClientConfig) SetKexAlgorithms(value string) error { + for _, alg := range strings.Split(value, ",") { + isValid := false + for _, val := range allSupportedKexAlgos { + if val == alg { + isValid = true + break + } + } + + if !isValid { + return errors.New(fmt.Sprintf(`DH KEX algorithm not supported: "%s"`, alg)) + } + + c.KeyExchanges = append(c.KeyExchanges, alg) + } + return nil +} + +func (c *ClientConfig) SetCiphers(value string) error { + for _, inCipher := range strings.Split(value, ",") { + isValid := false + for _, knownCipher := range allSupportedCiphers { + if inCipher == knownCipher { + isValid = true + break + } + } + + if !isValid { + return errors.New(fmt.Sprintf(`cipher not supported: "%s"`, inCipher)) + } + + c.Ciphers = append(c.Ciphers, inCipher) + } + + return nil +} diff --git a/lib/ssh/handshake.go b/lib/ssh/handshake.go index 109abb3..7f4612a 100644 --- a/lib/ssh/handshake.go +++ b/lib/ssh/handshake.go @@ -343,7 +343,7 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro return err } - if pkgConfig.Verbose { + if t.config.Verbose { if t.config.ConnLog != nil { t.config.ConnLog.ClientKex = myInit } @@ -417,7 +417,7 @@ func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) erro } else { result, err = t.client(kex, algs, &magics) } - if pkgConfig.Verbose { + if t.config.Verbose { if t.config.ConnLog != nil { t.config.ConnLog.Crypto = result } @@ -452,12 +452,12 @@ func (t *handshakeTransport) server(kex kexAlgorithm, algs *algorithms, magics * } } - r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey) + r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey, t.config) return r, err } func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics *handshakeMagics) (*kexResult, error) { - result, err := kex.Client(t.conn, t.config.Rand, magics) + result, err := kex.Client(t.conn, t.config.Rand, magics, t.config) if err != nil { return nil, err } diff --git a/lib/ssh/kex.go b/lib/ssh/kex.go index 213df4d..b194c12 100644 --- a/lib/ssh/kex.go +++ b/lib/ssh/kex.go @@ -145,11 +145,11 @@ func LogServerHostKey(sshRawKey []byte) *ServerHostKeyJsonLog { type kexAlgorithm interface { // Server runs server-side key agreement, signing the result // with a hostkey. - Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer) (*kexResult, error) + Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer, c *Config) (*kexResult, error) // Client runs the client-side key agreement. Caller is // responsible for verifying the host key signature. - Client(p packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) + Client(p packetConn, rand io.Reader, magics *handshakeMagics, c *Config) (*kexResult, error) // Create a JSON object for the kexAlgorithm group MarshalJSON() ([]byte, error) @@ -276,7 +276,7 @@ func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handsha }, nil } -func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { +func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer, config *Config) (result *kexResult, err error) { hashFunc := crypto.SHA1 packet, err := c.readPacket() if err != nil { @@ -381,10 +381,10 @@ func (kex *ecdh) GetNew(keyType string) kexAlgorithm { return ret } -func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) { +func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics, config *Config) (*kexResult, error) { kex.JsonLog.Parameters = new(ztoolsKeys.ECDHParams) kex.JsonLog.Parameters.ServerPublic = new(ztoolsKeys.ECPoint) - if pkgConfig.Verbose { + if config.Verbose { kex.JsonLog.Parameters.ClientPublic = new(ztoolsKeys.ECPoint) kex.JsonLog.Parameters.ClientPrivate = new(ztoolsKeys.ECDHPrivateParams) } @@ -394,7 +394,7 @@ func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) ( return nil, err } - if pkgConfig.Verbose { + if config.Verbose { kex.JsonLog.Parameters.ClientPublic.X = ephKey.PublicKey.X kex.JsonLog.Parameters.ClientPublic.Y = ephKey.PublicKey.Y kex.JsonLog.Parameters.ClientPrivate.Value = ephKey.D.Bytes() @@ -494,7 +494,7 @@ func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool { return true } -func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { +func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer, config *Config) (result *kexResult, err error) { packet, err := c.readPacket() if err != nil { return nil, err @@ -639,13 +639,13 @@ func (kp *curve25519KeyPair) generate(rand io.Reader) error { // wrong order. var curve25519Zeros [32]byte -func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) { +func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics, config *Config) (*kexResult, error) { var kp curve25519KeyPair if err := kp.generate(rand); err != nil { return nil, err } - if pkgConfig.Verbose { + if config.Verbose { kex.JsonLog.Parameters.ClientPublic = kp.pub[:] kex.JsonLog.Parameters.ClientPrivate = kp.priv[:] } @@ -700,7 +700,7 @@ func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handsh }, nil } -func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { +func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer, config *Config) (result *kexResult, err error) { packet, err := c.readPacket() if err != nil { return diff --git a/lib/ssh/kex_gex.go b/lib/ssh/kex_gex.go index a3af2ed..3d5ff60 100644 --- a/lib/ssh/kex_gex.go +++ b/lib/ssh/kex_gex.go @@ -83,12 +83,12 @@ func (gex *dhGEXSHA) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, e return new(big.Int).Exp(theirPublic, myPrivate, gex.p), nil } -func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) { +func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshakeMagics, config *Config) (*kexResult, error) { // Send GexRequest kexDHGexRequest := kexDHGexRequestMsg{ - MinBits: uint32(pkgConfig.GexMinBits), - PreferedBits: uint32(pkgConfig.GexPreferredBits), - MaxBits: uint32(pkgConfig.GexMaxBits), + MinBits: uint32(config.GexMinBits), + PreferedBits: uint32(config.GexPreferredBits), + MaxBits: uint32(config.GexMaxBits), } if err := c.writePacket(Marshal(&kexDHGexRequest)); err != nil { return nil, err @@ -111,7 +111,7 @@ func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshak } // reject if p's bit length < pkgConfig.GexMinBits or > pkgConfig.GexMaxBits - if kexDHGexGroup.P.BitLen() < int(pkgConfig.GexMinBits) || kexDHGexGroup.P.BitLen() > int(pkgConfig.GexMaxBits) { + if kexDHGexGroup.P.BitLen() < int(config.GexMinBits) || kexDHGexGroup.P.BitLen() > int(config.GexMaxBits) { return nil, fmt.Errorf("Server-generated gex p (dont't ask) is out of range (%d bits)", kexDHGexGroup.P.BitLen()) } @@ -128,7 +128,7 @@ func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshak X: X, } - if gex.JsonLog != nil && pkgConfig.Verbose { + if gex.JsonLog != nil && config.Verbose { gex.JsonLog.Parameters.ClientPrivate = x gex.JsonLog.Parameters.ClientPublic = X } @@ -164,9 +164,9 @@ func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshak h := gex.hashFunc.New() magics.write(h) writeString(h, kexDHGexReply.HostKey) - binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexMinBits)) - binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexPreferredBits)) - binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexMaxBits)) + binary.Write(h, binary.BigEndian, uint32(config.GexMinBits)) + binary.Write(h, binary.BigEndian, uint32(config.GexPreferredBits)) + binary.Write(h, binary.BigEndian, uint32(config.GexMaxBits)) writeInt(h, gex.p) writeInt(h, gex.g) writeInt(h, X) @@ -184,7 +184,7 @@ func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshak }, nil } -func (gex *dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) { +func (gex *dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer, config *Config) (result *kexResult, err error) { // *Receive GexRequest* packet, err := c.readPacket() if err != nil { @@ -196,11 +196,11 @@ func (gex *dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshak } // smoosh the user's preferred size into our own limits - if kexDHGexRequest.PreferedBits > uint32(pkgConfig.GexMaxBits) { - kexDHGexRequest.PreferedBits = uint32(pkgConfig.GexMaxBits) + if kexDHGexRequest.PreferedBits > uint32(config.GexMaxBits) { + kexDHGexRequest.PreferedBits = uint32(config.GexMaxBits) } - if kexDHGexRequest.PreferedBits < uint32(pkgConfig.GexMinBits) { - kexDHGexRequest.PreferedBits = uint32(pkgConfig.GexMinBits) + if kexDHGexRequest.PreferedBits < uint32(config.GexMinBits) { + kexDHGexRequest.PreferedBits = uint32(config.GexMinBits) } // fix min/max if they're inconsistent. technically, we could just pout // and hang up, but there's no harm in giving them the benefit of the @@ -251,9 +251,9 @@ func (gex *dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshak h := gex.hashFunc.New() magics.write(h) writeString(h, hostKeyBytes) - binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexMinBits)) - binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexPreferredBits)) - binary.Write(h, binary.BigEndian, uint32(pkgConfig.GexMaxBits)) + binary.Write(h, binary.BigEndian, uint32(config.GexMinBits)) + binary.Write(h, binary.BigEndian, uint32(config.GexPreferredBits)) + binary.Write(h, binary.BigEndian, uint32(config.GexMaxBits)) writeInt(h, gex.p) writeInt(h, gex.g) writeInt(h, kexDHGexInit.X) diff --git a/modules/ssh.go b/modules/ssh.go index fb505aa..d01a652 100644 --- a/modules/ssh.go +++ b/modules/ssh.go @@ -1,8 +1,13 @@ package modules import ( + "net" + "strconv" + "time" + log "github.com/sirupsen/logrus" "github.com/zmap/zgrab2" + "github.com/zmap/zgrab2/lib/ssh" ) type SSHFlags struct { @@ -11,6 +16,11 @@ type SSHFlags struct { KexAlgorithms string `long:"kex-algorithms" description:"Set SSH Key Exchange Algorithms"` HostKeyAlgorithms string `long:"host-key-algorithms" description:"Set SSH Host Key Algorithms"` NegativeOne bool `long:"negative-one" description:"Set SSH DH kex value to -1 in the selected group"` + Ciphers string `long:"ciphers" description:"A comma-separated list of which ciphers to offer."` + CollectUserAuth bool `long:"userauth" description:"Use the 'none' authentication request to see what userauth methods are allowed"` + GexMinBits uint `long:"gex-min-bits" description:"The minimum number of bits for the DH GEX prime." default:"1024"` + GexMaxBits uint `long:"gex-max-bits" description:"The maximum number of bits for the DH GEX prime." default:"8192"` + GexPreferredBits uint `long:"gex-preferred-bits" description:"The preferred number of bits for the DH GEX prime." default:"2048"` } type SSHModule struct { @@ -57,6 +67,44 @@ func (s *SSHScanner) InitPerSender(senderID int) error { func (s *SSHScanner) GetName() string { return s.config.Name } -func (s *SSHScanner) Scan(t zgrab2.ScanTarget, port uint) (interface{}, error) { - return nil, nil + +func (s *SSHScanner) makeSSHGrabber(hlog *ssh.HandshakeLog) func(string) error { + return func(netAddr string) error { + sshConfig := ssh.MakeSSHConfig() + sshConfig.Timeout = time.Duration(s.config.Timeout) * time.Second + sshConfig.ConnLog = hlog + sshConfig.ClientVersion = s.ClientID + if err := sshConfig.SetHostKeyAlgorithms(s.config.HostKeyAlgorithms); err != nil { + log.Fatal(err) + } + if err := sshConfig.SetKexAlgorithms(s.config.KexAlgorithms); err != nil { + log.Fatal(err) + } + if err := sshConfig.SetCiphers(s.config.Ciphers); err != nil { + log.Fatal(err) + } + sshConfig.Verbose = s.config.Verbose + sshConfig.DontAuthenticate = s.config.CollectUserAuth + sshConfig.GexMinBits = s.config.GexMinBits + sshConfig.GexMaxBits = s.config.GexMaxBits + sshConfig.GexPreferredBits = s.config.GexPreferredBits + _, err := ssh.Dial("tcp", netAddr, sshConfig) + if err != nil { + return err + } + return nil + } +} + +func (s *SSHScanner) Scan(t zgrab2.ScanTarget) (interface{}, error) { + data := new(ssh.HandshakeLog) + sshGrabber := s.makeSSHGrabber(data) + + //TODO: domain name? + port := strconv.FormatUint(uint64(s.config.Port), 10) + rhost := net.JoinHostPort(t.IP.String(), port) + + err := sshGrabber(rhost) + + return data, err }