Path: blob/dev/pkg/fuzz/analyzers/time/time_delay.go
2070 views
// Package time implements a time delay analyzer using linear1// regression heuristics inspired from ZAP to discover time2// based issues.3//4// The approach is the one used in ZAP for timing based checks.5// Advantages of this approach are many compared to the old approach of6// heuristics of sleep time.7//8// NOTE: This algorithm has been heavily modified after being introduced9// in nuclei. Now the logic has sever bug fixes and improvements and10// has been evolving to be more stable.11//12// As we are building a statistical model, we can predict if the delay13// is random or not very quickly. Also, the payloads are alternated to send14// a very high sleep and a very low sleep. This way the comparison is15// faster to eliminate negative cases. Only legitimate cases are sent for16// more verification.17//18// For more details on the algorithm, follow the links below:19// - https://groups.google.com/g/zaproxy-develop/c/KGSkNHlLtqk20// - https://github.com/zaproxy/zap-extensions/pull/505321//22// This file has been implemented from its original version. It was originally licensed under the Apache License 2.0 (see LICENSE file for details).23// The original algorithm is implemented in ZAP Active Scanner.24package time2526import (27"errors"28"fmt"29"math"30"strings"31)3233type timeDelayRequestSender func(delay int) (float64, error)3435// requestsSentMetadata is used to store the delay requested36// and delay received for each request37type requestsSentMetadata struct {38delay int39delayReceived float6440}4142// checkTimingDependency checks the timing dependency for a given request43//44// It alternates and sends first a high request, then a low request. Each time45// it checks if the delay of the application can be predictably controlled.46func checkTimingDependency(47requestsLimit int,48highSleepTimeSeconds int,49correlationErrorRange float64,50slopeErrorRange float64,51baselineDelay float64,52requestSender timeDelayRequestSender,53) (bool, string, error) {54if requestsLimit < 2 {55return false, "", errors.New("requests limit should be at least 2")56}5758regression := newSimpleLinearRegression()59requestsLeft := requestsLimit6061var requestsSent []requestsSentMetadata62for requestsLeft > 0 {63isCorrelationPossible, delayRecieved, err := sendRequestAndTestConfidence(regression, highSleepTimeSeconds, requestSender, baselineDelay)64if err != nil {65return false, "", err66}67if !isCorrelationPossible {68return false, "", nil69}70// Check the delay is greater than baseline by seconds requested71if delayRecieved < baselineDelay+float64(highSleepTimeSeconds)*0.8 {72return false, "", nil73}74requestsSent = append(requestsSent, requestsSentMetadata{75delay: highSleepTimeSeconds,76delayReceived: delayRecieved,77})7879isCorrelationPossibleSecond, delayRecievedSecond, err := sendRequestAndTestConfidence(regression, int(DefaultLowSleepTimeSeconds), requestSender, baselineDelay)80if err != nil {81return false, "", err82}83if !isCorrelationPossibleSecond {84return false, "", nil85}86if delayRecievedSecond < baselineDelay+float64(DefaultLowSleepTimeSeconds)*0.8 {87return false, "", nil88}89requestsLeft = requestsLeft - 29091requestsSent = append(requestsSent, requestsSentMetadata{92delay: int(DefaultLowSleepTimeSeconds),93delayReceived: delayRecievedSecond,94})95}9697result := regression.IsWithinConfidence(correlationErrorRange, 1.0, slopeErrorRange)98if result {99var resultReason strings.Builder100resultReason.WriteString(fmt.Sprintf(101"[time_delay] made %d requests (baseline: %.2fs) successfully, with a regression slope of %.2f and correlation %.2f",102requestsLimit,103baselineDelay,104regression.slope,105regression.correlation,106))107for _, request := range requestsSent {108resultReason.WriteString(fmt.Sprintf("\n - delay: %ds, delayReceived: %fs", request.delay, request.delayReceived))109}110return result, resultReason.String(), nil111}112return result, "", nil113}114115// sendRequestAndTestConfidence sends a request and tests the confidence of delay116func sendRequestAndTestConfidence(117regression *simpleLinearRegression,118delay int,119requestSender timeDelayRequestSender,120baselineDelay float64,121) (bool, float64, error) {122delayReceived, err := requestSender(delay)123if err != nil {124return false, 0, err125}126127if delayReceived < float64(delay) {128return false, 0, nil129}130131regression.AddPoint(float64(delay), delayReceived-baselineDelay)132133if !regression.IsWithinConfidence(0.3, 1.0, 0.5) {134return false, delayReceived, nil135}136return true, delayReceived, nil137}138139type simpleLinearRegression struct {140count float64141142sumX float64143sumY float64144sumXX float64145sumYY float64146sumXY float64147148slope float64149intercept float64150correlation float64151}152153func newSimpleLinearRegression() *simpleLinearRegression {154return &simpleLinearRegression{155// Start everything at zero until we have data156slope: 0.0,157intercept: 0.0,158correlation: 0.0,159}160}161162func (o *simpleLinearRegression) AddPoint(x, y float64) {163o.count += 1164o.sumX += x165o.sumY += y166o.sumXX += x * x167o.sumYY += y * y168o.sumXY += x * y169170// Need at least two points for meaningful calculation171if o.count < 2 {172return173}174175n := o.count176meanX := o.sumX / n177meanY := o.sumY / n178179// Compute sample variances and covariance180varX := (o.sumXX - n*meanX*meanX) / (n - 1)181varY := (o.sumYY - n*meanY*meanY) / (n - 1)182covXY := (o.sumXY - n*meanX*meanY) / (n - 1)183184// If varX is zero, slope cannot be computed meaningfully.185// This would mean all X are the same, so handle that edge case.186if varX == 0 {187o.slope = 0.0188o.intercept = meanY // Just the mean189o.correlation = 0.0 // No correlation since all X are identical190return191}192193o.slope = covXY / varX194o.intercept = meanY - o.slope*meanX195196// If varX or varY are zero, we cannot compute correlation properly.197if varX > 0 && varY > 0 {198o.correlation = covXY / (math.Sqrt(varX) * math.Sqrt(varY))199} else {200o.correlation = 0.0201}202}203204func (o *simpleLinearRegression) Predict(x float64) float64 {205return o.slope*x + o.intercept206}207208func (o *simpleLinearRegression) IsWithinConfidence(correlationErrorRange float64, expectedSlope float64, slopeErrorRange float64) bool {209if o.count < 2 {210return true211}212// Check if slope is within error range of expected slope213// Also consider cases where slope is approximately 2x of expected slope214// as this can happen with time-based responses215slopeDiff := math.Abs(expectedSlope - o.slope)216slope2xDiff := math.Abs(expectedSlope*2 - o.slope)217if slopeDiff > slopeErrorRange && slope2xDiff > slopeErrorRange {218return false219}220return o.correlation > 1.0-correlationErrorRange221}222223224