package ssh12import (3"context"4"fmt"5"strings"6"time"78"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"9"github.com/projectdiscovery/utils/errkit"10"github.com/zmap/zgrab2/lib/ssh"11)1213type (14// SSHClient is a client for SSH servers.15// Internally client uses github.com/zmap/zgrab2/lib/ssh driver.16// @example17// ```javascript18// const ssh = require('nuclei/ssh');19// const client = new ssh.SSHClient();20// ```21SSHClient struct {22connection *ssh.Client23timeout time.Duration24}25)2627// SetTimeout sets the timeout for the SSH connection in seconds28// @example29// ```javascript30// const ssh = require('nuclei/ssh');31// const client = new ssh.SSHClient();32// client.SetTimeout(10);33// ```34func (c *SSHClient) SetTimeout(sec int) {35c.timeout = time.Duration(sec) * time.Second36}3738// Connect tries to connect to provided host and port39// with provided username and password with ssh.40// Returns state of connection and error. If error is not nil,41// state will be false42// @example43// ```javascript44// const ssh = require('nuclei/ssh');45// const client = new ssh.SSHClient();46// const connected = client.Connect('acme.com', 22, 'username', 'password');47// ```48func (c *SSHClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {49executionId := ctx.Value("executionId").(string)50conn, err := connect(&connectOptions{51Host: host,52Port: port,53User: username,54Password: password,55ExecutionId: executionId,56})57if err != nil {58return false, err59}60c.connection = conn6162return true, nil63}6465// ConnectWithKey tries to connect to provided host and port66// with provided username and private_key.67// Returns state of connection and error. If error is not nil,68// state will be false69// @example70// ```javascript71// const ssh = require('nuclei/ssh');72// const client = new ssh.SSHClient();73// const privateKey = `-----BEGIN RSA PRIVATE KEY----- ...`;74// const connected = client.ConnectWithKey('acme.com', 22, 'username', privateKey);75// ```76func (c *SSHClient) ConnectWithKey(ctx context.Context, host string, port int, username, key string) (bool, error) {77executionId := ctx.Value("executionId").(string)78conn, err := connect(&connectOptions{79Host: host,80Port: port,81User: username,82PrivateKey: key,83ExecutionId: executionId,84})8586if err != nil {87return false, err88}89c.connection = conn9091return true, nil92}9394// ConnectSSHInfoMode tries to connect to provided host and port95// with provided host and port96// Returns HandshakeLog and error. If error is not nil,97// state will be false98// HandshakeLog is a struct that contains information about the99// ssh connection100// @example101// ```javascript102// const ssh = require('nuclei/ssh');103// const client = new ssh.SSHClient();104// const info = client.ConnectSSHInfoMode('acme.com', 22);105// log(to_json(info));106// ```107func (c *SSHClient) ConnectSSHInfoMode(ctx context.Context, host string, port int) (*ssh.HandshakeLog, error) {108executionId := ctx.Value("executionId").(string)109return memoizedconnectSSHInfoMode(&connectOptions{110Host: host,111Port: port,112ExecutionId: executionId,113})114}115116// Run tries to open a new SSH session, then tries to execute117// the provided command in said session118// Returns string and error. If error is not nil,119// state will be false120// The string contains the command output121// @example122// ```javascript123// const ssh = require('nuclei/ssh');124// const client = new ssh.SSHClient();125// client.Connect('acme.com', 22, 'username', 'password');126// const output = client.Run('id');127// log(output);128// ```129func (c *SSHClient) Run(cmd string) (string, error) {130if c.connection == nil {131return "", errkit.New("no connection")132}133session, err := c.connection.NewSession()134if err != nil {135return "", err136}137defer func() {138_ = session.Close()139}()140141data, err := session.Output(cmd)142if err != nil {143return "", err144}145146return string(data), nil147}148149// Close closes the SSH connection and destroys the client150// Returns the success state and error. If error is not nil,151// state will be false152// @example153// ```javascript154// const ssh = require('nuclei/ssh');155// const client = new ssh.SSHClient();156// client.Connect('acme.com', 22, 'username', 'password');157// const closed = client.Close();158// ```159func (c *SSHClient) Close() (bool, error) {160if err := c.connection.Close(); err != nil {161return false, err162}163return true, nil164}165166// unexported functions167type connectOptions struct {168Host string169Port int170User string171Password string172PrivateKey string173Timeout time.Duration // default 10s174ExecutionId string175}176177func (c *connectOptions) validate() error {178if c.Host == "" {179return errkit.New("host is required")180}181if c.Port <= 0 {182return errkit.New("port is required")183}184if !protocolstate.IsHostAllowed(c.ExecutionId, c.Host) {185// host is not valid according to network policy186return protocolstate.ErrHostDenied.Msgf(c.Host)187}188if c.Timeout == 0 {189c.Timeout = 10 * time.Second190}191return nil192}193194// @memo195func connectSSHInfoMode(opts *connectOptions) (*ssh.HandshakeLog, error) {196if err := opts.validate(); err != nil {197return nil, err198}199200data := new(ssh.HandshakeLog)201202sshConfig := ssh.MakeSSHConfig()203sshConfig.Timeout = 10 * time.Second204sshConfig.ConnLog = data205sshConfig.DontAuthenticate = true206sshConfig.BannerCallback = func(banner string) error {207data.Banner = strings.TrimSpace(banner)208return nil209}210rhost := fmt.Sprintf("%s:%d", opts.Host, opts.Port)211client, err := ssh.Dial("tcp", rhost, sshConfig)212if err != nil {213return nil, err214}215defer func() {216_ = client.Close()217}()218219return data, nil220}221222func connect(opts *connectOptions) (*ssh.Client, error) {223if err := opts.validate(); err != nil {224return nil, err225}226227conf := &ssh.ClientConfig{228User: opts.User,229Auth: []ssh.AuthMethod{},230Timeout: opts.Timeout,231}232if len(opts.Password) > 0 {233conf.Auth = append(conf.Auth, ssh.Password(opts.Password))234}235if len(opts.PrivateKey) > 0 {236signer, err := ssh.ParsePrivateKey([]byte(opts.PrivateKey))237if err != nil {238return nil, err239}240conf.Auth = append(conf.Auth, ssh.PublicKeys(signer))241}242243client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", opts.Host, opts.Port), conf)244if err != nil {245return nil, err246}247return client, nil248}249250251