From ec5687e2efbe2c882d5ff8aaaa22d23ebe74c01b Mon Sep 17 00:00:00 2001 From: Valerio Casalino Date: Thu, 23 Nov 2023 19:37:45 +0100 Subject: [PATCH] Make the SSH client used in javascript templates able to execute custom commands (#4407) * make ssh module store the connection * make ssh module able to execute commands using the client * add bingen + update docs --------- Co-authored-by: Tarun Koyalwar --- .gitignore | 2 +- pkg/js/devtools/bindgen/cmd/bindgen/main.go | 76 ++++++++++++ .../devtools/bindgen/templates/go_class.tmpl | 2 +- pkg/js/generated/js/libssh/ssh.js | 110 +++++++++++------- pkg/js/libs/ssh/ssh.go | 45 ++++++- 5 files changed, 190 insertions(+), 45 deletions(-) create mode 100644 pkg/js/devtools/bindgen/cmd/bindgen/main.go diff --git a/.gitignore b/.gitignore index d203503d..10269185 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ pkg/protocols/common/helpers/deserialization/testdata/ValueObject.class pkg/protocols/common/helpers/deserialization/testdata/ValueObject2.ser *.exe .gitignore -pkg/js/devtools/bindgen/cmd/bindgen +pkg/js/devtools/bindgen/cmd/bindgen/bindgen pkg/js/devtools/jsdocgen/jsdocgen *.DS_Store pkg/protocols/headless/engine/.cache diff --git a/pkg/js/devtools/bindgen/cmd/bindgen/main.go b/pkg/js/devtools/bindgen/cmd/bindgen/main.go new file mode 100644 index 00000000..cb849c76 --- /dev/null +++ b/pkg/js/devtools/bindgen/cmd/bindgen/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "flag" + "fmt" + "log" + "path" + "path/filepath" + + "github.com/pkg/errors" + generator "github.com/projectdiscovery/nuclei/v3/pkg/js/devtools/bindgen" + fileutil "github.com/projectdiscovery/utils/file" +) + +var ( + dir string + generatedDir string + targetModules string +) + +func main() { + flag.StringVar(&dir, "dir", "libs", "directory to process") + flag.StringVar(&generatedDir, "out", "generated", "directory to output generated files") + flag.StringVar(&targetModules, "target", "", "target modules to generate") + flag.Parse() + log.SetFlags(0) + if !fileutil.FolderExists(dir) { + log.Fatalf("directory %s does not exist", dir) + } + if err := process(); err != nil { + log.Fatal(err) + } +} + +func process() error { + modules, err := generator.GetLibraryModules(dir) + if err != nil { + return errors.Wrap(err, "could not get library modules") + } + if len(modules) == 0 && fileutil.FolderExists(dir) { + // if no modules are found, then given directory is the module itself + targetModules = path.Base(dir) + modules = append(modules, targetModules) + dir = filepath.Dir(dir) + } + for _, module := range modules { + log.Printf("[module] Generating %s", module) + + data, err := generator.CreateTemplateData(filepath.Join(dir, module), "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/") + if err != nil { + return fmt.Errorf("could not create template data: %v", err) + } + + prefixed := "lib" + module + err = data.WriteJSTemplate(filepath.Join(generatedDir, "js/"+prefixed), module) + if err != nil { + return fmt.Errorf("could not write js template: %v", err) + } + err = data.WriteGoTemplate(path.Join(generatedDir, "go/"+prefixed), module) + if err != nil { + return fmt.Errorf("could not write go template: %v", err) + } + // disabled for now since we have static website for docs + // err = data.WriteMarkdownLibraryDocumentation(path.Join(generatedDir, "markdown/"), module) + // if err != nil { + // return fmt.Errorf("could not write markdown template: %v", err) + // } + + // err = data.WriteMarkdownIndexTemplate(path.Join(generatedDir, "markdown/")) + // if err != nil { + // return fmt.Errorf("could not write markdown index template: %v", err) + // } + data.InitNativeScripts() + } + return nil +} diff --git a/pkg/js/devtools/bindgen/templates/go_class.tmpl b/pkg/js/devtools/bindgen/templates/go_class.tmpl index 3432430a..74d52b3e 100644 --- a/pkg/js/devtools/bindgen/templates/go_class.tmpl +++ b/pkg/js/devtools/bindgen/templates/go_class.tmpl @@ -6,7 +6,7 @@ import ( {{$pkgName}} "{{.PackagePath}}" "github.com/dop251/goja" - "github.com/projectdiscovery/nuclei/v2/pkg/js/gojs" + "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) var ( diff --git a/pkg/js/generated/js/libssh/ssh.js b/pkg/js/generated/js/libssh/ssh.js index 81294f0f..6c63b0be 100644 --- a/pkg/js/generated/js/libssh/ssh.js +++ b/pkg/js/generated/js/libssh/ssh.js @@ -1,68 +1,98 @@ /** @module ssh */ +/** + * @typedef {object} HandshakeLog + * @description HandshakeLog is a struct that contains information about the ssh connection. + */ +const HandshakeLog = {}; + /** * @class * @classdesc SSHClient is a client for SSH servers. Internally client uses github.com/zmap/zgrab2/lib/ssh driver. */ class SSHClient { /** - * @method - * @description Connect tries to connect to provided host and port with provided username and password with ssh. Returns state of connection and error. If error is not nil, state will be false. - * @param {string} host - The host to connect to. - * @param {number} port - The port to connect to. - * @param {string} username - The username to use for connection. - * @param {string} password - The password to use for connection. - * @returns {boolean} - The state of the connection. - * @throws {error} - The error encountered during connection. - * @example - * let m = require('nuclei/ssh'); - * let c = m.SSHClient(); - * let state = c.Connect('localhost', 22, 'user', 'password'); + @method + @description Close closes the SSH connection and destroys the client. Returns the success state and error. If error is not nil, state will be false. + @returns {boolean} - The success state of the operation. + @throws {error} - The error encountered during the operation. + @example + let m = require('nuclei/ssh'); + let c = m.SSHClient(); + let state = c.Connect('localhost', 22, 'user', 'password'); + c.Close(); + */ + Close() { + // implemented in go + }; + + /** + @method + @description Connect tries to connect to provided host and port with provided username and password with ssh. Returns state of connection and error. If error is not nil, state will be false. + @param {string} host - The host to connect to. + @param {number} port - The port to connect to. + @param {string} username - The username for the connection. + @param {string} password - The password for the connection. + @returns {boolean} - The state of the connection. + @throws {error} - The error encountered during the connection. + @example + let m = require('nuclei/ssh'); + let c = m.SSHClient(); + let result = c.Connect('localhost', 22, 'user', 'password'); */ Connect(host, port, username, password) { // implemented in go }; /** - * @method - * @description ConnectSSHInfoMode tries to connect to provided host and port. Returns HandshakeLog and error. If error is not nil, state will be false. HandshakeLog is a struct that contains information about the ssh connection. - * @param {string} host - The host to connect to. - * @param {number} port - The port to connect to. - * @returns {HandshakeLog} - The HandshakeLog object containing information about the ssh connection. - * @throws {error} - The error encountered during connection. - * @example - * let m = require('nuclei/ssh'); - * let c = m.SSHClient(); - * let log = c.ConnectSSHInfoMode('localhost', 22); + @method + @description ConnectSSHInfoMode tries to connect to provided host and port with provided host and port. Returns HandshakeLog and error. If error is not nil, state will be false. + @param {string} host - The host to connect to. + @param {number} port - The port to connect to. + @returns {HandshakeLog} - The HandshakeLog object containing information about the ssh connection. + @throws {error} - The error encountered during the connection. + @example + let m = require('nuclei/ssh'); + let c = m.SSHClient(); + let result = c.ConnectSSHInfoMode('localhost', 22); */ ConnectSSHInfoMode(host, port) { // implemented in go }; /** - * @method - * @description ConnectWithKey tries to connect to provided host and port with provided username and private_key. Returns state of connection and error. If error is not nil, state will be false. - * @param {string} host - The host to connect to. - * @param {number} port - The port to connect to. - * @param {string} username - The username to use for connection. - * @param {string} key - The private key to use for connection. - * @returns {boolean} - The state of the connection. - * @throws {error} - The error encountered during connection. - * @example - * let m = require('nuclei/ssh'); - * let c = m.SSHClient(); - * let state = c.ConnectWithKey('localhost', 22, 'user', 'key'); + @method + @description ConnectWithKey tries to connect to provided host and port with provided username and private_key. Returns state of connection and error. If error is not nil, state will be false. + @param {string} host - The host to connect to. + @param {number} port - The port to connect to. + @param {string} username - The username for the connection. + @param {string} key - The private key for the connection. + @returns {boolean} - The state of the connection. + @throws {error} - The error encountered during the connection. + @example + let m = require('nuclei/ssh'); + let c = m.SSHClient(); + let result = c.ConnectWithKey('localhost', 22, 'user', 'private_key'); */ ConnectWithKey(host, port, username, key) { // implemented in go }; -}; -/** - * @typedef {object} HandshakeLog - * @description HandshakeLog is a object containing information about the ssh connection. - */ -const HandshakeLog = {}; + /** + @method + @description Run tries to open a new SSH session, then tries to execute the provided command in said session. Returns string and error. If error is not nil, state will be false. The string contains the command output. + @param {string} cmd - The command to execute. + @returns {string} - The output of the command. + @throws {error} - The error encountered during the execution of the command. + @example + let m = require('nuclei/ssh'); + let c = m.SSHClient(); + let result = c.Run('ls'); + */ + Run(cmd) { + // implemented in go + }; +}; module.exports = { SSHClient: SSHClient, diff --git a/pkg/js/libs/ssh/ssh.go b/pkg/js/libs/ssh/ssh.go index f2577332..c8908327 100644 --- a/pkg/js/libs/ssh/ssh.go +++ b/pkg/js/libs/ssh/ssh.go @@ -7,13 +7,16 @@ import ( "time" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" + errorutil "github.com/projectdiscovery/utils/errors" "github.com/zmap/zgrab2/lib/ssh" ) // SSHClient is a client for SSH servers. // // Internally client uses github.com/zmap/zgrab2/lib/ssh driver. -type SSHClient struct{} +type SSHClient struct { + Connection *ssh.Client +} // Connect tries to connect to provided host and port // with provided username and password with ssh. @@ -25,7 +28,7 @@ func (c *SSHClient) Connect(host string, port int, username, password string) (b if err != nil { return false, err } - defer conn.Close() + c.Connection = conn return true, nil } @@ -40,7 +43,7 @@ func (c *SSHClient) ConnectWithKey(host string, port int, username, key string) if err != nil { return false, err } - defer conn.Close() + c.Connection = conn return true, nil } @@ -57,6 +60,42 @@ func (c *SSHClient) ConnectSSHInfoMode(host string, port int) (*ssh.HandshakeLog return connectSSHInfoMode(host, port) } +// Run tries to open a new SSH session, then tries to execute +// the provided command in said session +// +// Returns string and error. If error is not nil, +// state will be false +// +// The string contains the command output +func (c *SSHClient) Run(cmd string) (string, error) { + if c.Connection == nil { + return "", errorutil.New("no connection") + } + session, err := c.Connection.NewSession() + if err != nil { + return "", err + } + defer session.Close() + + data, err := session.Output(cmd) + if err != nil { + return "", err + } + + return string(data), nil +} + +// Close closes the SSH connection and destroys the client +// +// Returns the success state and error. If error is not nil, +// state will be false +func (c *SSHClient) Close() (bool, error) { + if err := c.Connection.Close(); err != nil { + return false, err + } + return true, nil +} + func connectSSHInfoMode(host string, port int) (*ssh.HandshakeLog, error) { if !protocolstate.IsHostAllowed(host) { // host is not valid according to network policy