Skip to content

Commit f4b78e7

Browse files
committed
fix(interactsh): dedupe interactions results
Add per-interaction deduplication cache to avoid processing the same interaction multiple times, which can occur due to DNS case variations or repeated polling. Signed-off-by: Dwi Siswanto <git@dw1.io>
1 parent bea7eb3 commit f4b78e7

File tree

1 file changed

+49
-8
lines changed

1 file changed

+49
-8
lines changed

pkg/protocols/common/interactsh/interactsh.go

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ type Client struct {
4343
matchedTemplates gcache.Cache[string, bool]
4444
// interactshURLs is a stored cache to track multiple interactsh markers
4545
interactshURLs gcache.Cache[string, string]
46+
// processedInteractions is a cache for interactions already processed
47+
processedInteractions gcache.Cache[string, bool]
4648

4749
eviction time.Duration
4850
pollDuration time.Duration
@@ -61,16 +63,18 @@ func New(options *Options) (*Client, error) {
6163
interactionsCache := gcache.New[string, []*server.Interaction](defaultMaxInteractionsCount).LRU().Build()
6264
matchedTemplateCache := gcache.New[string, bool](defaultMaxInteractionsCount).LRU().Build()
6365
interactshURLCache := gcache.New[string, string](defaultMaxInteractionsCount).LRU().Build()
66+
processedInteractionsCache := gcache.New[string, bool](defaultMaxInteractionsCount).LRU().Build()
6467

6568
interactClient := &Client{
66-
eviction: options.Eviction,
67-
interactions: interactionsCache,
68-
matchedTemplates: matchedTemplateCache,
69-
interactshURLs: interactshURLCache,
70-
options: options,
71-
requests: requestsCache,
72-
pollDuration: options.PollDuration,
73-
cooldownDuration: options.CooldownPeriod,
69+
eviction: options.Eviction,
70+
interactions: interactionsCache,
71+
matchedTemplates: matchedTemplateCache,
72+
interactshURLs: interactshURLCache,
73+
processedInteractions: processedInteractionsCache,
74+
options: options,
75+
requests: requestsCache,
76+
pollDuration: options.PollDuration,
77+
cooldownDuration: options.CooldownPeriod,
7478
}
7579
return interactClient, nil
7680
}
@@ -165,6 +169,10 @@ func (c *Client) processInteractionForRequest(interaction *server.Interaction, d
165169
data.Event.InternalEvent["interactsh_ip"] = interaction.RemoteAddress
166170
data.Event.Unlock()
167171

172+
if !c.shouldProcessInteraction(interaction) {
173+
return false
174+
}
175+
168176
if data.Operators != nil {
169177
result, matched = data.Operators.Execute(data.Event.InternalEvent, data.MatchFunc, data.ExtractFunc, c.options.Debug || c.options.DebugRequest || c.options.DebugResponse)
170178
} else {
@@ -276,6 +284,7 @@ func (c *Client) Close() bool {
276284
c.interactions.Purge()
277285
c.matchedTemplates.Purge()
278286
c.interactshURLs.Purge()
287+
c.processedInteractions.Purge()
279288

280289
return c.matched.Load()
281290
}
@@ -468,6 +477,38 @@ func (c *Client) setHostname(hostname string) {
468477
c.hostname = hostname
469478
}
470479

480+
func (c *Client) shouldProcessInteraction(interaction *server.Interaction) bool {
481+
key := interactionCacheKey(interaction)
482+
if key == "" {
483+
return true
484+
}
485+
486+
c.Lock()
487+
defer c.Unlock()
488+
489+
if _, err := c.processedInteractions.Get(key); err == nil {
490+
return false
491+
}
492+
_ = c.processedInteractions.SetWithExpire(key, true, defaultInteractionDuration)
493+
494+
return true
495+
}
496+
497+
func interactionCacheKey(interaction *server.Interaction) string {
498+
if interaction == nil {
499+
return ""
500+
}
501+
502+
uniqueID := strings.ToLower(interaction.UniqueID)
503+
if uniqueID == "" {
504+
return ""
505+
}
506+
507+
protocol := strings.ToLower(interaction.Protocol)
508+
509+
return uniqueID + ":" + protocol
510+
}
511+
471512
// GetHostname returns the configured interactsh server hostname.
472513
func (c *Client) GetHostname() string {
473514
return c.getHostname()

0 commit comments

Comments
 (0)