New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Web Cache Deception Vulnerability on Go Frameworks #15057
base: main
Are you sure you want to change the base?
Conversation
|
Probably I should not push the "vendor" directory. If you approve it I can remove it. |
|
Please use depstubber to create just the stubs that are needed and put those in the vendor directory. |
|
Updated @owen-mc |
|
QHelp previews: go/ql/src/experimental/CWE-525/WebCacheDeception.qhelpUnknown queryWeb Cache Deception is a security vulnerability where an attacker tricks a web server into caching sensitive information and then accesses that cached data. This attack exploits certain behaviors in caching mechanisms by requesting URLs that trick the server into thinking that a non-cachable page is cachable. If a user then accesses sensitive information on these pages, it could be cached and later retrieved by the attacker. RecommendationTo prevent Web Cache Deception attacks, web applications should clearly define cacheable and non-cacheable resources. Implementing strict cache controls and validating requested URLs can mitigate the risk of sensitive data being cached. ExampleVulnerable code example: A web server is configured to cache all responses ending in '.css'. An attacker requests 'profile.css', and the server processes 'profile', a sensitive page, and caches it. package bad
import (
"fmt"
"html/template"
"log"
"net/http"
"os/exec"
"strings"
"sync"
)
var sessionMap = make(map[string]string)
var (
templateCache = make(map[string]*template.Template)
mutex = &sync.Mutex{}
)
type Lists struct {
Uid string
UserName string
UserLists []string
ReadFile func(filename string) string
}
func parseTemplateFile(templateName string, tmplFile string) (*template.Template, error) {
mutex.Lock()
defer mutex.Unlock()
// Check if the template is already cached
if cachedTemplate, ok := templateCache[templateName]; ok {
fmt.Println("cached")
return cachedTemplate, nil
}
// Parse and store the template in the cache
parsedTemplate, _ := template.ParseFiles(tmplFile)
fmt.Println("not cached")
templateCache[templateName] = parsedTemplate
return parsedTemplate, nil
}
func ShowAdminPageCache(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
fmt.Println("cache called")
sessionMap[r.RequestURI] = "admin"
// Check if a session value exists
if _, ok := sessionMap[r.RequestURI]; ok {
cmd := "mysql -h mysql -u root -prootwolf -e 'select id,name,mail,age,created_at,updated_at from vulnapp.user where name not in (\"" + "admin" + "\");'"
// mysql -h mysql -u root -prootwolf -e 'select id,name,mail,age,created_at,updated_at from vulnapp.user where name not in ("test");--';echo");'
fmt.Println(cmd)
res, err := exec.Command("sh", "-c", cmd).Output()
if err != nil {
fmt.Println("err : ", err)
}
splitedRes := strings.Split(string(res), "\n")
p := Lists{Uid: "1", UserName: "admin", UserLists: splitedRes}
parsedTemplate, _ := parseTemplateFile("page", "./views/admin/userlists.gtpl")
w.Header().Set("Cache-Control", "no-store, no-cache")
err = parsedTemplate.Execute(w, p)
}
} else {
http.NotFound(w, nil)
}
}
func main() {
fmt.Println("Vulnapp server listening : 1337")
http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/"))))
http.HandleFunc("/adminusers/", ShowAdminPageCache)
err := http.ListenAndServe(":1337", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}ExampleSecure code example: The server is configured with strict cache controls and URL validation, preventing caching of dynamic or sensitive pages regardless of their URL pattern. package good
import (
"fmt"
"html/template"
"log"
"net/http"
"os/exec"
"strings"
"sync"
)
var sessionMap = make(map[string]string)
var (
templateCache = make(map[string]*template.Template)
mutex = &sync.Mutex{}
)
type Lists struct {
Uid string
UserName string
UserLists []string
ReadFile func(filename string) string
}
func parseTemplateFile(templateName string, tmplFile string) (*template.Template, error) {
mutex.Lock()
defer mutex.Unlock()
// Check if the template is already cached
if cachedTemplate, ok := templateCache[templateName]; ok {
fmt.Println("cached")
return cachedTemplate, nil
}
// Parse and store the template in the cache
parsedTemplate, _ := template.ParseFiles(tmplFile)
fmt.Println("not cached")
templateCache[templateName] = parsedTemplate
return parsedTemplate, nil
}
func ShowAdminPageCache(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
fmt.Println("cache called")
sessionMap[r.RequestURI] = "admin"
// Check if a session value exists
if _, ok := sessionMap[r.RequestURI]; ok {
cmd := "mysql -h mysql -u root -prootwolf -e 'select id,name,mail,age,created_at,updated_at from vulnapp.user where name not in (\"" + "admin" + "\");'"
// mysql -h mysql -u root -prootwolf -e 'select id,name,mail,age,created_at,updated_at from vulnapp.user where name not in ("test");--';echo");'
fmt.Println(cmd)
res, err := exec.Command("sh", "-c", cmd).Output()
if err != nil {
fmt.Println("err : ", err)
}
splitedRes := strings.Split(string(res), "\n")
p := Lists{Uid: "1", UserName: "admin", UserLists: splitedRes}
parsedTemplate, _ := parseTemplateFile("page", "./views/admin/userlists.gtpl")
w.Header().Set("Cache-Control", "no-store, no-cache")
err = parsedTemplate.Execute(w, p)
}
} else {
http.NotFound(w, nil)
}
}
func main() {
fmt.Println("Vulnapp server listening : 1337")
http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/"))))
http.HandleFunc("/adminusers", ShowAdminPageCache)
err := http.ListenAndServe(":1337", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}ExampleVulnerable code example: The server is configured with strict cache controls and URL validation, preventing caching of dynamic or sensitive pages regardless of their URL pattern. package fiber
import (
"fmt"
"log"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
log.Println("We are logging in Golang!")
// GET /api/register
app.Get("/api/*", func(c *fiber.Ctx) error {
msg := fmt.Sprintf("✋")
return c.SendString(msg) // => ✋ register
})
app.Post("/api/*", func(c *fiber.Ctx) error {
msg := fmt.Sprintf("✋")
return c.SendString(msg) // => ✋ register
})
// GET /flights/LAX-SFO
app.Get("/flights/:from-:to", func(c *fiber.Ctx) error {
msg := fmt.Sprintf("💸 From: %s, To: %s", c.Params("from"), c.Params("to"))
return c.SendString(msg) // => 💸 From: LAX, To: SFO
})
// GET /dictionary.txt
app.Get("/:file.:ext", func(c *fiber.Ctx) error {
msg := fmt.Sprintf("📃 %s.%s", c.Params("file"), c.Params("ext"))
return c.SendString(msg) // => 📃 dictionary.txt
})
log.Fatal(app.Listen(":3000"))
}ExampleVulnerable code example: The server is configured with strict cache controls and URL validation, preventing caching of dynamic or sensitive pages regardless of their URL pattern. package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
http.ListenAndServe(":3000", r)
}References
|
| from DataFlow::CallNode httpHandleFuncCall, ImportSpec importSpec | ||
| where | ||
| importSpec.getPath() = "github.com/gofiber/fiber/v2" and | ||
| httpHandleFuncCall.getCall().getArgument(0).toString().matches("%/*%") and |
Check warning
Code scanning / CodeQL
Using 'toString' in query logic Warning
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getStringValue does not works for me, so I use toString
| where | ||
| importSpec.getPath() = "github.com/gofiber/fiber/v2" and | ||
| httpHandleFuncCall.getCall().getArgument(0).toString().matches("%/*%") and | ||
| not httpHandleFuncCall.getCall().getArgument(0).toString().matches("%$%") and |
Check warning
Code scanning / CodeQL
Using 'toString' in query logic Warning
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getStringValue does not works for me, so I use toString
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What string does getStringValue give you? Or does it not have a value in a case that you care about? Queries cannot use toString.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know why but I did not get the endpoint which is "/*". I use getType() for getting type but it is not string for CodeQL.
Code Sample:
r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
When I use getStringValue it did not get this argument.
| from DataFlow::CallNode httpHandleFuncCall, ImportSpec importSpec | ||
| where | ||
| importSpec.getPath() = "github.com/go-chi/chi/v5" and | ||
| httpHandleFuncCall.getCall().getArgument(0).toString().matches("%/*%") and |
Check warning
Code scanning / CodeQL
Using 'toString' in query logic Warning
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getStringValue does not works for me, so I use toString
| where | ||
| importSpec.getPath() = "github.com/go-chi/chi/v5" and | ||
| httpHandleFuncCall.getCall().getArgument(0).toString().matches("%/*%") and | ||
| not httpHandleFuncCall.getCall().getArgument(0).toString().matches("%$%") and |
Check warning
Code scanning / CodeQL
Using 'toString' in query logic Warning
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getStringValue does not works for me, so I use toString
|
fixed the qhelp issue and tests. |


Pull Request: Add Web Cache Deception Query for CodeQL
Overview
This pull request introduces a new CodeQL query to detect potential Web Cache Deception vulnerabilities in web applications. Web Cache Deception is a security vulnerability where attackers trick a server into caching sensitive information, which they can later access. This query aims to identify code patterns that might make an application susceptible to this type of attack.
Changes Introduced
WebCacheDeceptionFiber.ql- Detects patterns where web applications might cache sensitive information inadvertently.WebCacheDeceptionGoChi.ql- Detects patterns where web applications might cache sensitive information inadvertently./examplesdirectory, demonstrating both vulnerable (bad) and secure (good) coding practices related to caching.Implementation Details
Testing and Validation
Future Work
References