nuclei/v2/pkg/js/compiler/compiler.go
Tarun Koyalwar 39d094075b
add 'fs' module to javascript protocol (#4156)
* rebase js-layer PR from @ice3man543

* package restructuring

* working

* fix duplicated event & matcher status

* fix lint error

* fix response field

* add new functions

* multiple minor improvements

* fix incorrect stats in js protocol

* sort output metadata in cli

* remove temp files

* remove dead code

* add unit and integration test

* fix lint error

* add jsdoclint using llm

* fix error in test

* add js lint using llm

* generate docs of libs

* llm lint

* remove duplicated docs

* update generated docs

* update prompt in doclint

* update docs

* temp disable version check test

* fix unit test and add retry

* fix panic in it

* update and move jsdocs

* updated jsdocs

* update docs

* update container platform in test

* dir restructure and adding docs

* add api_reference and remove markdown docs

* fix imports

* add javascript design and contribution docs

* add js protocol documentation

* update integration test and docs

* update doc ext mdx->md

* minor update to docs

* new integration test and more

* move go libs and add docs

* gen new net docs and more

* final docs update

* add new devtool

* use fastdialer

* fix build fail

* use fastdialer + network sandbox support

* add reserved keyword 'Port'

* update Port to new syntax

* misc update

* always enable templatectx in js protocol

* move docs to 'js-proto-docs' repo

* remove scrapefuncs binary

* add fs library

* add fs module

* add init code block and 'updatePayload'

* use go native func for isPortOpen

* docgen improvements + 'fs' module docs

* update func signature and more

* prompt improvements

* fix inconsitencies in jsdocs

* remove debug statements

---------

Co-authored-by: sandeep <8293321+ehsandeep@users.noreply.github.com>
2023-09-26 16:55:25 +05:30

216 lines
7.2 KiB
Go

// Package compiler provides a compiler for the goja runtime.
package compiler
import (
"runtime/debug"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/console"
"github.com/dop251/goja_nodejs/require"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/projectdiscovery/gologger"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libbytes"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libfs"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libikev2"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libkerberos"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libldap"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libmssql"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libmysql"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libnet"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/liboracle"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libpop3"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libpostgres"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/librdp"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libredis"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/librsync"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libsmb"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libsmtp"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libssh"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libstructs"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libtelnet"
_ "github.com/projectdiscovery/nuclei/v2/pkg/js/generated/go/libvnc"
"github.com/projectdiscovery/nuclei/v2/pkg/js/global"
"github.com/projectdiscovery/nuclei/v2/pkg/js/libs/goconsole"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
)
// Compiler provides a runtime to execute goja runtime
// based javascript scripts efficiently while also
// providing them access to custom modules defined in libs/.
type Compiler struct {
registry *require.Registry
}
// New creates a new compiler for the goja runtime.
func New() *Compiler {
registry := new(require.Registry) // this can be shared by multiple runtimes
// autoregister console node module with default printer it uses gologger backend
require.RegisterNativeModule(console.ModuleName, console.RequireWithPrinter(goconsole.NewGoConsolePrinter()))
return &Compiler{registry: registry}
}
// ExecuteOptions provides options for executing a script.
type ExecuteOptions struct {
// Pool specifies whether to use a pool of goja runtimes
// Can be used to speedup execution but requires
// the script to not make any global changes.
Pool bool
// CaptureOutput specifies whether to capture the output
// of the script execution.
CaptureOutput bool
// CaptureVariables specifies the variables to capture
// from the script execution.
CaptureVariables []string
// Callback can be used to register new runtime helper functions
// ex: export etc
Callback func(runtime *goja.Runtime) error
}
// ExecuteArgs is the arguments to pass to the script.
type ExecuteArgs struct {
Args map[string]interface{} //these are protocol variables
TemplateCtx map[string]interface{} // templateCtx contains template scoped variables
}
// NewExecuteArgs returns a new execute arguments.
func NewExecuteArgs() *ExecuteArgs {
return &ExecuteArgs{
Args: make(map[string]interface{}),
TemplateCtx: make(map[string]interface{}),
}
}
// ExecuteResult is the result of executing a script.
type ExecuteResult map[string]interface{}
func NewExecuteResult() ExecuteResult {
return make(map[string]interface{})
}
// GetSuccess returns whether the script was successful or not.
func (e ExecuteResult) GetSuccess() bool {
val, ok := e["success"].(bool)
if !ok {
return false
}
return val
}
// Execute executes a script with the default options.
func (c *Compiler) Execute(code string, args *ExecuteArgs) (ExecuteResult, error) {
return c.ExecuteWithOptions(code, args, &ExecuteOptions{})
}
// VM returns a new goja runtime for the compiler.
func (c *Compiler) VM() *goja.Runtime {
runtime := c.newRuntime(false)
c.registerHelpersForVM(runtime)
return runtime
}
// ExecuteWithOptions executes a script with the provided options.
func (c *Compiler) ExecuteWithOptions(code string, args *ExecuteArgs, opts *ExecuteOptions) (ExecuteResult, error) {
defer func() {
if err := recover(); err != nil {
gologger.Error().Msgf("Recovered panic %s %v: %v", code, args, err)
gologger.Verbose().Msgf("%s", debug.Stack())
return
}
}()
if opts == nil {
opts = &ExecuteOptions{}
}
runtime := c.newRuntime(opts.Pool)
c.registerHelpersForVM(runtime)
// register runtime functions if any
if opts.Callback != nil {
if err := opts.Callback(runtime); err != nil {
return nil, err
}
}
if args == nil {
args = NewExecuteArgs()
}
for k, v := range args.Args {
_ = runtime.Set(k, v)
}
if args.TemplateCtx == nil {
args.TemplateCtx = make(map[string]interface{})
}
// merge all args into templatectx
args.TemplateCtx = generators.MergeMaps(args.TemplateCtx, args.Args)
_ = runtime.Set("template", args.TemplateCtx)
results, err := runtime.RunString(code)
if err != nil {
return nil, err
}
captured := results.Export()
if opts.CaptureOutput {
return convertOutputToResult(captured)
}
if len(opts.CaptureVariables) > 0 {
return c.captureVariables(runtime, opts.CaptureVariables)
}
// success is true by default . since js throws errors on failure
// hence output result is always success
return ExecuteResult{"response": captured, "success": results.ToBoolean()}, nil
}
// captureVariables captures the variables from the runtime.
func (c *Compiler) captureVariables(runtime *goja.Runtime, variables []string) (ExecuteResult, error) {
results := make(ExecuteResult, len(variables))
for _, variable := range variables {
value := runtime.Get(variable)
if value == nil {
continue
}
results[variable] = value.Export()
}
return results, nil
}
func convertOutputToResult(output interface{}) (ExecuteResult, error) {
marshalled, err := jsoniter.Marshal(output)
if err != nil {
return nil, errors.Wrap(err, "could not marshal output")
}
var outputMap map[string]interface{}
if err := jsoniter.Unmarshal(marshalled, &outputMap); err != nil {
var v interface{}
if unmarshalErr := jsoniter.Unmarshal(marshalled, &v); unmarshalErr != nil {
return nil, unmarshalErr
}
outputMap = map[string]interface{}{"output": v}
return outputMap, nil
}
return outputMap, nil
}
// newRuntime creates a new goja runtime
// TODO: Add support for runtime reuse for helper functions
func (c *Compiler) newRuntime(reuse bool) *goja.Runtime {
return goja.New()
}
// registerHelpersForVM registers all the helper functions for the goja runtime.
func (c *Compiler) registerHelpersForVM(runtime *goja.Runtime) {
_ = c.registry.Enable(runtime)
// by default import below modules every time
_ = runtime.Set("console", require.Require(runtime, console.ModuleName))
// Register embedded scripts
if err := global.RegisterNativeScripts(runtime); err != nil {
gologger.Error().Msgf("Could not register scripts: %s\n", err)
}
}