Documentation
¶
Overview ¶
Package diffscan provides differential analysis tools for detecting injection vulnerabilities through response comparison.
Index ¶
- func CountMatches(response, match []byte) int
- func GetCanaryKeys(customCanary string) []string
- func GetMergedNonMatchingFingerprints(attack1, attack2 *Attack) map[string]bool
- func GetNonMatchingFingerprints(attack1, attack2 *Attack) map[string]bool
- func Identical(candidate *Attack, attack2 *Attack) bool
- func IsCookieInsertionPoint(ip httpmsg.InsertionPoint) bool
- func IsHeaderInsertionPoint(ip httpmsg.InsertionPoint) bool
- func MapInsertionPointToPosition(ipType httpmsg.InsertionPointType) string
- func Similar(baselineGroup *Attack, breakAttack *Attack) bool
- func SimilarWithTolerance(baselineGroup *Attack, breakGroup *Attack, baselineAttack *Attack, ...) bool
- func VerySimilar(attack1 *Attack, attack2 *Attack) bool
- type Attack
- func NewAttack(quantDiffKeys []string, quantileFactor int, customCanary string) *Attack
- func NewAttackFromSnapshot(snap *ResponseSnapshot, probe *Probe, payload string, anchor string, ...) *Attack
- func NewAttackFromSnapshotSimple(snap *ResponseSnapshot, anchor string, quantDiffKeys []string, ...) *Attack
- type Option
- type PayloadInjector
- func (s *PayloadInjector) BuildAttack(payload string, random bool) (*Attack, error)
- func (s *PayloadInjector) Fuzz(baselineAttack *Attack, probe *Probe) ([]*Attack, error)
- func (s *PayloadInjector) GetPosition() string
- func (s *PayloadInjector) IsCookie() bool
- func (s *PayloadInjector) IsHeader() bool
- func (s *PayloadInjector) ProbeAttack(payload string) (*Attack, error)
- type Probe
- func (p *Probe) AddEscapePair(args ...string)
- func (p *Probe) GetAllEscapeSets() [][]string
- func (p *Probe) GetBreakStrings() []string
- func (p *Probe) GetEscapeStrings() [][]string
- func (p *Probe) GetNextBreakPayload() string
- func (p *Probe) GetNextEscapePayloadSet() []string
- func (p *Probe) SetEscapeStrings(args ...string)
- func (p *Probe) SetRandomAnchor(randomAnchor bool)
- func (p *Probe) SetUseCacheBuster(useCacheBuster bool)
- type ProbeInjectType
- type QuantitativeMeasurements
- func (qm *QuantitativeMeasurements) BasicOverlap(o *QuantitativeMeasurements) bool
- func (qm *QuantitativeMeasurements) Equals(o interface{}) bool
- func (qm *QuantitativeMeasurements) GetQuantileTop(list []int64) int64
- func (qm *QuantitativeMeasurements) Merge(newMeasurements *QuantitativeMeasurements)
- func (qm *QuantitativeMeasurements) QuantileOverlap(compareMeasurements *QuantitativeMeasurements) bool
- func (qm *QuantitativeMeasurements) String() string
- func (qm *QuantitativeMeasurements) UpdateWith(val int64)
- type ReflectionCount
- type ResponseSnapshot
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CountMatches ¶
func GetCanaryKeys ¶
GetCanaryKeys returns canary keys with optional custom canary prepended.
func GetMergedNonMatchingFingerprints ¶
GetMergedNonMatchingFingerprints returns the keys whose merged (stable) fingerprint value differs between the two attacks: the merged Fingerprint that survived every confirmation, rather than the transient last sample compared by GetNonMatchingFingerprints. Callers that explain or gate a finding (the report Diff Signal, the body-reflection gate) use this so the reported evidence matches what actually drove the decision.
func GetNonMatchingFingerprints ¶
GetNonMatchingFingerprints returns the keys whose last-sample fingerprint value differs between the two attacks. LastFingerprint is the single most recent sample and so includes per-request jitter; callers that need the stable signal the detection fired on should use GetMergedNonMatchingFingerprints.
func Identical ¶
Identical performs deep comparison of two attack fingerprints. Returns true if fingerprints are exactly equal. Returns false if candidate is nil.
func IsCookieInsertionPoint ¶
func IsCookieInsertionPoint(ip httpmsg.InsertionPoint) bool
IsCookieInsertionPoint checks if insertion point is for cookies
func IsHeaderInsertionPoint ¶
func IsHeaderInsertionPoint(ip httpmsg.InsertionPoint) bool
IsHeaderInsertionPoint checks if insertion point is for headers
func MapInsertionPointToPosition ¶
func MapInsertionPointToPosition(ipType httpmsg.InsertionPointType) string
MapInsertionPointToPosition maps insertion point type to position string
func Similar ¶
Similar checks if all key-value pairs in baselineGroup exist and match in breakAttack. This function checks if the second fingerprint is a superset of (or identical to) the first fingerprint. Returns true if the second fingerprint contains all entries from the first.
func SimilarWithTolerance ¶
func SimilarWithTolerance( baselineGroup *Attack, breakGroup *Attack, baselineAttack *Attack, breakAttack *Attack, ) bool
SimilarWithTolerance compares consistency between break/baseline groups and individuals. Returns true if similar according to complex conditions. This logic checks the consistency of fingerprint attributes between groups and individual attacks, handling missing keys by referencing the corresponding individual attack.
func VerySimilar ¶
VerySimilar checks if two attack fingerprints are "very similar". First, it checks if the number of keys is equal. Then, it iterates through the first fingerprint and checks if each key exists in the second fingerprint with the same value, except for the key "input_reflections" (which allows the value ReflectionCountIncalculable in either). Returns true if very similar (same length and matching key-values, except input_reflections).
Types ¶
type Attack ¶
type Attack struct {
FirstSnapshot *ResponseSnapshot
LastSnapshot *ResponseSnapshot
LastFingerprint map[string]any
Payload string
Anchor string
Fingerprint map[string]any
ResponseFingerprint *anomaly.Fingerprint
ResponseKeywordsFingerprint *anomaly.FastResponseKeywords
ResponseReflections ReflectionCount
Probe *Probe
// contains filtered or unexported fields
}
func NewAttackFromSnapshot ¶
func (*Attack) AllKeysAreQuantitative ¶
type Option ¶
type PayloadInjector ¶
type PayloadInjector struct {
InsertionPoint httpmsg.InsertionPoint
Options *Option
// contains filtered or unexported fields
}
func NewPayloadInjector ¶
func NewPayloadInjector( baseRequest []byte, insertionPoint httpmsg.InsertionPoint, httpService *httpmsg.Service, httpClient *http.Requester, options *Option, ) *PayloadInjector
func (*PayloadInjector) BuildAttack ¶
func (s *PayloadInjector) BuildAttack(payload string, random bool) (*Attack, error)
func (*PayloadInjector) Fuzz ¶
func (s *PayloadInjector) Fuzz( baselineAttack *Attack, probe *Probe, ) ([]*Attack, error)
func (*PayloadInjector) GetPosition ¶
func (s *PayloadInjector) GetPosition() string
GetPosition returns the position string for this injector's insertion point
func (*PayloadInjector) IsCookie ¶
func (s *PayloadInjector) IsCookie() bool
IsCookie returns true if the insertion point is a cookie parameter
func (*PayloadInjector) IsHeader ¶
func (s *PayloadInjector) IsHeader() bool
IsHeader returns true if the insertion point is a header
func (*PayloadInjector) ProbeAttack ¶
func (s *PayloadInjector) ProbeAttack(payload string) (*Attack, error)
type Probe ¶
type Probe struct {
Base string
Name string
InjectType ProbeInjectType
RandomAnchor bool
UseCacheBuster bool
RequireConsistentEvidence bool
Severity int
// contains filtered or unexported fields
}
func (*Probe) AddEscapePair ¶
func (*Probe) GetAllEscapeSets ¶
func (*Probe) GetBreakStrings ¶
func (*Probe) GetEscapeStrings ¶
func (*Probe) GetNextBreakPayload ¶
func (*Probe) GetNextEscapePayloadSet ¶
func (*Probe) SetEscapeStrings ¶
func (*Probe) SetRandomAnchor ¶
func (*Probe) SetUseCacheBuster ¶
type ProbeInjectType ¶
type ProbeInjectType int
const ( InjectType_Undefined ProbeInjectType = iota InjectType_Append InjectType_Prepend InjectType_Replace )
func (ProbeInjectType) String ¶
func (s ProbeInjectType) String() string
type QuantitativeMeasurements ¶
func NewQuantitativeMeasurements ¶
func NewQuantitativeMeasurements(key string, factor int) *QuantitativeMeasurements
func (*QuantitativeMeasurements) BasicOverlap ¶
func (qm *QuantitativeMeasurements) BasicOverlap(o *QuantitativeMeasurements) bool
func (*QuantitativeMeasurements) Equals ¶
func (qm *QuantitativeMeasurements) Equals(o interface{}) bool
func (*QuantitativeMeasurements) GetQuantileTop ¶
func (qm *QuantitativeMeasurements) GetQuantileTop(list []int64) int64
func (*QuantitativeMeasurements) Merge ¶
func (qm *QuantitativeMeasurements) Merge(newMeasurements *QuantitativeMeasurements)
func (*QuantitativeMeasurements) QuantileOverlap ¶
func (qm *QuantitativeMeasurements) QuantileOverlap(compareMeasurements *QuantitativeMeasurements) bool
func (*QuantitativeMeasurements) String ¶
func (qm *QuantitativeMeasurements) String() string
func (*QuantitativeMeasurements) UpdateWith ¶
func (qm *QuantitativeMeasurements) UpdateWith(val int64)
type ReflectionCount ¶
type ReflectionCount int
ReflectionCount tracks how many times the injected payload appears in the response. Negative values represent special states rather than actual counts.
const ( ReflectionCountUninitialized ReflectionCount = -1 ReflectionCountDynamic ReflectionCount = -2 ReflectionCountIncalculable ReflectionCount = -3 )
func (ReflectionCount) String ¶
func (s ReflectionCount) String() string
type ResponseSnapshot ¶
type ResponseSnapshot struct {
// Filtered response for keyword tracking and anchor reflection counting
FilteredResponse []byte
// Full fingerprint for structural comparison (replaces FastResponseVariations + QuantFingerprint)
Fingerprint *anomaly.Fingerprint
// WAF detection metadata
StatusCode int
// Report generation metadata
RequestDump string
ContentLength int
URL string
Method string
}
ResponseSnapshot contains extracted data from a ResponseChain. It is designed to be created immediately after an HTTP request, allowing the ResponseChain to be closed and its buffers returned to the pool.
func NewResponseSnapshot ¶
func NewResponseSnapshot(respChain *httputil.ResponseChain) *ResponseSnapshot
NewResponseSnapshot creates a snapshot from a ResponseChain. IMPORTANT: This function closes the ResponseChain after extracting data. The caller should NOT use the ResponseChain after calling this function.
func (*ResponseSnapshot) IsEmptyBody ¶
func (s *ResponseSnapshot) IsEmptyBody() bool
IsEmptyBody reports whether the response carried no body. Error-based, diff-based detection needs the payload to reach something that produces output: when a confirmed response has a 0-length body there is nothing rendered that could have evaluated the payload, so a break-vs-escape difference across empty responses can only be header/cookie jitter, not template evaluation.
func (*ResponseSnapshot) IsNotFound ¶
func (s *ResponseSnapshot) IsNotFound() bool
IsNotFound reports whether the response is a 404. A pair of 404s means the route/template was never reached — in most stacks the router rejects the path before any template layer runs — so a difference between two 404s is not evidence the payload was evaluated. (Reflection into a custom 404 page is still caught separately by the body-reflection gate.)
func (*ResponseSnapshot) IsRedirect ¶
func (s *ResponseSnapshot) IsRedirect() bool
IsRedirect reports whether the response is an HTTP redirect (3xx).
A redirect carries no observable evidence of how the application *processed* an injected payload: the body is a fixed stub and the only payload-dependent part is the echoed Location header. Diff-based, error-based detection (SSTI / behaviour probing) therefore cannot use a redirect as an evaluation signal — a break-vs-escape difference between two redirects is the literal payload being reflected back into the redirect target, not the template engine evaluating it. This was the source of the reported diff-based SSTI false positives on identity/CIAM hosts that 301-redirect every request: break `{{7/0}}` and escape `{{7/1}}` produced identical 301s with identical body length, differing only in the CRC32 of the echoed Location header.
A status *transition* (e.g. 200 → 301) is still a legitimate signal: it shows up in the STATUS_CODE fingerprint attribute, and at least one side of the comparison is not a redirect, so it is never suppressed by this gate.
func (*ResponseSnapshot) IsSuccess ¶
func (s *ResponseSnapshot) IsSuccess() bool
IsSuccess reports whether the response is a 2xx success — i.e. the server actually served a real resource rather than an error/not-found/redirect stub.
Diff-based path-escape and normalization detection compares how the server resolves a "control" path that should reach a genuine resource against an exploit path. When the reached side is a 4xx/5xx (e.g. an empty 404 emitted for everything under a CDN-internal prefix like /cdn-cgi/) or a 3xx, nothing was actually reached: any break-vs-escape difference is header jitter (Ray-ID / ETag / Date / Set-Cookie), not evidence of a path escape. Callers gate on this to require the reached resource be a served 2xx body. (WafBlocked already drops 401/403/429/503; this is the complementary "did we reach a real resource" check that also excludes 404/3xx/5xx.)
func (*ResponseSnapshot) WafBlocked ¶
func (s *ResponseSnapshot) WafBlocked() bool
WafBlocked reports whether the response is a denial / throttle / unavailable page — a WAF/CDN/auth/rate-limit layer rejecting the request — rather than the application actually processing the injected payload. Diff-based detection keys off how the app *reacts* to a payload, so a response that never reached the app is not a vulnerability signal and must not count as a "diff".
Any 401/403/429/503 is treated as blocked regardless of vendor: the previous vendor-gated check (cloudflare/akamai/cloudfront/incapsula only) let a generic app/proxy 403 "Forbidden", 401 auth challenge, or 503 maintenance page slip through as a payload-driven difference — the reported diff-based false positive. A 5xx *application* error such as 500 is deliberately NOT treated as blocked: error-based SSTI/behaviour detection legitimately relies on it.