Skip to content

Commit

Permalink
improved deception, added new flag stime, minor fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
m10x committed Nov 6, 2024
1 parent f953f7d commit e4685e7
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 34 deletions.
2 changes: 1 addition & 1 deletion buildBinariesLinux.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
version=1.2.1
version=1.3.0

# Windows amd64
goos=windows
Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ module github.com/Hackmanit/Web-Cache-Vulnerability-Scanner
go 1.21

require (
github.com/fatih/color v1.16.0
golang.org/x/net v0.22.0
golang.org/x/time v0.5.0
github.com/fatih/color v1.18.0
golang.org/x/net v0.30.0
golang.org/x/time v0.7.0
moul.io/http2curl v1.0.0
)

require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/smartystreets/goconvey v1.8.1 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
)
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
Expand All @@ -13,15 +13,15 @@ github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGB
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
1 change: 1 addition & 0 deletions pkg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type (
UseHTTP bool
CLDiff int
HMDiff int
SkipTimebased bool
CacheHeader string
DisableColor bool
IgnoreStatus []int
Expand Down
83 changes: 71 additions & 12 deletions pkg/deception.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,44 @@ func TestWebCacheDeception() reportResult {
repResult.Technique = "Cache Deception"

// cacheable extensions: class, css, jar, js, jpg, jpeg, gif, ico, png, bmp, pict, csv, doc, docx, xls, xlsx, ps, pdf, pls, ppt, pptx, tif, tiff, ttf, otf, webp, woff, woff2, svg, svgz, eot, eps, ejs, swf, torrent, midi, mid

appendings := []string{
"/.css", // Path parameter
"/nonexistent.css", // Path parameter
"/../nonexistent.css", // Path traversal
"/%2e%2e/nonexistent.css", // Encoded path traversal
"%0Anonexistent.css", // Encoded Newline
"%00nonexistent.css", // Encoded Newline
"%3Bnonexistent.css", // Encoded Semicolon
"%23nonexistent.css", // Encoded Pound
"%3Fname = valnonexistent.css", // Encoded Question Mark
"%26name=valnonexistent.css", // Encoded Ampersand
"/.css", // Path parameter
"/nonexistent1.css", // Path parameter
"/../nonexistent2.css", // Path traversal
"/%2e%2e/nonexistent3.css", // Encoded path traversal
"%0Anonexistent4.css", // Encoded Newline
"%00nonexistent5.css", // Encoded Null Byte
"%09nonexistent6.css", // Encoded Tab
"%3Bnonexistent7.css", // Encoded Semicolon
"%23nonexistent8.css", // Encoded Pound
"%3Fname=valnonexistent9.css", // Encoded Question Mark
"%26name=valnonexistent10.css", // Encoded Ampersand
";nonexistent11.css", // Semicolon
"?nonexistent12.css", // Question Mark
"&nonexistent13.css", // Ampersand
"%0A%2f%2e%2e%2fresources%2fnonexistent1.css", // Encoded Path Traversal to static directory using Encoded Newline
"%00%2f%2e%2e%2fresources%2fnonexistent2.css", // Encoded Path Traversal to static directory using Encoded Null Byte
"%09%2f%2e%2e%2fresources%2fnonexistent3.css", // Encoded Path Traversal to static directory using Encoded Tab
"%3B%2f%2e%2e%2fresources%2fnonexistent4.css", // Encoded Path Traversal to static directoryEncoded using Semicolon
"%23%2f%2e%2e%2fresources%2fnonexistent5.css", // Encoded Path Traversal to static directory using Encoded Pound
"%3F%2f%2e%2e%2fresources%2fnonexistent6.css", // Encoded Path Traversal to static directory using Encoded Question Mark
"%26%2f%2e%2e%2fresources%2fnonexistent7.css", // Encoded Path Traversal to static directory using Encoded Ampersand
";%2f%2e%2e%2fresources%2fnonexistent8.css", // Encoded Path Traversal to static directory using Semicolon
"?%2f%2e%2e%2fresources%2fnonexistent9.css", // Encoded Path Traversal to static directoy using Question Mark
"&%2f%2e%2e%2fresources%2fnonexistent10.css", // Encoded Path Traversal to static directory using Ampersand
"%0A%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?a", // Encoded Path Traversal to robots.txt using Encoded Newline
"%00%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?b", // Encoded Path Traversal to robots.txt directory using Encoded Null Byte
"%09%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?c", // Encoded Path Traversal to robots.txt directory using Encoded Tab
"%3B%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?d", // Encoded Path Traversal to robots.txt directoryEncoded using Semicolon
"%23%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?e", // Encoded Path Traversal to robots.txt directory using Encoded Pound
"%3F%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?f", // Encoded Path Traversal to robots.txt directory using Encoded Question Mark
"%26%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?g", // Encoded Path Traversal to robots.txt directory using Encoded Ampersand
";%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?h", // Encoded Path Traversal to robots.txt directory using Semicolon
"?%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?i", // Encoded Path Traversal to robots.txt directoy using Question Mark
"&%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2frobots.txt?j", // Encoded Path Traversal to robots.txt directory using Ampersand
}
// TODO add "Exploiting normalization by the origin server" cache deception which needs to prepend something before the url path

PrintVerbose("Testing for Web Cache Deception\n", NoColor, 1)

Expand Down Expand Up @@ -65,7 +91,13 @@ func webCacheDeceptionTemplate(repResult *reportResult, appendStr string) error
Timeout: http.DefaultClient.Timeout,
}
setRequest(req, false, "", http.Cookie{})
resp, err = newClient.Do(req)
_, err = newClient.Do(req)
if err != nil {
msg = fmt.Sprintf("webCacheDeceptionTemplate: %s: newClient.Do: %s\n", appendStr, err.Error())
PrintVerbose(msg, Yellow, 1)
return errors.New(msg)
}
resp, err = newClient.Do(req) // send request 2 times so it'll return a cache hit if deception was successful!
if err != nil {
msg = fmt.Sprintf("webCacheDeceptionTemplate: %s: newClient.Do: %s\n", appendStr, err.Error())
PrintVerbose(msg, Yellow, 1)
Expand Down Expand Up @@ -93,7 +125,34 @@ func webCacheDeceptionTemplate(repResult *reportResult, appendStr string) error
}
repRequest.CurlCommand = command.String()

indicValue := strings.TrimSpace(strings.ToLower(resp.Header.Get(Config.Website.Cache.Indicator)))
var indicValue string
if Config.Website.Cache.Indicator == "" { // check if now a cache indicator exists
customCacheHeader := strings.ToLower(Config.CacheHeader)
for key, val := range resp.Header {
switch strings.ToLower(key) {
case "x-cache", "cf-cache-status", "x-drupal-cache", "x-varnish-cache", "akamai-cache-status", "server-timing", "x-iinfo", "x-nc", "x-hs-cf-cache-status", "x-proxy-cache", "x-cache-hits", "x-cache-status", "x-cache-info", "x-rack-cache", "cdn_cache_status", "x-akamai-cache", "x-akamai-cache-remote", "x-cache-remote", customCacheHeader:
// CacheHeader flag might not be set (=> ""). Continue in this case
if key == "" {
continue
}
Config.Website.Cache.Indicator = key
msg := fmt.Sprintf("%s: %s header was found: %s \n", req.URL, key, val)
PrintVerbose(msg, NoColor, 1)
addHitMissIndicatorMap(strings.ToLower(key))
case "age":
// only set it it wasn't set to x-cache or sth. similar beforehand
if Config.Website.Cache.Indicator == "" {
Config.Website.Cache.Indicator = key
msg := fmt.Sprintf("%s: %s header was found: %s\n", req.URL, key, val)
PrintVerbose(msg, NoColor, 1)
addHitMissIndicatorMap(strings.ToLower("age"))
}
}
}
}

indicValue = strings.TrimSpace(strings.ToLower(resp.Header.Get(Config.Website.Cache.Indicator)))

// check if there's a cache hit and if the body didn't change (otherwise it could be a cached error page, for example)
if checkCacheHit(indicValue) && string(body) == Config.Website.Body {
repResult.Vulnerable = true
Expand Down
4 changes: 3 additions & 1 deletion pkg/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ func ParseFlags(vers string) {
appendBoolean(&generalOptions, &Config.Force,
"force", "f", false, "Perform the tests no matter if there is a cache or even the cachebuster works or not")
appendString(&generalOptions, &ignoreStatus,
"ignorestatus", "is", "", "Specify a custom cache header")
"ignorestatus", "is", "", "Ignore a specific status code for cache poisoning")
appendInt(&generalOptions, &Config.CLDiff,
"contentlengthdifference", "cldiff", 0, "Threshold for reporting possible Finding, when 'poisoned' response differs more from the original length. Default is 0 (don't check)")
appendInt(&generalOptions, &Config.HMDiff,
"hitmissdifference", "hmdiff", 30, "Threshold for time difference between cache hit and cache miss responses. Default is 30")
appendBoolean(&generalOptions, &Config.SkipTimebased,
"skiptimebased", "stime", false, "Skip checking if a repsonse gets cached by measuring time differences")
appendString(&generalOptions, &Config.CacheHeader,
"cacheheader", "ch", "", "Specify a custom cache header")
appendBoolean(&generalOptions, &Config.DisableColor,
Expand Down
19 changes: 16 additions & 3 deletions pkg/recon.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,6 @@ func CheckCache(stat string) (CacheStruct, bool, []error) {
cache.Indicator = key
msg := fmt.Sprintf("%s header was found: %s\n", key, val)
PrintVerbose(msg, NoColor, 1)
if cache.Indicator == "" {
cache.Indicator = "age"
}
addHitMissIndicatorMap(strings.ToLower("age"))
}
}
Expand Down Expand Up @@ -355,6 +352,10 @@ func cachebusterCookie(cache *CacheStruct) []error {

if cache.Indicator == "" {
// No Cache Indicator was found. So time will be used as Indicator
if Config.SkipTimebased {
continue
}

var newCookie http.Cookie
var cb string
for ii := 0; ii < 5*2; ii++ {
Expand Down Expand Up @@ -563,6 +564,10 @@ func cachebusterHeader(cache *CacheStruct) []error {

if cache.Indicator == "" {
// No Cache Indicator was found. So time will be used as Indicator
if Config.SkipTimebased {
continue
}

var cb string
for ii := 0; ii < 5*2; ii++ {
weburl := Config.Website.Url.String()
Expand Down Expand Up @@ -775,6 +780,10 @@ func cachebusterParameter(cache *CacheStruct) error {

if cache.Indicator == "" {
// No Cache Indicator was found. So time will be used as Indicator
if Config.SkipTimebased {
return nil
}

var urlCb string
for i := 0; i < 5*2; i++ {
if i%2 == 0 {
Expand Down Expand Up @@ -958,6 +967,10 @@ func cachebusterHTTPMethod(cache *CacheStruct) []error {

if cache.Indicator == "" {
// No Cache Indicator was found. So time will be used as Indicator
if Config.SkipTimebased {
continue
}

skip := false
for ii := 0; ii < 5*2; ii++ {
weburl := Config.Website.Url.String()
Expand Down
4 changes: 2 additions & 2 deletions web-cache-vulnerability-scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"golang.org/x/net/http2"
)

const version = "1.2.1"
const version = "1.3.0"

var (
currentDate string
Expand Down Expand Up @@ -339,7 +339,7 @@ func runTests(rec int, u string, progress string, foundUrls *[]string, stat stri
msg = addSeparator("Web Cache Deception")
pkg.PrintVerbose(msg, pkg.NoColor, 1)

if alwaysMiss { // test for Web Cache Deception
if alwaysMiss || pkg.Config.Website.Cache.Indicator == "" {
pkg.Statistics[stat+"deceptiontested"]++
repWebsite.Results = append(repWebsite.Results, pkg.TestWebCacheDeception())
} else {
Expand Down

0 comments on commit e4685e7

Please sign in to comment.