Merge pull request #1432 from projectdiscovery/issue-1312-headless-matchers

Adding support for navigation history to matchers
dev
Sandeep Singh 2021-12-30 18:18:00 +05:30 committed by GitHub
commit b0f16b724c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 71 additions and 8 deletions

View File

@ -2,6 +2,8 @@ package engine
import ( import (
"net/url" "net/url"
"strings"
"sync"
"time" "time"
"github.com/go-rod/rod" "github.com/go-rod/rod"
@ -10,10 +12,18 @@ import (
// Page is a single page in an isolated browser instance // Page is a single page in an isolated browser instance
type Page struct { type Page struct {
page *rod.Page page *rod.Page
rules []requestRule rules []requestRule
instance *Instance instance *Instance
router *rod.HijackRouter router *rod.HijackRouter
historyMutex *sync.RWMutex
History []HistoryData
}
// HistoryData contains the page request/response pairs
type HistoryData struct {
RawRequest string
RawResponse string
} }
// Run runs a list of actions by creating a new page in the browser. // Run runs a list of actions by creating a new page in the browser.
@ -30,7 +40,7 @@ func (i *Instance) Run(baseURL *url.URL, actions []*Action, timeout time.Duratio
} }
} }
createdPage := &Page{page: page, instance: i} createdPage := &Page{page: page, instance: i, historyMutex: &sync.RWMutex{}}
router := page.HijackRequests() router := page.HijackRequests()
if routerErr := router.Add("*", "", createdPage.routingRuleHandler); routerErr != nil { if routerErr := router.Add("*", "", createdPage.routingRuleHandler); routerErr != nil {
return nil, nil, routerErr return nil, nil, routerErr
@ -81,3 +91,24 @@ func (p *Page) URL() string {
} }
return info.URL return info.URL
} }
// DumpHistory returns the full page navigation history
func (p *Page) DumpHistory() string {
p.historyMutex.RLock()
defer p.historyMutex.RUnlock()
var historyDump strings.Builder
for _, historyData := range p.History {
historyDump.WriteString(historyData.RawRequest)
historyDump.WriteString(historyData.RawResponse)
}
return historyDump.String()
}
// addToHistory adds a request/response pair to the page history
func (p *Page) addToHistory(historyData HistoryData) {
p.historyMutex.Lock()
defer p.historyMutex.Unlock()
p.History = append(p.History, historyData)
}

View File

@ -2,6 +2,8 @@ package engine
import ( import (
"fmt" "fmt"
"net/http/httputil"
"strings"
"github.com/go-rod/rod" "github.com/go-rod/rod"
) )
@ -10,7 +12,6 @@ import (
func (p *Page) routingRuleHandler(ctx *rod.Hijack) { func (p *Page) routingRuleHandler(ctx *rod.Hijack) {
// usually browsers don't use chunked transfer encoding, so we set the content-length nevertheless // usually browsers don't use chunked transfer encoding, so we set the content-length nevertheless
ctx.Request.Req().ContentLength = int64(len(ctx.Request.Body())) ctx.Request.Req().ContentLength = int64(len(ctx.Request.Body()))
for _, rule := range p.rules { for _, rule := range p.rules {
if rule.Part != "request" { if rule.Part != "request" {
continue continue
@ -51,4 +52,32 @@ func (p *Page) routingRuleHandler(ctx *rod.Hijack) {
ctx.Response.SetBody(rule.Args["body"]) ctx.Response.SetBody(rule.Args["body"])
} }
} }
// store history
req := ctx.Request.Req()
var rawReq string
if raw, err := httputil.DumpRequestOut(req, true); err == nil {
rawReq = string(raw)
}
// attempts to rebuild the response
var rawResp strings.Builder
respPayloads := ctx.Response.Payload()
if respPayloads != nil {
rawResp.WriteString("HTTP/1.1 ")
rawResp.WriteString(fmt.Sprint(respPayloads.ResponseCode))
rawResp.WriteString(" " + respPayloads.ResponsePhrase + "+\n")
for _, header := range respPayloads.ResponseHeaders {
rawResp.WriteString(header.Name + ": " + header.Value + "\n")
}
rawResp.WriteString("\n")
rawResp.WriteString(ctx.Response.Body())
}
// dump request
historyData := HistoryData{
RawRequest: rawReq,
RawResponse: rawResp.String(),
}
p.addToHistory(historyData)
} }

View File

@ -54,6 +54,8 @@ func (request *Request) getMatchPart(part string, data output.InternalEvent) (st
switch part { switch part {
case "body", "resp", "": case "body", "resp", "":
part = "data" part = "data"
case "history":
part = "history"
} }
item, ok := data[part] item, ok := data[part]
@ -66,12 +68,13 @@ func (request *Request) getMatchPart(part string, data output.InternalEvent) (st
} }
// responseToDSLMap converts a headless response to a map for use in DSL matching // responseToDSLMap converts a headless response to a map for use in DSL matching
func (request *Request) responseToDSLMap(resp, req, host, matched string) output.InternalEvent { func (request *Request) responseToDSLMap(resp, req, host, matched string, history string) output.InternalEvent {
return output.InternalEvent{ return output.InternalEvent{
"host": host, "host": host,
"matched": matched, "matched": matched,
"req": req, "req": req,
"data": resp, "data": resp,
"history": history,
"type": request.Type().String(), "type": request.Type().String(),
"template-id": request.options.TemplateID, "template-id": request.options.TemplateID,
"template-info": request.options.TemplateInfo, "template-info": request.options.TemplateInfo,

View File

@ -66,7 +66,7 @@ func (request *Request) ExecuteWithResults(inputURL string, metadata, previous o
if err == nil { if err == nil {
responseBody, _ = html.HTML() responseBody, _ = html.HTML()
} }
outputEvent := request.responseToDSLMap(responseBody, reqBuilder.String(), inputURL, inputURL) outputEvent := request.responseToDSLMap(responseBody, reqBuilder.String(), inputURL, inputURL, page.DumpHistory())
for k, v := range out { for k, v := range out {
outputEvent[k] = v outputEvent[k] = v
} }