mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-23 15:25:24 +00:00
172 lines
5.0 KiB
Go
172 lines
5.0 KiB
Go
|
|
// Package time implements a time delay analyzer using linear
|
||
|
|
// regression heuristics inspired from ZAP to discover time
|
||
|
|
// based issues.
|
||
|
|
//
|
||
|
|
// The approach is the one used in ZAP for timing based checks.
|
||
|
|
// Advantages of this approach are many compared to the old approach of
|
||
|
|
// heuristics of sleep time.
|
||
|
|
//
|
||
|
|
// As we are building a statistical model, we can predict if the delay
|
||
|
|
// is random or not very quickly. Also, the payloads are alternated to send
|
||
|
|
// a very high sleep and a very low sleep. This way the comparison is
|
||
|
|
// faster to eliminate negative cases. Only legitimate cases are sent for
|
||
|
|
// more verification.
|
||
|
|
//
|
||
|
|
// For more details on the algorithm, follow the links below:
|
||
|
|
// - https://groups.google.com/g/zaproxy-develop/c/KGSkNHlLtqk
|
||
|
|
// - https://github.com/zaproxy/zap-extensions/pull/5053
|
||
|
|
//
|
||
|
|
// This file has been implemented from its original version. It was originally licensed under the Apache License 2.0 (see LICENSE file for details).
|
||
|
|
// The original algorithm is implemented in ZAP Active Scanner.
|
||
|
|
package time
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"math"
|
||
|
|
)
|
||
|
|
|
||
|
|
type timeDelayRequestSender func(delay int) (float64, error)
|
||
|
|
|
||
|
|
// checkTimingDependency checks the timing dependency for a given request
|
||
|
|
//
|
||
|
|
// It alternates and sends first a high request, then a low request. Each time
|
||
|
|
// it checks if the delay of the application can be predictably controlled.
|
||
|
|
func checkTimingDependency(
|
||
|
|
requestsLimit int,
|
||
|
|
highSleepTimeSeconds int,
|
||
|
|
correlationErrorRange float64,
|
||
|
|
slopeErrorRange float64,
|
||
|
|
requestSender timeDelayRequestSender,
|
||
|
|
) (bool, string, error) {
|
||
|
|
if requestsLimit < 2 {
|
||
|
|
return false, "", errors.New("requests limit should be at least 2")
|
||
|
|
}
|
||
|
|
|
||
|
|
regression := newSimpleLinearRegression()
|
||
|
|
requestsLeft := requestsLimit
|
||
|
|
|
||
|
|
for {
|
||
|
|
if requestsLeft <= 0 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
|
||
|
|
isCorrelationPossible, err := sendRequestAndTestConfidence(regression, highSleepTimeSeconds, requestSender)
|
||
|
|
if err != nil {
|
||
|
|
return false, "", err
|
||
|
|
}
|
||
|
|
if !isCorrelationPossible {
|
||
|
|
return false, "", nil
|
||
|
|
}
|
||
|
|
|
||
|
|
isCorrelationPossible, err = sendRequestAndTestConfidence(regression, 1, requestSender)
|
||
|
|
if err != nil {
|
||
|
|
return false, "", err
|
||
|
|
}
|
||
|
|
if !isCorrelationPossible {
|
||
|
|
return false, "", nil
|
||
|
|
}
|
||
|
|
requestsLeft = requestsLeft - 2
|
||
|
|
}
|
||
|
|
|
||
|
|
result := regression.IsWithinConfidence(correlationErrorRange, 1.0, slopeErrorRange)
|
||
|
|
if result {
|
||
|
|
resultReason := fmt.Sprintf(
|
||
|
|
"[time_delay] made %d requests successfully, with a regression slope of %.2f and correlation %.2f",
|
||
|
|
requestsLimit,
|
||
|
|
regression.slope,
|
||
|
|
regression.correlation,
|
||
|
|
)
|
||
|
|
return result, resultReason, nil
|
||
|
|
}
|
||
|
|
return result, "", nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// sendRequestAndTestConfidence sends a request and tests the confidence of delay
|
||
|
|
func sendRequestAndTestConfidence(
|
||
|
|
regression *simpleLinearRegression,
|
||
|
|
delay int,
|
||
|
|
requestSender timeDelayRequestSender,
|
||
|
|
) (bool, error) {
|
||
|
|
delayReceived, err := requestSender(delay)
|
||
|
|
if err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
|
||
|
|
if delayReceived < float64(delay) {
|
||
|
|
return false, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
regression.AddPoint(float64(delay), delayReceived)
|
||
|
|
|
||
|
|
if !regression.IsWithinConfidence(0.3, 1.0, 0.5) {
|
||
|
|
return false, nil
|
||
|
|
}
|
||
|
|
return true, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// simpleLinearRegression is a simple linear regression model that can be updated at runtime.
|
||
|
|
// It is based on the same algorithm in ZAP for doing timing checks.
|
||
|
|
type simpleLinearRegression struct {
|
||
|
|
count float64
|
||
|
|
independentSum float64
|
||
|
|
dependentSum float64
|
||
|
|
|
||
|
|
// Variances
|
||
|
|
independentVarianceN float64
|
||
|
|
dependentVarianceN float64
|
||
|
|
sampleCovarianceN float64
|
||
|
|
|
||
|
|
slope float64
|
||
|
|
intercept float64
|
||
|
|
correlation float64
|
||
|
|
}
|
||
|
|
|
||
|
|
func newSimpleLinearRegression() *simpleLinearRegression {
|
||
|
|
return &simpleLinearRegression{
|
||
|
|
slope: 1,
|
||
|
|
correlation: 1,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (o *simpleLinearRegression) AddPoint(x, y float64) {
|
||
|
|
independentResidualAdjustment := x - o.independentSum/o.count
|
||
|
|
dependentResidualAdjustment := y - o.dependentSum/o.count
|
||
|
|
|
||
|
|
o.count += 1
|
||
|
|
o.independentSum += x
|
||
|
|
o.dependentSum += y
|
||
|
|
|
||
|
|
if math.IsNaN(independentResidualAdjustment) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
independentResidual := x - o.independentSum/o.count
|
||
|
|
dependentResidual := y - o.dependentSum/o.count
|
||
|
|
|
||
|
|
o.independentVarianceN += independentResidual * independentResidualAdjustment
|
||
|
|
o.dependentVarianceN += dependentResidual * dependentResidualAdjustment
|
||
|
|
o.sampleCovarianceN += independentResidual * dependentResidualAdjustment
|
||
|
|
|
||
|
|
o.slope = o.sampleCovarianceN / o.independentVarianceN
|
||
|
|
o.correlation = o.slope * math.Sqrt(o.independentVarianceN/o.dependentVarianceN)
|
||
|
|
o.correlation *= o.correlation
|
||
|
|
|
||
|
|
// NOTE: zap had the reverse formula, changed it to the correct one
|
||
|
|
// for intercept. Verify if this is correct.
|
||
|
|
o.intercept = o.dependentSum/o.count - o.slope*(o.independentSum/o.count)
|
||
|
|
if math.IsNaN(o.correlation) {
|
||
|
|
o.correlation = 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (o *simpleLinearRegression) Predict(x float64) float64 {
|
||
|
|
return o.slope*x + o.intercept
|
||
|
|
}
|
||
|
|
|
||
|
|
func (o *simpleLinearRegression) IsWithinConfidence(correlationErrorRange float64, expectedSlope float64, slopeErrorRange float64,
|
||
|
|
) bool {
|
||
|
|
return o.correlation > 1.0-correlationErrorRange &&
|
||
|
|
math.Abs(expectedSlope-o.slope) < slopeErrorRange
|
||
|
|
}
|