Working interactsh integration

This commit is contained in:
Ice3man543 2021-04-18 16:10:10 +05:30
parent 4c594627a9
commit 33bf306f28
5 changed files with 127 additions and 26 deletions

View File

@ -372,7 +372,10 @@ func (r *Runner) RunEnumeration() {
wgtemplates.Wait()
if r.interactsh != nil {
r.interactsh.Close()
matched := r.interactsh.Close()
if matched {
results.CAS(false, true)
}
}
r.progress.Stop()

View File

@ -65,6 +65,32 @@ type Result struct {
PayloadValues map[string]interface{}
}
// Merge merges a result structure into the other.
func (r *Result) Merge(result *Result) {
if !r.Matched && result.Matched {
r.Matched = result.Matched
}
if !r.Extracted && result.Extracted {
r.Extracted = result.Extracted
}
for k, v := range result.Matches {
r.Matches[k] = v
}
for k, v := range result.Extracts {
r.Extracts[k] = v
}
for _, v := range result.OutputExtracts {
r.OutputExtracts = append(r.OutputExtracts, v)
}
for k, v := range result.DynamicValues {
r.DynamicValues[k] = v
}
for k, v := range result.PayloadValues {
r.PayloadValues[k] = v
}
}
// MatchFunc performs matching operation for a matcher on model and returns true or false.
type MatchFunc func(data map[string]interface{}, matcher *matchers.Matcher) bool
@ -81,6 +107,7 @@ func (r *Operators) Execute(data map[string]interface{}, match MatchFunc, extrac
Extracts: make(map[string][]string),
DynamicValues: make(map[string]interface{}),
}
// Start with the extractors first and evaluate them.
for _, extractor := range r.Extractors {
var extractorResults []string

View File

@ -22,6 +22,7 @@ type Client struct {
// requests is a stored cache for interactsh-url->request-event data.
requests *ccache.Cache
matched bool
dotHostname string
eviction time.Duration
pollDuration time.Duration
@ -77,24 +78,38 @@ func New(options *Options) (*Client, error) {
pollDuration: options.PollDuration,
cooldownDuration: options.ColldownPeriod,
}
interactClient.interactsh.StartPolling(interactClient.pollDuration, func(interaction *server.Interaction) {
item := interactClient.requests.Get(interaction.UniqueID)
if item == nil {
return
}
data, ok := item.Value().(*internalRequestEvent)
data, ok := item.Value().(*RequestData)
if !ok {
return
}
data.Event.InternalEvent["interactsh-protocol"] = interaction.Protocol
data.Event.InternalEvent["interactsh-request"] = interaction.RawRequest
data.Event.InternalEvent["interactsh-response"] = interaction.RawResponse
result, matched := data.Operators.Execute(data.Event.InternalEvent, data.MatchFunc, data.ExtractFunc)
if !matched || result == nil {
return // if we don't match, return
}
interactClient.requests.Delete(interaction.UniqueID)
data.event.OperatorsResult = &operators.Result{
Matches: map[string]struct{}{strings.ToLower(interaction.Protocol): {}},
if data.Event.OperatorsResult != nil {
data.Event.OperatorsResult.Merge(result)
} else {
data.Event.OperatorsResult = result
}
data.event.Results = data.makeResultFunc(data.event)
for _, result := range data.event.Results {
data.Event.Results = data.MakeResultFunc(data.Event)
for _, result := range data.Event.Results {
result.Interaction = interaction
_ = options.Output.Write(result)
if !interactClient.matched {
interactClient.matched = true
}
options.Progress.IncrementMatched()
}
})
@ -107,12 +122,13 @@ func (c *Client) URL() string {
}
// Close closes the interactsh clients after waiting for cooldown period.
func (c *Client) Close() {
func (c *Client) Close() bool {
if c.cooldownDuration > 0 {
time.Sleep(c.cooldownDuration)
}
c.interactsh.StopPolling()
c.interactsh.Close()
return c.matched
}
// ReplaceMarkers replaces the {{interactsh-url}} placeholders to actual
@ -133,13 +149,36 @@ func (c *Client) ReplaceMarkers(data, interactshURL string) string {
// MakeResultEventFunc is a result making function for nuclei
type MakeResultEventFunc func(wrapped *output.InternalWrappedEvent) []*output.ResultEvent
type internalRequestEvent struct {
makeResultFunc MakeResultEventFunc
event *output.InternalWrappedEvent
// RequestData contains data for a request event
type RequestData struct {
MakeResultFunc MakeResultEventFunc
Event *output.InternalWrappedEvent
Operators *operators.Operators
MatchFunc operators.MatchFunc
ExtractFunc operators.ExtractFunc
}
// RequestEvent is the event for a network request sent by nuclei.
func (c *Client) RequestEvent(interactshURL string, event *output.InternalWrappedEvent, makeResult MakeResultEventFunc) {
func (c *Client) RequestEvent(interactshURL string, data *RequestData) {
id := strings.TrimSuffix(interactshURL, c.dotHostname)
c.requests.Set(id, &internalRequestEvent{makeResultFunc: makeResult, event: event}, c.eviction)
c.requests.Set(id, data, c.eviction)
}
// HasMatchers returns true if an operator has interactsh part
// matchers or extractors.
//
// Used by requests to show result or not depending on presence of interact.sh
// data part matchers.
func HasMatchers(operators *operators.Operators) bool {
for _, matcher := range operators.Matchers {
if strings.HasPrefix(matcher.Part, "interactsh-") {
return true
}
}
for _, matcher := range operators.Extractors {
if strings.HasPrefix(matcher.Part, "interactsh-") {
return true
}
}
return false
}

View File

@ -17,6 +17,7 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool"
"github.com/projectdiscovery/rawhttp"
@ -219,8 +220,16 @@ func (r *Request) ExecuteWithResults(reqURL string, dynamicValues, previous outp
gotOutput = true
dynamicValues = generators.MergeMaps(dynamicValues, event.OperatorsResult.DynamicValues)
}
if r.options.Interactsh != nil {
r.options.Interactsh.RequestEvent(interactURL, event, r.MakeResultEvent)
if interactsh.HasMatchers(r.CompiledOperators) {
if r.options.Interactsh != nil {
r.options.Interactsh.RequestEvent(interactURL, &interactsh.RequestData{
MakeResultFunc: r.MakeResultEvent,
Event: event,
Operators: r.CompiledOperators,
MatchFunc: r.Match,
ExtractFunc: r.Extract,
})
}
}
callback(event)
}, requestCount)
@ -398,14 +407,16 @@ func (r *Request) executeRequest(reqURL string, request *generatedRequest, previ
}
event := &output.InternalWrappedEvent{InternalEvent: outputEvent}
if r.CompiledOperators != nil {
var ok bool
event.OperatorsResult, ok = r.CompiledOperators.Execute(finalEvent, r.Match, r.Extract)
if ok && event.OperatorsResult != nil {
event.OperatorsResult.PayloadValues = request.meta
event.Results = r.MakeResultEvent(event)
if !interactsh.HasMatchers(r.CompiledOperators) {
if r.CompiledOperators != nil {
var ok bool
event.OperatorsResult, ok = r.CompiledOperators.Execute(finalEvent, r.Match, r.Extract)
if ok && event.OperatorsResult != nil {
event.OperatorsResult.PayloadValues = request.meta
event.Results = r.MakeResultEvent(event)
}
event.InternalEvent = outputEvent
}
event.InternalEvent = outputEvent
}
callback(event)
return nil

View File

@ -13,6 +13,7 @@ import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer"
)
@ -77,6 +78,11 @@ func (r *Request) executeAddress(actualAddress, address, input string, shouldUse
defer conn.Close()
_ = conn.SetReadDeadline(time.Now().Add(time.Duration(r.options.Options.Timeout) * time.Second))
var interactURL string
if r.options.Interactsh != nil {
interactURL = r.options.Interactsh.URL()
}
responseBuilder := &strings.Builder{}
reqBuilder := &strings.Builder{}
@ -88,6 +94,9 @@ func (r *Request) executeAddress(actualAddress, address, input string, shouldUse
case "hex":
data, err = hex.DecodeString(input.Data)
default:
if r.options.Interactsh != nil {
input.Data = r.options.Interactsh.ReplaceMarkers(input.Data, interactURL)
}
data = []byte(input.Data)
}
if err != nil {
@ -150,11 +159,23 @@ func (r *Request) executeAddress(actualAddress, address, input string, shouldUse
}
event := &output.InternalWrappedEvent{InternalEvent: outputEvent}
if r.CompiledOperators != nil {
result, ok := r.CompiledOperators.Execute(outputEvent, r.Match, r.Extract)
if ok && result != nil {
event.OperatorsResult = result
event.Results = r.MakeResultEvent(event)
if !interactsh.HasMatchers(r.CompiledOperators) {
if r.CompiledOperators != nil {
result, ok := r.CompiledOperators.Execute(outputEvent, r.Match, r.Extract)
if ok && result != nil {
event.OperatorsResult = result
event.Results = r.MakeResultEvent(event)
}
}
} else {
if r.options.Interactsh != nil {
r.options.Interactsh.RequestEvent(interactURL, &interactsh.RequestData{
MakeResultFunc: r.MakeResultEvent,
Event: event,
Operators: r.CompiledOperators,
MatchFunc: r.Match,
ExtractFunc: r.Extract,
})
}
}
callback(event)