Skip to content
Merged
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
22 changes: 20 additions & 2 deletions fingers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import (
"context"
"fmt"
"strings"

"github.com/chainreactors/logs"
"github.com/chainreactors/neutron/protocols"
Expand Down Expand Up @@ -293,14 +294,19 @@
rawContent = r.RawContentDraft
}

switch r.Engine {
engine := r.Engine
if engine == "fingerprinthub" && hasTag(r.Finger, xrayRouteTag) {

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / External consumer build

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag

Check failure on line 298 in fingers/config.go

View workflow job for this annotation

GitHub Actions / Go test

undefined: xrayRouteTag
engine = "xray"
}

switch engine {
case "fingerprinthub", "xray":
if rawContent != "" {
tmpl := parseTemplate(rawContent, execOpts)
if tmpl != nil {
ff.Template = tmpl
ff.RawContent = rawContent
ff.Engine = r.Engine
ff.Engine = engine
}
}
default:
Expand All @@ -317,6 +323,18 @@
return f
}

func hasTag(finger *types.Finger, tag string) bool {
if finger == nil {
return false
}
for _, t := range finger.Tags {
if strings.EqualFold(strings.TrimSpace(t), tag) {
return true
}
}
return false
}

func parseTemplate(rawYAML string, opts *protocols.ExecuterOptions) *types.Template {
tmpl := &types.Template{}
if err := yaml.Unmarshal([]byte(rawYAML), tmpl); err != nil {
Expand Down
82 changes: 82 additions & 0 deletions fingers/config_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,88 @@ func TestConfigSourceSelectionAndFiltering(t *testing.T) {
}
}

func TestMergeExportsSource1OverridesEngineToXray(t *testing.T) {
rawTemplate := `id: source1-route-test
info:
name: source1 route test
author: tester
severity: info
http:
- method: GET
path:
- "{{BaseURL}}/"
matchers:
- type: word
words:
- source1-route-marker
`

exports := []cyberhub.FingerprintExport{{
Finger: &types.Finger{
Name: "source1 route test",
Protocol: "http",
Tags: []string{"neutron", "source1"},
},
Engine: "fingerprinthub",
RawContent: rawTemplate,
}}

full := (FullFingers{}).MergeExports(exports, false)
if got := len(full.TemplateItems("fingerprinthub")); got != 0 {
t.Fatalf("fingerprinthub template count = %d, want 0 (source1 overrides to xray)", got)
}
if got := len(full.TemplateItems("xray")); got != 1 {
t.Fatalf("xray template count = %d, want 1", got)
}
}

func TestMergeExportsWithoutSource1StaysFingerprinthub(t *testing.T) {
rawTemplate := `id: no-source1-test
info:
name: no source1 test
author: tester
severity: info
http:
- method: GET
path:
- "{{BaseURL}}/"
matchers:
- type: word
words:
- marker
`

exports := []cyberhub.FingerprintExport{{
Finger: &types.Finger{
Name: "no source1 test",
Protocol: "http",
Tags: []string{"neutron"},
},
Engine: "fingerprinthub",
RawContent: rawTemplate,
}}

full := (FullFingers{}).MergeExports(exports, false)
if got := len(full.TemplateItems("fingerprinthub")); got != 1 {
t.Fatalf("fingerprinthub template count = %d, want 1", got)
}
if got := len(full.TemplateItems("xray")); got != 0 {
t.Fatalf("xray template count = %d, want 0 (no source1 tag)", got)
}
}

func TestHasTag(t *testing.T) {
if hasTag(&types.Finger{Tags: []string{"xray"}}, "source1") {
t.Fatal("xray tag should not match source1")
}
if !hasTag(&types.Finger{Tags: []string{" source1 "}}, "source1") {
t.Fatal("source1 tag with whitespace should match")
}
if hasTag(nil, "source1") {
t.Fatal("nil finger should return false")
}
}

func TestExecuteMatchTaskUsesSDKResult(t *testing.T) {
eng := newDetailTestEngine(t, NewConfig(), &types.Finger{
Name: "execute-app",
Expand Down
36 changes: 33 additions & 3 deletions fingers/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
"github.com/chainreactors/fingers/alias"
"github.com/chainreactors/fingers/common"
"github.com/chainreactors/fingers/favicon"
fingersEngine "github.com/chainreactors/fingers/fingers"
"github.com/chainreactors/fingers/fingerprinthub"
fingersEngine "github.com/chainreactors/fingers/fingers"
"github.com/chainreactors/fingers/resources"
"github.com/chainreactors/fingers/xray"
"github.com/chainreactors/logs"
Expand Down Expand Up @@ -503,6 +503,7 @@ func (e *Engine) scanHTTPTarget(ctx *Context, url string, level int) *TargetResu
if parsedURL.Port != "" && parsedURL.Port != "80" && parsedURL.Port != "443" {
baseURL += ":" + parsedURL.Port
}
templateBaseURL := activeTemplateBaseURL(baseURL, parsedURL.Path)

client := ctx.GetClient()

Expand Down Expand Up @@ -544,6 +545,7 @@ func (e *Engine) scanHTTPTarget(ctx *Context, url string, level int) *TargetResu
if transport == nil {
transport = http.DefaultTransport
}
transport = wrapRedirectResolvingTransport(transport)

activeCallback := func(frame *common.Framework, vuln *common.Vuln) {
if frame != nil {
Expand All @@ -556,13 +558,13 @@ func (e *Engine) scanHTTPTarget(ctx *Context, url string, level int) *TargetResu

if fpHub := e.engine.FingerPrintHub(); fpHub != nil {
safeHTTPActiveMatch("fingerprinthub", func() {
fpHub.HTTPActiveMatch(baseURL, level, transport, activeCallback)
fpHub.HTTPActiveMatch(templateBaseURL, level, transport, activeCallback)
})
}

if xrayEng := e.engine.Xray(); xrayEng != nil {
safeHTTPActiveMatch("xray", func() {
xrayEng.HTTPActiveMatch(baseURL, level, transport, activeCallback)
xrayEng.HTTPActiveMatch(templateBaseURL, level, transport, activeCallback)
})
}

Expand Down Expand Up @@ -881,3 +883,31 @@ func pathJoin(base, append string) string {

return base + append
}

func activeTemplateBaseURL(baseURL, targetPath string) string {
if targetPath == "" || targetPath == "/" {
return baseURL
}
return baseURL + strings.TrimRight(targetPath, "/")
}

type redirectResolvingTransport struct {
base http.RoundTripper
}

func wrapRedirectResolvingTransport(base http.RoundTripper) http.RoundTripper {
if base == nil {
base = http.DefaultTransport
}
return redirectResolvingTransport{base: base}
}

func (t redirectResolvingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
client := &http.Client{
Transport: t.base,
}
clone := req.Clone(req.Context())
clone.Body = req.Body
clone.GetBody = req.GetBody
return client.Do(clone)
}
3 changes: 1 addition & 2 deletions neutron/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,9 @@ func (e *Engine) compileOptions() *protocols.ExecuterOptions {

func (e *Engine) compileTemplates(allTemplates []*types.Template) []*types.Template {
compiledTemplates := make([]*types.Template, 0, len(allTemplates))
options := e.compileOptions()

for _, t := range allTemplates {
if err := t.Compile(options); err != nil {
if err := t.Compile(e.compileOptions()); err != nil {
continue
}
compiledTemplates = append(compiledTemplates, t)
Expand Down
66 changes: 66 additions & 0 deletions neutron/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package neutron

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/chainreactors/sdk/pkg/types"
"gopkg.in/yaml.v3"
)

func TestConfigWithCapacity(t *testing.T) {
Expand Down Expand Up @@ -61,3 +64,66 @@ func TestNoCapacityByDefault(t *testing.T) {
t.Fatal("engine should have no capacity by default")
}
}

func TestCompileTemplatesIsolatesTemplateVariables(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("path=" + r.URL.Path))
}))
defer server.Close()

first := parseTemplateForTest(t, `id: first-template
info:
name: First Template
severity: info
variables:
token: first
http:
- method: GET
path:
- "{{BaseURL}}/{{token}}"
matchers:
- type: word
words:
- "path=/first"
`)
second := parseTemplateForTest(t, `id: second-template
info:
name: Second Template
severity: info
variables:
token: second
http:
- method: GET
path:
- "{{BaseURL}}/{{token}}"
matchers:
- type: word
words:
- "path=/second"
`)

engine := &Engine{config: NewConfig()}
compiled := engine.compileTemplates([]*types.Template{first, second})
if len(compiled) != 2 {
t.Fatalf("compiled templates = %d, want 2", len(compiled))
}

for _, tpl := range compiled {
result, err := tpl.Execute(server.URL, nil)
if err != nil {
t.Fatalf("execute %s: %v", tpl.Id, err)
}
if result == nil || !result.Matched {
t.Fatalf("expected %s to match its own variable-expanded path", tpl.Id)
}
}
}

func parseTemplateForTest(t *testing.T, raw string) *types.Template {
t.Helper()
var tpl types.Template
if err := yaml.Unmarshal([]byte(raw), &tpl); err != nil {
t.Fatalf("parse template: %v", err)
}
return &tpl
}
2 changes: 2 additions & 0 deletions pkg/cyberhub/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type fingerprintListResponse struct {

type pocResponse struct {
*types.Template `json:",inline" yaml:",inline"`
RawContent string `json:"raw_content,omitempty" yaml:"raw_content,omitempty"`
RawContentDraft string `json:"raw_content_draft,omitempty" yaml:"raw_content_draft,omitempty"`
}

type pocListResponse struct {
Expand Down
66 changes: 66 additions & 0 deletions pkg/cyberhub/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cyberhub
import (
"net/url"
"testing"

"github.com/chainreactors/sdk/pkg/types"
)

func TestApplyFilterParams_DedupTags(t *testing.T) {
Expand Down Expand Up @@ -152,3 +154,67 @@ func TestApplyDefaultPOCStatus_WithReviewStatus(t *testing.T) {
t.Fatalf("expected no default status when review_status set, got %q", params.Get("status"))
}
}

func TestPOCTemplateFromResponsePrefersRawContent(t *testing.T) {
resp := pocResponse{
Template: &types.Template{Id: "json-template"},
RawContent: `id: raw-template
info:
name: Raw Template
variables:
s1: '{{rand_int(1000, 9999)}}'
http:
- method: GET
path:
- '{{BaseURL}}/check?s={{s1}}'
`,
}

tpl := pocTemplateFromResponse(resp, false)
if tpl == nil {
t.Fatal("expected template")
}
if tpl.Id != "raw-template" {
t.Fatalf("expected raw template id, got %q", tpl.Id)
}
if tpl.Variables.Len() == 0 {
t.Fatal("expected variables parsed from raw content")
}
}

func TestPOCTemplateFromResponseHonorsDraftRawContent(t *testing.T) {
resp := pocResponse{
RawContent: `id: active-template
info:
name: Active Template
http:
- method: GET
`,
RawContentDraft: `id: draft-template
info:
name: Draft Template
http:
- method: POST
`,
}

tpl := pocTemplateFromResponse(resp, true)
if tpl == nil {
t.Fatal("expected template")
}
if tpl.Id != "draft-template" {
t.Fatalf("expected draft template id, got %q", tpl.Id)
}
}

func TestPOCTemplateFromResponseFallsBackToStructuredTemplate(t *testing.T) {
resp := pocResponse{Template: &types.Template{Id: "json-template"}}

tpl := pocTemplateFromResponse(resp, true)
if tpl == nil {
t.Fatal("expected template")
}
if tpl.Id != "json-template" {
t.Fatalf("expected structured template id, got %q", tpl.Id)
}
}
Loading
Loading