Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions cmd/nuclei/main_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ func runEnumBenchmark(b *testing.B, options *types.Options) {

func BenchmarkRunEnumeration(b *testing.B) {
// Default case: run enumeration with default options == all nuclei-templates
// b.Run("Default", func(b *testing.B) {
// options := getDefaultOptions()
// options.Targets = []string{targetURL}
b.Run("Default", func(b *testing.B) {
options := getDefaultOptions()
options.Targets = []string{targetURL}

// runEnumBenchmark(b, options)
// })
runEnumBenchmark(b, options)
})

// Case: https://github.com/projectdiscovery/nuclei/pull/6258
b.Run("Multiproto", func(b *testing.B) {
Expand All @@ -132,4 +132,53 @@ func BenchmarkRunEnumeration(b *testing.B) {

runEnumBenchmark(b, options)
})

// Case: https://github.com/projectdiscovery/nuclei/issues/6263
b.Run("Flow", func(b *testing.B) {
options := getDefaultOptions()
options.Targets = []string{
"https://google.com",
"https://youtube.com",
"https://facebook.com",
"https://baidu.com",
"https://wikipedia.org",
"https://qq.com",
"https://taobao.com",
"https://yahoo.com",
"https://tmall.com",
"https://amazon.com",
"https://twitter.com",
"https://sohu.com",
"https://live.com",
"https://jd.com",
"https://vk.com",
"https://instagram.com",
"https://sina.com.cn",
"https://weibo.com",
"https://reddit.com",
"https://login.tmall.com",
"https://360.cn",
"https://yandex.ru",
"https://linkedin.com",
"https://blogspot.com",
"https://netflix.com",
"https://twitch.tv",
"https://whatsapp.com",
"https://yahoo.co.jp",
"https://csdn.net",
"https://wordcamp.org",
}

b.Run("Local-Scoping", func(b *testing.B) {
options.Templates = []string{"./cmd/nuclei/testdata/benchmark/flow/wordpress-readme-local-scoping.yaml"}

runEnumBenchmark(b, options)
})

b.Run("Namespacing", func(b *testing.B) {
options.Templates = []string{"./cmd/nuclei/testdata/benchmark/flow/wordpress-readme-namespacing.yaml"}

runEnumBenchmark(b, options)
})
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
id: wordpress_readme-scoping

info:
name: WordPress readme detection
author: nisay759
severity: info
tags: readme,wordpress,themes,plugins

flow: |
// get imports from website's root
http(1);

let uniq = Dedupe();

// get unique webroot of each import
for (let path of iterate(template["imports"])) {
let pathArray = path.split("/");
let rootIdx = pathArray.indexOf("wp-content") + 3;
let finalUrl = pathArray.slice(0, rootIdx).join("/");
uniq.Add(finalUrl);
}

// for each import, look for readme
for (let url of iterate(uniq.Values())) {
set("target", url);
http(2);
}

http:
# http(1) - Query web root and extract themes/plugins imports
- method: GET
redirects: true
path:
- "{{BaseURL}}"

extractors:
- type: xpath
name: "imports"
internal: true
xpath:
- "/html/*/script/@src[contains(.,'wp-content/plugins/')]"
- "/html/*/script/@src[contains(.,'wp-content/themes/')]"
- "/html/*/style/@src[contains(.,'wp-content/themes/')]"
- "/html/*/img/@src[contains(.,'wp-content/themes/')]"

# http(2) - Given a theme/plugin "target", look for Readme file
- method: GET
path:
- "{{target}}/README.md"
- "{{target}}/README.txt"

stop-at-first-match: true
matchers:
- type: status
status:
- 200
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
id: wordpress_readme-namespacing

info:
name: WordPress readme detection
author: nisay759
severity: info
tags: readme,wordpress,themes,plugins

flow: |
// get imports from website's root
http(1);

template["{{BaseURL}}_results"] = Dedupe();

// get unique webroot of each import
for (let path of iterate(template["imports"])) {
let pathArray = path.split("/");
let rootIdx = pathArray.indexOf("wp-content") + 3;
let finalUrl = pathArray.slice(0, rootIdx).join("/");
template["{{BaseURL}}_results"].Add(finalUrl);
}

// for each import, look for readme
for (let url of iterate(template["{{BaseURL}}_results"].Values())) {
set("target", url);
http(2);
}

http:
# http(1) - Query web root and extract themes/plugins imports
- method: GET
redirects: true
path:
- "{{BaseURL}}"

extractors:
- type: xpath
name: "imports"
internal: true
xpath:
- "/html/*/script/@src[contains(.,'wp-content/plugins/')]"
- "/html/*/script/@src[contains(.,'wp-content/themes/')]"
- "/html/*/style/@src[contains(.,'wp-content/themes/')]"
- "/html/*/img/@src[contains(.,'wp-content/themes/')]"

# http(2) - Given a theme/plugin "target", look for Readme file
- method: GET
path:
- "{{target}}/README.md"
- "{{target}}/README.txt"

stop-at-first-match: true
matchers:
- type: status
status:
- 200
14 changes: 13 additions & 1 deletion pkg/tmplexec/flow/flow_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,19 @@ func (f *FlowExecutor) Compile() error {
}
}
}
return runtime.ToValue(f.requestExecutor(runtime, reqMap, opts))

result := f.requestExecutor(runtime, reqMap, opts)
// NOTE(dwisiswant0): After exec of a proto, we need to update the
// template var in the JS runtime. This is because the template obj
// is a copy of the template context, and any changes made to the
// context during proto exec (ex. extractors) will not be reflected
// in the JS runtime otherwise.
tmplObj := f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll()
if tmplObj == nil {
tmplObj = make(map[string]any)
}
_ = runtime.Set("template", tmplObj)
return runtime.ToValue(result)
}
}
return nil
Expand Down
37 changes: 9 additions & 28 deletions pkg/tmplexec/flow/vm.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package flow

import (
"context"
"reflect"
"sync"

"github.com/dop251/goja"
"github.com/logrusorgru/aurora"
Expand All @@ -13,40 +11,23 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow/builtin"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/utils/sync/sizedpool"
)

var jsOnce sync.Once

// js runtime pool using sync.Pool
var gojapool = &sync.Pool{
New: func() interface{} {
runtime := protocolstate.NewJSRuntime()
registerBuiltins(runtime)
return runtime
},
}

var sizedgojapool *sizedpool.SizedPool[*goja.Runtime]

// GetJSRuntime returns a new JS runtime from pool
// GetJSRuntime returns a new JS runtime.
//
// A new runtime is created for each call to avoid state leakage between
// executions.
func GetJSRuntime(opts *types.Options) *goja.Runtime {
jsOnce.Do(func() {
if opts.JsConcurrency < 100 {
opts.JsConcurrency = 100
}
sizedgojapool, _ = sizedpool.New[*goja.Runtime](
sizedpool.WithPool[*goja.Runtime](gojapool),
sizedpool.WithSize[*goja.Runtime](int64(opts.JsConcurrency)),
)
})
runtime, _ := sizedgojapool.Get(context.TODO())
runtime := protocolstate.NewJSRuntime()
registerBuiltins(runtime)
return runtime
}

// PutJSRuntime returns a JS runtime to pool
//
// Deprecated: This function is a no-op and does not do anything.
func PutJSRuntime(runtime *goja.Runtime) {
sizedgojapool.Put(runtime)
// Do nothing.
}

func registerBuiltins(runtime *goja.Runtime) {
Expand Down
19 changes: 19 additions & 0 deletions pkg/tmplexec/flow/vm_benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package flow_test

import (
"testing"

"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)

func BenchmarkGetJSRuntime(b *testing.B) {
opts := types.DefaultOptions()

b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
runtime := flow.GetJSRuntime(opts)
flow.PutJSRuntime(runtime)
}
}
Loading