Skip to content

Commit e4685e7

Browse files
committed
improved deception, added new flag stime, minor fixes
1 parent f953f7d commit e4685e7

File tree

8 files changed

+109
-34
lines changed

8 files changed

+109
-34
lines changed

buildBinariesLinux.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
version=1.2.1
2+
version=1.3.0
33

44
# Windows amd64
55
goos=windows

go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ module github.com/Hackmanit/Web-Cache-Vulnerability-Scanner
33
go 1.21
44

55
require (
6-
github.com/fatih/color v1.16.0
7-
golang.org/x/net v0.22.0
8-
golang.org/x/time v0.5.0
6+
github.com/fatih/color v1.18.0
7+
golang.org/x/net v0.30.0
8+
golang.org/x/time v0.7.0
99
moul.io/http2curl v1.0.0
1010
)
1111

1212
require (
1313
github.com/mattn/go-colorable v0.1.13 // indirect
1414
github.com/mattn/go-isatty v0.0.20 // indirect
1515
github.com/smartystreets/goconvey v1.8.1 // indirect
16-
golang.org/x/sys v0.18.0 // indirect
17-
golang.org/x/text v0.14.0 // indirect
16+
golang.org/x/sys v0.26.0 // indirect
17+
golang.org/x/text v0.19.0 // indirect
1818
)

go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
2-
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
1+
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
2+
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
33
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
44
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
55
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@@ -13,15 +13,15 @@ github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGB
1313
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
1414
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
1515
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
16-
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
17-
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
16+
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
17+
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
1818
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1919
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
20-
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
21-
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
22-
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
23-
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
24-
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
25-
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
20+
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
21+
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
22+
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
23+
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
24+
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
25+
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
2626
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
2727
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=

