documentation overhaul on client-exposed-cookie (and restricting it to server-side)

This commit is contained in:
Erik Krogh Kristensen
2021-10-06 15:01:25 +02:00
parent ab23ffff3d
commit 2cb3d2c53f
11 changed files with 116 additions and 72 deletions

View File

@@ -24,9 +24,25 @@ module CookieWrites {
/**
* Holds if the cookie is likely an authentication cookie or otherwise sensitive.
* Can never hold for client-side cookies. TODO: Or can it...?
* Can never hold for client-side cookies.
*/
abstract predicate isSensitive();
/**
* Holds if the cookie write happens on a server, that is `httpOnly` flag is relevant.
*/
predicate isServerSide() {
any() // holds by default. Client-side cookie writes should extend ClientSideCookieWrite.
}
}
/**
* A client-side write to a cookie.
*/
abstract class ClientSideCookieWrite extends CookieWrite {
final override predicate isHttpOnly() { none() }
final override predicate isServerSide() { none() }
}
/**
@@ -43,6 +59,7 @@ module CookieWrites {
/**
* A model of the `js-cookie` library (https://github.com/js-cookie/js-cookie).
*/
// TODO: Writes to document.cookie.
private module JsCookie {
/**
* Gets a function call that invokes method `name` of the `js-cookie` library.
@@ -61,7 +78,8 @@ private module JsCookie {
}
}
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode, CookieWrites::CookieWrite {
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode,
CookieWrites::ClientSideCookieWrite {
WriteAccess() { this = libMemberCall("set") }
string getKey() { getArgument(0).mayHaveStringValue(result) }
@@ -78,8 +96,6 @@ private module JsCookie {
this.getArgument(0).mayHaveStringValue(s)
), _)
}
override predicate isHttpOnly() { none() } // js-cookie is browser side library and doesn't support HttpOnly
}
}

View File

@@ -0,0 +1,42 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Authentication cookies stored by a server can be accessed by a client if the <code>httpOnly</code> flag is not set.
<p>
<p>
An attacker that manages a cross-site scripting (XSS) attack can read the cookie and hijack the session.
</p>
</overview>
<recommendation>
<p>
Set the <code>httpOnly</code> flag on all cookies that are not needed by the client.
</p>
</recommendation>
<references>
<example>
<p>
The following example stores an authentication token in a cookie that can
viewed by the client.
</p>
<sample src="examples/ClientExposedCookieGood.js"/>
<p>
To force the cookie to be transmitted using SSL, set the <code>secure</code>
attribute on the cookie.
</p>
<sample src="examples/ClientExposedCookieBad.js"/>
</example>
<references>
<li>ExpressJS: <a href="https://expressjs.com/en/advanced/best-practice-security.html#use-cookies-securely">Use cookies securely</a>.</li>
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html#set-cookie-flags-appropriately">Set cookie flags appropriately</a>.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Sensitive server cookie exposed to the client
* @description Sensitive cookies set by a server can be read by the client if the `httpOnly` flag is not set.
* @kind problem
* @problem.severity warning
* @security-severity 5.0
* @precision high
* @id js/client-exposed-cookie
* @tags security
* external/cwe/cwe-1004
*/
import javascript
from CookieWrites::CookieWrite cookie
where
cookie.isSensitive() and
cookie.isServerSide() and
not cookie.isHttpOnly()
select cookie, "Sensitive server cookie is missing 'httpOnly' flag."

View File

@@ -1,25 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Cookies without <code>HttpOnly</code> flag are accessible to JavaScript running in the same origin. In case of
Cross-Site Scripting (XSS) vulnerability the cookie can be stolen by malicious script.</p>
</overview>
<recommendation>
<p>Protect sensitive cookies, such as those related to authentication, by setting <code>HttpOnly</code> to <code>true</code> to make
them not accessible to JavaScript.</p>
</recommendation>
<references>
<li>Production Best Practices: Security:<a href="https://expressjs.com/en/advanced/best-practice-security.html#use-cookies-securely">Use cookies securely</a>.</li>
<li>NodeJS security cheat sheet:<a href="https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html#set-cookie-flags-appropriately">Set cookie flags appropriately</a>.</li>
<li>express-session:<a href="https://github.com/expressjs/session#cookiehttponly">cookie.httpOnly</a>.</li>
<li>cookie-session:<a href="https://github.com/expressjs/cookie-session#cookie-options">Cookie Options</a>.</li>
<li><a href="https://expressjs.com/en/api.html#res.cookie">express response.cookie</a>.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie</a>.</li>
</references>
</qhelp>

View File

@@ -1,23 +0,0 @@
/**
* @name 'HttpOnly' attribute is not set to true
* @description Omitting the 'HttpOnly' attribute for security sensitive cookie data allows
* malicious JavaScript to steal it in case of XSS vulnerabilities. Always set
* 'HttpOnly' to 'true' for authentication related cookies to make them
* inaccessible from JavaScript.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/cookie-httponly-not-set
* @tags security
* external/cwe/cwe-1004
*/
import javascript
from DataFlow::Node node
where
// TODO: Give all descriptions, qlhelp, qldocs, an overhaul. Consider precisions, severity, cwes.
exists(CookieWrites::CookieWrite cookie | cookie = node |
cookie.isSensitive() and not cookie.isHttpOnly()
)
select node, "Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie."

View File

@@ -0,0 +1,7 @@
const http = require('http');
const server = http.createServer((req, res) => {
res.setHeader("Set-Cookie", `authKey=${makeAuthkey()}`);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h2>Hello world</h2>');
});

View File

@@ -0,0 +1,7 @@
const http = require('http');
const server = http.createServer((req, res) => {
res.setHeader("Set-Cookie", `authKey=${makeAuthkey()}; secure; httpOnly`);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h2>Hello world</h2>');
});

View File

@@ -0,0 +1,19 @@
| tst-httpOnly.js:11:9:15:2 | session ... BAD\\n}) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:29:9:29:21 | session(sess) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:38:9:38:22 | session(sess2) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:47:9:47:22 | session(sess3) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:51:9:55:2 | session ... BAD\\n}) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:68:5:73:10 | res.coo ... }) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:78:5:81:10 | res.coo ... }) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:101:5:101:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:109:5:109:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:118:5:118:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:137:5:137:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:148:5:148:41 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:159:5:159:43 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:170:5:170:40 | res.coo ... ptions) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:209:37:209:51 | "authKey=ninja" | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:229:38:229:52 | "authKey=ninja" | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:289:37:289:59 | `authKe ... {attr}` | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:303:9:307:2 | session ... BAD\\n}) | Sensitive server cookie is missing 'httpOnly' flag. |
| tst-httpOnly.js:320:9:324:2 | session ... tter\\n}) | Sensitive server cookie is missing 'httpOnly' flag. |

View File

@@ -0,0 +1 @@
Security/CWE-1004/ClientExposedCookie.ql

View File

@@ -1,19 +0,0 @@
| tst-httpOnly.js:11:9:15:2 | session ... BAD\\n}) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:29:9:29:21 | session(sess) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:38:9:38:22 | session(sess2) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:47:9:47:22 | session(sess3) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:51:9:55:2 | session ... BAD\\n}) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:68:5:73:10 | res.coo ... }) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:78:5:81:10 | res.coo ... }) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:101:5:101:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:109:5:109:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:118:5:118:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:137:5:137:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:148:5:148:41 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:159:5:159:43 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:170:5:170:40 | res.coo ... ptions) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:209:37:209:51 | "authKey=ninja" | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:229:38:229:52 | "authKey=ninja" | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:289:37:289:59 | `authKe ... {attr}` | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:303:9:307:2 | session ... BAD\\n}) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |
| tst-httpOnly.js:320:9:324:2 | session ... tter\\n}) | Cookie attribute 'HttpOnly' is not set to true for this sensitive cookie. |

View File

@@ -1 +0,0 @@
Security/CWE-1004/CookieWithoutHttpOnly.ql