Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions change-notes/1.20/analysis-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

* Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following features:
- client-side code, for example [React](https://reactjs.org/)
- cookies and webstorage, for example [js-cookie](https://github.com/js-cookie/js-cookie)
- server-side code, for example [hapi](https://hapijs.com/)

* The taint tracking library now recognizes flow through persistent storage, this may give more results for the security queries.

## New queries

| **Query** | **Tags** | **Purpose** |
Expand Down
1 change: 1 addition & 0 deletions javascript/ql/src/javascript.qll
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import semmle.javascript.frameworks.Azure
import semmle.javascript.frameworks.Babel
import semmle.javascript.frameworks.ComposedFunctions
import semmle.javascript.frameworks.ClientRequests
import semmle.javascript.frameworks.CookieLibraries
import semmle.javascript.frameworks.Credentials
import semmle.javascript.frameworks.CryptoLibraries
import semmle.javascript.frameworks.DigitalOcean
Expand Down
24 changes: 24 additions & 0 deletions javascript/ql/src/semmle/javascript/Concepts.qll
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,27 @@ abstract class DatabaseAccess extends DataFlow::Node {
/** Gets an argument to this database access that is interpreted as a query. */
abstract DataFlow::Node getAQueryArgument();
}

/**
* A data flow node that reads persistent data.
*/
abstract class PersistentReadAccess extends DataFlow::Node {

/**
* Gets a corresponding persistent write, if any.
*/
abstract PersistentWriteAccess getAWrite();

}

/**
* A data flow node that writes persistent data.
*/
abstract class PersistentWriteAccess extends DataFlow::Node {

/**
* Gets the data flow node corresponding to the written value.
*/
abstract DataFlow::Node getValue();

}
18 changes: 18 additions & 0 deletions javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,24 @@ module TaintTracking {
}
}

/**
* A taint propagating data flow edge through persistent storage.
*/
private class StorageTaintStep extends AdditionalTaintStep {
Comment thread
This conversation was marked as resolved.

PersistentReadAccess read;

StorageTaintStep() {
this = read
}

override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = read.getAWrite().getValue() and
succ = read
}

}

/**
* A taint propagating data flow edge caused by the builtin array functions.
*/
Expand Down
93 changes: 93 additions & 0 deletions javascript/ql/src/semmle/javascript/frameworks/CookieLibraries.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@

/**
* Provides classes for reasoning about cookies.
*/

import javascript

/**
* A model of the `js-cookie` library (https://github.com/js-cookie/js-cookie).
*/
private module JsCookie {
/**
* Gets a function call that invokes method `name` of the `js-cookie` library.
*/
DataFlow::CallNode libMemberCall(string name) {
result = DataFlow::globalVarRef("Cookie").getAMemberCall(name) or
result = DataFlow::globalVarRef("Cookie").getAMemberCall("noConflict").getAMemberCall(name) or
result = DataFlow::moduleMember("js-cookie", name).getACall()
}

class ReadAccess extends PersistentReadAccess, DataFlow::CallNode {
ReadAccess() { this = libMemberCall("get") }

override PersistentWriteAccess getAWrite() {
getArgument(0).mayHaveStringValue(result.(WriteAccess).getKey())
}
}

class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
WriteAccess() { this = libMemberCall("set") }

string getKey() { getArgument(0).mayHaveStringValue(result) }

override DataFlow::Node getValue() { result = getArgument(1) }
}
}

/**
* A model of the `browser-cookies` library (https://github.com/voltace/browser-cookies).
*/
private module BrowserCookies {
/**
* Gets a function call that invokes method `name` of the `browser-cookies` library.
*/
DataFlow::CallNode libMemberCall(string name) {
result = DataFlow::moduleMember("browser-cookies", name).getACall()
}

class ReadAccess extends PersistentReadAccess, DataFlow::CallNode {
ReadAccess() { this = libMemberCall("get") }

override PersistentWriteAccess getAWrite() {
getArgument(0).mayHaveStringValue(result.(WriteAccess).getKey())
}
}

class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
WriteAccess() { this = libMemberCall("set") }

string getKey() { getArgument(0).mayHaveStringValue(result) }

override DataFlow::Node getValue() { result = getArgument(1) }
}
}

