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 <tarun@projectdiscovery.io>
dev
Valerio Casalino 2023-11-23 19:37:45 +01:00 committed by GitHub
parent 7cb03f24b2
commit ec5687e2ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 190 additions and 45 deletions

2
.gitignore vendored
View File

@ -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

View File

@ -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
}

View File

@ -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 (

View File

@ -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.
@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');
*/
const HandshakeLog = {};
Run(cmd) {
// implemented in go
};
};
module.exports = {
SSHClient: SSHClient,

View File

@ -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