github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/headless/engine/rules.go (about)

     1  package engine
     2  
     3  import (
     4  	"fmt"
     5  	"net/http/httputil"
     6  	"strings"
     7  
     8  	"github.com/go-rod/rod"
     9  	"github.com/go-rod/rod/lib/proto"
    10  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate"
    11  )
    12  
    13  // routingRuleHandler handles proxy rule for actions related to request/response modification
    14  func (p *Page) routingRuleHandler(ctx *rod.Hijack) {
    15  	// usually browsers don't use chunked transfer encoding, so we set the content-length nevertheless
    16  	ctx.Request.Req().ContentLength = int64(len(ctx.Request.Body()))
    17  	for _, rule := range p.rules {
    18  		if rule.Part != "request" {
    19  			continue
    20  		}
    21  
    22  		switch rule.Action {
    23  		case ActionSetMethod:
    24  			rule.Do(func() {
    25  				ctx.Request.Req().Method = rule.Args["method"]
    26  			})
    27  		case ActionAddHeader:
    28  			ctx.Request.Req().Header.Add(rule.Args["key"], rule.Args["value"])
    29  		case ActionSetHeader:
    30  			ctx.Request.Req().Header.Set(rule.Args["key"], rule.Args["value"])
    31  		case ActionDeleteHeader:
    32  			ctx.Request.Req().Header.Del(rule.Args["key"])
    33  		case ActionSetBody:
    34  			body := rule.Args["body"]
    35  			ctx.Request.Req().ContentLength = int64(len(body))
    36  			ctx.Request.SetBody(body)
    37  		}
    38  	}
    39  
    40  	if p.options.CookieReuse {
    41  		// each http request is performed via the native go http client
    42  		// we first inject the shared cookies
    43  		if cookies := p.input.CookieJar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
    44  			p.instance.browser.httpclient.Jar.SetCookies(ctx.Request.URL(), cookies)
    45  		}
    46  	}
    47  
    48  	// perform the request
    49  	_ = ctx.LoadResponse(p.instance.browser.httpclient, true)
    50  
    51  	if p.options.CookieReuse {
    52  		// retrieve the updated cookies from the native http client and inject them into the shared cookie jar
    53  		// keeps existing one if not present
    54  		if cookies := p.instance.browser.httpclient.Jar.Cookies(ctx.Request.URL()); len(cookies) > 0 {
    55  			p.input.CookieJar.SetCookies(ctx.Request.URL(), cookies)
    56  		}
    57  	}
    58  
    59  	for _, rule := range p.rules {
    60  		if rule.Part != "response" {
    61  			continue
    62  		}
    63  
    64  		switch rule.Action {
    65  		case ActionAddHeader:
    66  			ctx.Response.Headers().Add(rule.Args["key"], rule.Args["value"])
    67  		case ActionSetHeader:
    68  			ctx.Response.Headers().Set(rule.Args["key"], rule.Args["value"])
    69  		case ActionDeleteHeader:
    70  			ctx.Response.Headers().Del(rule.Args["key"])
    71  		case ActionSetBody:
    72  			body := rule.Args["body"]
    73  			ctx.Response.Headers().Set("Content-Length", fmt.Sprintf("%d", len(body)))
    74  			ctx.Response.SetBody(rule.Args["body"])
    75  		}
    76  	}
    77  
    78  	// store history
    79  	req := ctx.Request.Req()
    80  	var rawReq string
    81  	if raw, err := httputil.DumpRequestOut(req, true); err == nil {
    82  		rawReq = string(raw)
    83  	}
    84  
    85  	// attempts to rebuild the response
    86  	var rawResp strings.Builder
    87  	respPayloads := ctx.Response.Payload()
    88  	if respPayloads != nil {
    89  		rawResp.WriteString(fmt.Sprintf("HTTP/1.1 %d %s\n", respPayloads.ResponseCode, respPayloads.ResponsePhrase))
    90  		for _, header := range respPayloads.ResponseHeaders {
    91  			rawResp.WriteString(header.Name + ": " + header.Value + "\n")
    92  		}
    93  		rawResp.WriteString("\n")
    94  		rawResp.WriteString(ctx.Response.Body())
    95  	}
    96  
    97  	// dump request
    98  	historyData := HistoryData{
    99  		RawRequest:  rawReq,
   100  		RawResponse: rawResp.String(),
   101  	}
   102  	p.addToHistory(historyData)
   103  }
   104  
   105  // routingRuleHandlerNative handles native proxy rule
   106  func (p *Page) routingRuleHandlerNative(e *proto.FetchRequestPaused) error {
   107  	// ValidateNFailRequest validates if Local file access is enabled
   108  	// and local network access is enables if not it will fail the request
   109  	// that don't match the rules
   110  	if err := protocolstate.ValidateNFailRequest(p.page, e); err != nil {
   111  		return err
   112  	}
   113  	body, _ := FetchGetResponseBody(p.page, e)
   114  	headers := make(map[string][]string)
   115  	for _, h := range e.ResponseHeaders {
   116  		headers[h.Name] = []string{h.Value}
   117  	}
   118  
   119  	var statusCode int
   120  	if e.ResponseStatusCode != nil {
   121  		statusCode = *e.ResponseStatusCode
   122  	}
   123  
   124  	// attempts to rebuild request
   125  	var rawReq strings.Builder
   126  	rawReq.WriteString(fmt.Sprintf("%s %s %s\n", e.Request.Method, e.Request.URL, "HTTP/1.1"))
   127  	for _, header := range e.Request.Headers {
   128  		rawReq.WriteString(fmt.Sprintf("%s\n", header.String()))
   129  	}
   130  	if e.Request.HasPostData {
   131  		rawReq.WriteString(fmt.Sprintf("\n%s\n", e.Request.PostData))
   132  	}
   133  
   134  	// attempts to rebuild the response
   135  	var rawResp strings.Builder
   136  	rawResp.WriteString(fmt.Sprintf("HTTP/1.1 %d %s\n", statusCode, e.ResponseStatusText))
   137  	for _, header := range e.ResponseHeaders {
   138  		rawResp.WriteString(header.Name + ": " + header.Value + "\n")
   139  	}
   140  	rawResp.WriteString("\n")
   141  	rawResp.Write(body)
   142  
   143  	// dump request
   144  	historyData := HistoryData{
   145  		RawRequest:  rawReq.String(),
   146  		RawResponse: rawResp.String(),
   147  	}
   148  	p.addToHistory(historyData)
   149  
   150  	return FetchContinueRequest(p.page, e)
   151  }