diff --git a/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeception.ql b/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeception.ql new file mode 100644 index 000000000000..6c4b7c029bdc --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeception.ql @@ -0,0 +1,19 @@ +/** + * @name Web Cache Deception in Express + * @description A caching system has been detected on the application and is vulnerable to web cache deception. By manipulating the URL it is possible to force the application to cache pages that are only accessible by an authenticated user. Once cached, these pages can be accessed by an unauthenticated user. + * @kind problem + * @problem.severity error + * @security-severity 9 + * @precision medium + * @id js/web-cache-deception-express + * @tags javascript + * cwe-525 + * bug + */ + +import javascript +import WebCacheDeceptionLib + +from WebCacheDeception::Sink httpHandleFuncCall +where httpHandleFuncCall.toString().matches("%*%") +select httpHandleFuncCall, httpHandleFuncCall + " is used as wildcard endpoint." diff --git a/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeceptionBad.js b/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeceptionBad.js new file mode 100644 index 000000000000..13b5099cd665 --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeceptionBad.js @@ -0,0 +1,12 @@ +const express = require('express') +const app = express() +port = 3000 + +app.get('/test*', (req, res) => { + res.send('test') +}) + + +app.listen(port, () => { + console.log(`Example app listening on port ${port}`) +}) \ No newline at end of file diff --git a/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeceptionGood.js b/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeceptionGood.js new file mode 100644 index 000000000000..0b643703f74a --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeceptionGood.js @@ -0,0 +1,12 @@ +const express = require('express') +const app = express() +port = 3000 + +app.get('/test', (req, res) => { + res.send('test') +}) + + +app.listen(port, () => { + console.log(`Example app listening on port ${port}`) +}) \ No newline at end of file diff --git a/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeceptionLib.qll b/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeceptionLib.qll new file mode 100644 index 000000000000..e9b612be1ecb --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-525/WebCacheDeceptionLib.qll @@ -0,0 +1,16 @@ +import javascript +import StringOps + +module WebCacheDeception { + abstract class Sink extends DataFlow::Node { } + + private class Express extends Sink { + Express() { + exists(Import is | is.getImportedModuleNode().getASuccessor().toString() = "express") and + exists(DataFlow::CallNode m | + m.getAMethodCall().getArgument(0).getStringValue().matches("%*") and + this = m.getAMethodCall().getArgument(0) + ) + } + } +} diff --git a/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.expected b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.expected new file mode 100644 index 000000000000..f37ea991c303 --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.expected @@ -0,0 +1 @@ +| WebCacheDeceptionBad.js:5:9:5:16 | '/test*' | '/test*' is used as wildcard endpoint. | diff --git a/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.qhelp b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.qhelp new file mode 100644 index 000000000000..f60fea4ec8bb --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.qhelp @@ -0,0 +1,35 @@ + + + +

+ Web 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. +

+
+ +

+ To 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. +

+
+ +

+ Vulnerable 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. +

+ +
+ +

+ Secure 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. +

+ +
+ +
  • + OWASP Web Cache Deception Attack: + Understanding Web Cache Deception Attacks +
  • + +
    +
    \ No newline at end of file diff --git a/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.ql b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.ql new file mode 100644 index 000000000000..6c4b7c029bdc --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.ql @@ -0,0 +1,19 @@ +/** + * @name Web Cache Deception in Express + * @description A caching system has been detected on the application and is vulnerable to web cache deception. By manipulating the URL it is possible to force the application to cache pages that are only accessible by an authenticated user. Once cached, these pages can be accessed by an unauthenticated user. + * @kind problem + * @problem.severity error + * @security-severity 9 + * @precision medium + * @id js/web-cache-deception-express + * @tags javascript + * cwe-525 + * bug + */ + +import javascript +import WebCacheDeceptionLib + +from WebCacheDeception::Sink httpHandleFuncCall +where httpHandleFuncCall.toString().matches("%*%") +select httpHandleFuncCall, httpHandleFuncCall + " is used as wildcard endpoint." diff --git a/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.qlref b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.qlref new file mode 100644 index 000000000000..92bb095ef462 --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeception.qlref @@ -0,0 +1 @@ +experimental/Security/CWE-525/WebCacheDeception.ql \ No newline at end of file diff --git a/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeceptionBad.js b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeceptionBad.js new file mode 100644 index 000000000000..5310a59f2caf --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeceptionBad.js @@ -0,0 +1,11 @@ +const express = require('express') +const app = express() +port = 3000 + +app.get('/test*', (req, res) => { + res.send('test') +}) + +app.listen(port, () => { + console.log(`Example app listening on port ${port}`) +}) \ No newline at end of file diff --git a/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeceptionGood.js b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeceptionGood.js new file mode 100644 index 000000000000..0b643703f74a --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeceptionGood.js @@ -0,0 +1,12 @@ +const express = require('express') +const app = express() +port = 3000 + +app.get('/test', (req, res) => { + res.send('test') +}) + + +app.listen(port, () => { + console.log(`Example app listening on port ${port}`) +}) \ No newline at end of file diff --git a/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeceptionLib.qll b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeceptionLib.qll new file mode 100644 index 000000000000..e9b612be1ecb --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-525/WebCacheDeceptionLib.qll @@ -0,0 +1,16 @@ +import javascript +import StringOps + +module WebCacheDeception { + abstract class Sink extends DataFlow::Node { } + + private class Express extends Sink { + Express() { + exists(Import is | is.getImportedModuleNode().getASuccessor().toString() = "express") and + exists(DataFlow::CallNode m | + m.getAMethodCall().getArgument(0).getStringValue().matches("%*") and + this = m.getAMethodCall().getArgument(0) + ) + } + } +}