pkg/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type (
2424
UseHTTP bool
2525
CLDiff int
2626
HMDiff int
27+
SkipTimebased bool
2728
CacheHeader string
2829
DisableColor bool
2930
IgnoreStatus []int

pkg/deception.go

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,44 @@ func TestWebCacheDeception() reportResult {
1616
repResult.Technique = "Cache Deception"
1717

1818
// 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
19+
1920
appendings := []string{
20-
"/.css", // Path parameter
21-
"/nonexistent.css", // Path parameter
22-
"/../nonexistent.css", // Path traversal
23-
"/%2e%2e/nonexistent.css", // Encoded path traversal
24-
"%0Anonexistent.css", // Encoded Newline
25-
"%00nonexistent.css", // Encoded Newline
26-
"%3Bnonexistent.css", // Encoded Semicolon
27-
"%23nonexistent.css", // Encoded Pound
28-
"%3Fname = valnonexistent.css", // Encoded Question Mark
29-
"%26name=valnonexistent.css", // Encoded Ampersand
21+
"/.css", // Path parameter
22+
"/nonexistent1.css", // Path parameter
23+
"/../nonexistent2.css", // Path traversal
24+
"/%2e%2e/nonexistent3.css", // Encoded path traversal
25+
"%0Anonexistent4.css", // Encoded Newline
26+
"%00nonexistent5.css", // Encoded Null Byte
27+
"%09nonexistent6.css", // Encoded Tab
28+
"%3Bnonexistent7.css", // Encoded Semicolon
29+
"%23nonexistent8.css", // Encoded Pound
30+
"%3Fname=valnonexistent9.css", // Encoded Question Mark
31+
"%26name=valnonexistent10.css", // Encoded Ampersand
32+
";nonexistent11.css", // Semicolon
33+
"?nonexistent12.css", // Question Mark
34+
"&nonexistent13.css", // Ampersand
35+
"%0A%2f%2e%2e%2fresources%2fnonexistent1.css", // Encoded Path Traversal to static directory using Encoded Newline
36+
"%00%2f%2e%2e%2fresources%2fnonexistent2.css", // Encoded Path Traversal to static directory using Encoded Null Byte
37+
"%09%2f%2e%2e%2fresources%2fnonexistent3.css", // Encoded Path Traversal to static directory using Encoded Tab
38+
"%3B%2f%2e%2e%2fresources%2fnonexistent4.css", // Encoded Path Traversal to static directoryEncoded using Semicolon
39+
"%23%2f%2e%2e%2fresources%2fnonexistent5.css", // Encoded Path Traversal to static directory using Encoded Pound
40+
"%3F%2f%2e%2e%2fresources%2fnonexistent6.css", // Encoded Path Traversal to static directory using Encoded Question Mark
41+
"%26%2f%2e%2e%2fresources%2fnonexistent7.css", // Encoded Path Traversal to static directory using Encoded Ampersand
42+
";%2f%2e%2e%2fresources%2fnonexistent8.css", // Encoded Path Traversal to static directory using Semicolon
43+
"?%2f%2e%2e%2fresources%2fnonexistent9.css", // Encoded Path Traversal to static directoy using Question Mark
44+
"&%2f%2e%2e%2fresources%2fnonexistent10.css", // Encoded Path Traversal to static directory using Ampersand
45+
"%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
46+
"%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
47+
"%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
48+
"%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
49+
"%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
50+
"%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
51+
"%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
52+
";%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
53+
"?%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
54+
"&%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
3055
}
56+
// TODO add "Exploiting normalization by the origin server" cache deception which needs to prepend something before the url path
3157

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

@@ -65,7 +91,13 @@ func webCacheDeceptionTemplate(repResult *reportResult, appendStr string) error
6591
Timeout: http.DefaultClient.Timeout,
6692
}
6793
setRequest(req, false, "", http.Cookie{})
68-
resp, err = newClient.Do(req)
94+
_, err = newClient.Do(req)
95+
if err != nil {
96+
msg = fmt.Sprintf("webCacheDeceptionTemplate: %s: newClient.Do: %s\n", appendStr, err.Error())
97+
PrintVerbose(msg, Yellow, 1)
98+
return errors.New(msg)
99+
}
100+
resp, err = newClient.Do(req) // send request 2 times so it'll return a cache hit if deception was successful!
69101
if err != nil {
70102
msg = fmt.Sprintf("webCacheDeceptionTemplate: %s: newClient.Do: %s\n", appendStr, err.Error())
71103
PrintVerbose(msg, Yellow, 1)
@@ -93,7 +125,34 @@ func webCacheDeceptionTemplate(repResult *reportResult, appendStr string) error
93125
}
94126
repRequest.CurlCommand = command.String()
95127

96-
indicValue := strings.TrimSpace(strings.ToLower(resp.Header.Get(Config.Website.Cache.Indicator)))
128+
var indicValue string
129+
if Config.Website.Cache.Indicator == "" { // check if now a cache indicator exists
130+
customCacheHeader := strings.ToLower(Config.CacheHeader)
131+
for key, val := range resp.Header {
132+
switch strings.ToLower(key) {
133+
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:
134+
// CacheHeader flag might not be set (=> ""). Continue in this case
135+
if key == "" {
136+
continue
137+
}
138+
Config.Website.Cache.Indicator = key
139+
msg := fmt.Sprintf("%s: %s header was found: %s \n", req.URL, key, val)
140+
PrintVerbose(msg, NoColor, 1)
141+
addHitMissIndicatorMap(strings.ToLower(key))
142+
case "age":
143+
// only set it it wasn't set to x-cache or sth. similar beforehand
144+
if Config.Website.Cache.Indicator == "" {
145+
Config.Website.Cache.Indicator = key
146+
msg := fmt.Sprintf("%s: %s header was found: %s\n", req.URL, key, val)
147+
PrintVerbose(msg, NoColor, 1)
148+
addHitMissIndicatorMap(strings.ToLower("age"))
149+
}
150+
}
151+
}
152+
}
153+
154+
indicValue = strings.TrimSpace(strings.ToLower(resp.Header.Get(Config.Website.Cache.Indicator)))
155+
97156
// check if there's a cache hit and if the body didn't change (otherwise it could be a cached error page, for example)
98157
if checkCacheHit(indicValue) && string(body) == Config.Website.Body {
99158
repResult.Vulnerable = true

pkg/flags.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,13 @@ func ParseFlags(vers string) {
6767
appendBoolean(&generalOptions, &Config.Force,
6868
"force", "f", false, "Perform the tests no matter if there is a cache or even the cachebuster works or not")
6969
appendString(&generalOptions, &ignoreStatus,
70-
"ignorestatus", "is", "", "Specify a custom cache header")
70+
"ignorestatus", "is", "", "Ignore a specific status code for cache poisoning")
7171
appendInt(&generalOptions, &Config.CLDiff,
7272
"contentlengthdifference", "cldiff", 0, "Threshold for reporting possible Finding, when 'poisoned' response differs more from the original length. Default is 0 (don't check)")
7373
appendInt(&generalOptions, &Config.HMDiff,
7474
"hitmissdifference", "hmdiff", 30, "Threshold for time difference between cache hit and cache miss responses. Default is 30")
75+
appendBoolean(&generalOptions, &Config.SkipTimebased,
76+
"skiptimebased", "stime", false, "Skip checking if a repsonse gets cached by measuring time differences")
7577
appendString(&generalOptions, &Config.CacheHeader,
7678
"cacheheader", "ch", "", "Specify a custom cache header")
7779
appendBoolean(&generalOptions, &Config.DisableColor,

pkg/recon.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,6 @@ func CheckCache(stat string) (CacheStruct, bool, []error) {
158158
cache.Indicator = key
159159
msg := fmt.Sprintf("%s header was found: %s\n", key, val)
160160
PrintVerbose(msg, NoColor, 1)
161-
if cache.Indicator == "" {
162-
cache.Indicator = "age"
163-
}
164161
addHitMissIndicatorMap(strings.ToLower("age"))
165162
}
166163
}
@@ -355,6 +352,10 @@ func cachebusterCookie(cache *CacheStruct) []error {
355352

356353
if cache.Indicator == "" {
357354
// No Cache Indicator was found. So time will be used as Indicator
355+
if Config.SkipTimebased {
356+
continue
357+
}
358+
358359
var newCookie http.Cookie
359360
var cb string
360361
for ii := 0; ii < 5*2; ii++ {
@@ -563,6 +564,10 @@ func cachebusterHeader(cache *CacheStruct) []error {
563564

564565
if cache.Indicator == "" {
565566
// No Cache Indicator was found. So time will be used as Indicator
567+
if Config.SkipTimebased {
568+
continue
569+
}
570+
566571
var cb string
567572
for ii := 0; ii < 5*2; ii++ {
568573
weburl := Config.Website.Url.String()
@@ -775,6 +780,10 @@ func cachebusterParameter(cache *CacheStruct) error {
775780

776781
if cache.Indicator == "" {
777782
// No Cache Indicator was found. So time will be used as Indicator
783+
if Config.SkipTimebased {
784+
return nil
785+
}
786+
778787
var urlCb string
779788
for i := 0; i < 5*2; i++ {
780789
if i%2 == 0 {
@@ -958,6 +967,10 @@ func cachebusterHTTPMethod(cache *CacheStruct) []error {
958967

959968
if cache.Indicator == "" {
960969
// No Cache Indicator was found. So time will be used as Indicator
970+
if Config.SkipTimebased {
971+
continue
972+
}
973+
961974
skip := false
962975
for ii := 0; ii < 5*2; ii++ {
963976
weburl := Config.Website.Url.String()

web-cache-vulnerability-scanner.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"golang.org/x/net/http2"
1616
)
1717

18-
const version = "1.2.1"
18+
const version = "1.3.0"
1919

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

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

0 commit comments

Comments
 (0)