/**
* A model of the `cookie` library (https://github.com/jshttp/cookie).
*/
private module LibCookie {
/**
* Gets a function call that invokes method `name` of the `cookie` library.
*/
DataFlow::CallNode libMemberCall(string name) {
result = DataFlow::moduleMember("cookie", name).getACall()
}

class ReadAccess extends PersistentReadAccess {
string key;
ReadAccess() { this = libMemberCall("parse").getAPropertyRead(key) }

override PersistentWriteAccess getAWrite() {
key = result.(WriteAccess).getKey()
}
}

class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
WriteAccess() { this = libMemberCall("serialize") }

string getKey() { getArgument(0).mayHaveStringValue(result) }

override DataFlow::Node getValue() { result = getArgument(1) }
}
}
38 changes: 38 additions & 0 deletions javascript/ql/src/semmle/javascript/security/dataflow/DOM.qll
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,44 @@ class WebStorageWrite extends Expr {
}
}

/**
* Persistent storage through web storage such as `localStorage` or `sessionStorage`.
*/
private module PersistentWebStorage {
private DataFlow::SourceNode webStorage(string kind) {
(kind = "localStorage" or kind = "sessionStorage") and
result = DataFlow::globalVarRef(kind)
}

/**
* A read access.
*/
class ReadAccess extends PersistentReadAccess, DataFlow::CallNode {
string kind;

ReadAccess() { this = webStorage(kind).getAMethodCall("getItem") }

override PersistentWriteAccess getAWrite() {
getArgument(0).mayHaveStringValue(result.(WriteAccess).getKey()) and
result.(WriteAccess).getKind() = kind
}
}

/**
* A write access.
*/
class WriteAccess extends PersistentWriteAccess, DataFlow::CallNode {
string kind;

WriteAccess() { this = webStorage(kind).getAMethodCall("setItem") }

string getKey() { getArgument(0).mayHaveStringValue(result) }

string getKind() { result = kind }

override DataFlow::Node getValue() { result = getArgument(1) }
}
}
/**
* An event handler that handles `postMessage` events.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
| persistence.js:3:5:3:33 | localSt ... prop1') |
| persistence.js:6:5:6:35 | session ... prop2') |
| persistence.js:10:5:10:33 | localSt ... prop4') |
| persistence.js:13:5:13:35 | session ... prop5') |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from PersistentReadAccess read
select read
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
| persistence.js:3:5:3:33 | localSt ... prop1') | persistence.js:2:5:2:37 | localSt ... 1', v1) |
| persistence.js:6:5:6:35 | session ... prop2') | persistence.js:5:5:5:39 | session ... 2', v2) |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from PersistentReadAccess read
select read, read.getAWrite()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
| persistence.js:2:5:2:37 | localSt ... 1', v1) | persistence.js:2:35:2:36 | v1 |
| persistence.js:5:5:5:39 | session ... 2', v2) | persistence.js:5:37:5:38 | v2 |
| persistence.js:8:5:8:37 | localSt ... 3', v3) | persistence.js:8:35:8:36 | v3 |
| persistence.js:12:5:12:37 | localSt ... 5', v5) | persistence.js:12:35:12:36 | v5 |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from PersistentWriteAccess write
select write, write.getValue()
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(function(){
localStorage.setItem('prop1', v1);
localStorage.getItem('prop1');

sessionStorage.setItem('prop2', v2);
sessionStorage.getItem('prop2');

localStorage.setItem('prop3', v3);

localStorage.getItem('prop4');

localStorage.setItem('prop5', v5);
sessionStorage.getItem('prop5');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| tst.js:7:2:7:21 | js_cookie.get('key') |
| tst.js:12:2:12:27 | browser ... ('key') |
| tst.js:18:2:18:22 | cookie. ... ['key'] |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from PersistentReadAccess read
select read
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| tst.js:7:2:7:21 | js_cookie.get('key') | tst.js:6:2:6:30 | js_cook ... value') |
| tst.js:12:2:12:27 | browser ... ('key') | tst.js:11:2:11:36 | browser ... value') |
| tst.js:18:2:18:22 | cookie. ... ['key'] | tst.js:17:2:17:33 | cookie. ... value') |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from PersistentReadAccess read
select read, read.getAWrite()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| tst.js:6:2:6:30 | js_cook ... value') | tst.js:6:23:6:29 | 'value' |
| tst.js:11:2:11:36 | browser ... value') | tst.js:11:29:11:35 | 'value' |
| tst.js:17:2:17:33 | cookie. ... value') | tst.js:17:26:17:32 | 'value' |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import javascript

from PersistentWriteAccess write
select write, write.getValue()
19 changes: 19 additions & 0 deletions javascript/ql/test/library-tests/frameworks/CookieLibraries/tst.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const js_cookie = require('js-cookie'),
browser_cookies = require('browser-cookies'),
cookie = require('cookie');

(function() {
js_cookie.set('key', 'value');
js_cookie.get('key');
});

(function() {
browser_cookies.set('key', 'value');
browser_cookies.get('key');
});


(function() {
cookie.serialize('key', 'value');
cookie.parse()['key'];
});
10 changes: 10 additions & 0 deletions javascript/ql/test/query-tests/Security/CWE-079/StoredXss.expected
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ nodes
| react-native.js:7:17:7:33 | req.param("code") |
| react-native.js:8:18:8:24 | tainted |
| react-native.js:9:27:9:33 | tainted |
| stored-xss.js:2:39:2:55 | document.location |
| stored-xss.js:2:39:2:62 | documen ... .search |
| stored-xss.js:3:35:3:51 | document.location |
| stored-xss.js:3:35:3:58 | documen ... .search |
| stored-xss.js:5:20:5:52 | session ... ssion') |
| stored-xss.js:8:20:8:48 | localSt ... local') |
| string-manipulations.js:3:16:3:32 | document.location |
| string-manipulations.js:4:16:4:32 | document.location |
| string-manipulations.js:4:16:4:37 | documen ... on.href |
Expand Down Expand Up @@ -262,6 +268,10 @@ edges
| react-native.js:7:7:7:33 | tainted | react-native.js:8:18:8:24 | tainted |
| react-native.js:7:7:7:33 | tainted | react-native.js:9:27:9:33 | tainted |
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
| stored-xss.js:2:39:2:55 | document.location | stored-xss.js:2:39:2:62 | documen ... .search |
| stored-xss.js:2:39:2:62 | documen ... .search | stored-xss.js:5:20:5:52 | session ... ssion') |
| stored-xss.js:3:35:3:51 | document.location | stored-xss.js:3:35:3:58 | documen ... .search |
| stored-xss.js:3:35:3:58 | documen ... .search | stored-xss.js:8:20:8:48 | localSt ... local') |
| string-manipulations.js:4:16:4:32 | document.location | string-manipulations.js:4:16:4:37 | documen ... on.href |
| string-manipulations.js:5:16:5:32 | document.location | string-manipulations.js:5:16:5:37 | documen ... on.href |
| string-manipulations.js:5:16:5:37 | documen ... on.href | string-manipulations.js:5:16:5:47 | documen ... lueOf() |
Expand Down
12 changes: 12 additions & 0 deletions javascript/ql/test/query-tests/Security/CWE-079/Xss.expected
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ nodes
| react-native.js:7:17:7:33 | req.param("code") |
| react-native.js:8:18:8:24 | tainted |
| react-native.js:9:27:9:33 | tainted |
| stored-xss.js:2:39:2:55 | document.location |
| stored-xss.js:2:39:2:62 | documen ... .search |
| stored-xss.js:3:35:3:51 | document.location |
| stored-xss.js:3:35:3:58 | documen ... .search |
| stored-xss.js:5:20:5:52 | session ... ssion') |
| stored-xss.js:8:20:8:48 | localSt ... local') |
| string-manipulations.js:3:16:3:32 | document.location |
| string-manipulations.js:4:16:4:32 | document.location |
| string-manipulations.js:4:16:4:37 | documen ... on.href |
Expand Down Expand Up @@ -177,6 +183,10 @@ edges
| react-native.js:7:7:7:33 | tainted | react-native.js:8:18:8:24 | tainted |
| react-native.js:7:7:7:33 | tainted | react-native.js:9:27:9:33 | tainted |
| react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted |
| stored-xss.js:2:39:2:55 | document.location | stored-xss.js:2:39:2:62 | documen ... .search |
| stored-xss.js:2:39:2:62 | documen ... .search | stored-xss.js:5:20:5:52 | session ... ssion') |
| stored-xss.js:3:35:3:51 | document.location | stored-xss.js:3:35:3:58 | documen ... .search |
| stored-xss.js:3:35:3:58 | documen ... .search | stored-xss.js:8:20:8:48 | localSt ... local') |
| string-manipulations.js:4:16:4:32 | document.location | string-manipulations.js:4:16:4:37 | documen ... on.href |
| string-manipulations.js:5:16:5:32 | document.location | string-manipulations.js:5:16:5:37 | documen ... on.href |
| string-manipulations.js:5:16:5:37 | documen ... on.href | string-manipulations.js:5:16:5:47 | documen ... lueOf() |
Expand Down Expand Up @@ -296,6 +306,8 @@ edges
| nodemailer.js:13:11:13:69 | `Hi, yo ... sage}.` | nodemailer.js:13:50:13:66 | req.query.message | nodemailer.js:13:11:13:69 | `Hi, yo ... sage}.` | HTML injection vulnerability due to $@. | nodemailer.js:13:50:13:66 | req.query.message | user-provided value |
| react-native.js:8:18:8:24 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:8:18:8:24 | tainted | Cross-site scripting vulnerability due to $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value |
| react-native.js:9:27:9:33 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:9:27:9:33 | tainted | Cross-site scripting vulnerability due to $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value |
| stored-xss.js:5:20:5:52 | session ... ssion') | stored-xss.js:2:39:2:55 | document.location | stored-xss.js:5:20:5:52 | session ... ssion') | Cross-site scripting vulnerability due to $@. | stored-xss.js:2:39:2:55 | document.location | user-provided value |
| stored-xss.js:8:20:8:48 | localSt ... local') | stored-xss.js:3:35:3:51 | document.location | stored-xss.js:8:20:8:48 | localSt ... local') | Cross-site scripting vulnerability due to $@. | stored-xss.js:3:35:3:51 | document.location | user-provided value |
| string-manipulations.js:3:16:3:32 | document.location | string-manipulations.js:3:16:3:32 | document.location | string-manipulations.js:3:16:3:32 | document.location | Cross-site scripting vulnerability due to $@. | string-manipulations.js:3:16:3:32 | document.location | user-provided value |
| string-manipulations.js:4:16:4:37 | documen ... on.href | string-manipulations.js:4:16:4:32 | document.location | string-manipulations.js:4:16:4:37 | documen ... on.href | Cross-site scripting vulnerability due to $@. | string-manipulations.js:4:16:4:32 | document.location | user-provided value |
| string-manipulations.js:5:16:5:47 | documen ... lueOf() | string-manipulations.js:5:16:5:32 | document.location | string-manipulations.js:5:16:5:47 | documen ... lueOf() | Cross-site scripting vulnerability due to $@. | string-manipulations.js:5:16:5:32 | document.location | user-provided value |
Expand Down
9 changes: 9 additions & 0 deletions javascript/ql/test/query-tests/Security/CWE-079/stored-xss.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(function() {
sessionStorage.setItem('session', document.location.search);
localStorage.setItem('local', document.location.search);

$('myId').html(sessionStorage.getItem('session')); // NOT OK
$('myId').html(localStorage.getItem('session')); // OK
$('myId').html(sessionStorage.getItem('local')); // OK
$('myId').html(localStorage.getItem('local')); // NOT OK
});