mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
JS: add query: js/stored-xss
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
+ semmlecode-javascript-queries/Security/CWE-022/TaintedPath.ql: /Security/CWE/CWE-022
|
||||
+ semmlecode-javascript-queries/Security/CWE-078/CommandInjection.ql: /Security/CWE/CWE-078
|
||||
+ semmlecode-javascript-queries/Security/CWE-079/ReflectedXss.ql: /Security/CWE/CWE-079
|
||||
+ semmlecode-javascript-queries/Security/CWE-079/StoredXss.ql: /Security/CWE/CWE-079
|
||||
+ semmlecode-javascript-queries/Security/CWE-079/Xss.ql: /Security/CWE/CWE-079
|
||||
+ semmlecode-javascript-queries/Security/CWE-089/SqlInjection.ql: /Security/CWE/CWE-089
|
||||
+ semmlecode-javascript-queries/Security/CWE-094/CodeInjection.ql: /Security/CWE/CWE-094
|
||||
|
||||
63
javascript/ql/src/Security/CWE-079/StoredXss.qhelp
Normal file
63
javascript/ql/src/Security/CWE-079/StoredXss.qhelp
Normal file
@@ -0,0 +1,63 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
|
||||
Directly using uncontrolled stored value (for example, file names) to
|
||||
create HTML content without properly sanitizing the input first,
|
||||
allows for a cross-site scripting vulnerability.
|
||||
|
||||
</p>
|
||||
<p>
|
||||
|
||||
This kind of vulnerability is also called <i>stored</i> cross-site
|
||||
scripting, to distinguish it from other types of cross-site scripting.
|
||||
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
|
||||
To guard against cross-site scripting, consider using contextual
|
||||
output encoding/escaping before using uncontrolled stored values to
|
||||
create HTML content, or one of the other solutions that are mentioned
|
||||
in the references.
|
||||
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
|
||||
The following example code writes file names directly to a HTTP
|
||||
response. This leaves the website vulnerable to cross-site scripting,
|
||||
if an attacker can choose the file names on the disk.
|
||||
|
||||
</p>
|
||||
<sample src="examples/StoredXss.js" />
|
||||
<p>
|
||||
Sanitizing the file names prevents the vulnerability:
|
||||
</p>
|
||||
<sample src="examples/StoredXssGood.js" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet">XSS
|
||||
(Cross Site Scripting) Prevention Cheat Sheet</a>.
|
||||
</li>
|
||||
<li>
|
||||
OWASP
|
||||
<a href="https://www.owasp.org/index.php/Types_of_Cross-Site_Scripting">Types of Cross-Site
|
||||
Scripting</a>.
|
||||
</li>
|
||||
<li>
|
||||
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
20
javascript/ql/src/Security/CWE-079/StoredXss.ql
Normal file
20
javascript/ql/src/Security/CWE-079/StoredXss.ql
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Stored cross-site scripting
|
||||
* @description Using uncontrolled stored values in HTML allows for
|
||||
* a stored cross-site scripting vulnerability.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/stored-xss
|
||||
* @tags security
|
||||
* external/cwe/cwe-079
|
||||
* external/cwe/cwe-116
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.StoredXss::StoredXss
|
||||
|
||||
from Configuration xss, DataFlow::Node source, DataFlow::Node sink
|
||||
where xss.hasFlow(source, sink)
|
||||
select sink, "Stored cross-site scripting vulnerability due to $@.",
|
||||
source, "stored value"
|
||||
13
javascript/ql/src/Security/CWE-079/examples/StoredXss.js
Normal file
13
javascript/ql/src/Security/CWE-079/examples/StoredXss.js
Normal file
@@ -0,0 +1,13 @@
|
||||
var express = require('express'),
|
||||
fs = require('fs');
|
||||
|
||||
express().get('/list-directory', function(req, res) {
|
||||
fs.readdir('/public', function (error, fileNames) {
|
||||
var list = '<ul>';
|
||||
fileNames.forEach(fileName => {
|
||||
list += '<li>' + fileName '</li>'; // BAD: `fileName` can contain HTML elements
|
||||
});
|
||||
list += '</ul>'
|
||||
res.send(list);
|
||||
});
|
||||
});
|
||||
14
javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js
Normal file
14
javascript/ql/src/Security/CWE-079/examples/StoredXssGood.js
Normal file
@@ -0,0 +1,14 @@
|
||||
var express = require('express'),
|
||||
fs = require('fs'),
|
||||
escape = require('escape-html');
|
||||
|
||||
express().get('/list-directory', function(req, res) {
|
||||
fs.readdir('/public', function (error, fileNames) {
|
||||
var list = '<ul>';
|
||||
fileNames.forEach(fileName => {
|
||||
list += '<li>' + escape(fileName) '</li>'; // GOOD: escaped `fileName` can not contain HTML elements
|
||||
});
|
||||
list += '</ul>'
|
||||
res.send(list);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about stored
|
||||
* cross-site scripting vulnerabilities.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.RemoteFlowSources
|
||||
import semmle.javascript.security.dataflow.ReflectedXss as ReflectedXss
|
||||
import semmle.javascript.security.dataflow.DomBasedXss as DomBasedXss
|
||||
|
||||
module StoredXss {
|
||||
/**
|
||||
* A data flow source for XSS vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for XSS vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for XSS vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about XSS.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "StoredXss" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof Source
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink instanceof Sink
|
||||
}
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
super.isSanitizer(node) or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
}
|
||||
|
||||
/** A file name, considered as a flow source for stored XSS. */
|
||||
class FileNameSourceAsSource extends Source {
|
||||
FileNameSourceAsSource() {
|
||||
this instanceof FileNameSource
|
||||
}
|
||||
}
|
||||
|
||||
/** An ordinary XSS sink, considered as a flow sink for stored XSS. */
|
||||
class XssSinkAsSink extends Sink {
|
||||
XssSinkAsSink() {
|
||||
this instanceof ReflectedXss::ReflectedXss::Sink or
|
||||
this instanceof DomBasedXss::DomBasedXss::Sink
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
| xss-through-filenames.js:8:18:8:23 | files1 | Stored cross-site scripting vulnerability due to $@. | xss-through-filenames.js:7:43:7:48 | files1 | stored value |
|
||||
| xss-through-filenames.js:26:19:26:24 | files1 | Stored cross-site scripting vulnerability due to $@. | xss-through-filenames.js:25:43:25:48 | files1 | stored value |
|
||||
| xss-through-filenames.js:33:19:33:24 | files2 | Stored cross-site scripting vulnerability due to $@. | xss-through-filenames.js:25:43:25:48 | files1 | stored value |
|
||||
| xss-through-filenames.js:37:19:37:24 | files3 | Stored cross-site scripting vulnerability due to $@. | xss-through-filenames.js:25:43:25:48 | files1 | stored value |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-079/StoredXss.ql
|
||||
@@ -0,0 +1,40 @@
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
|
||||
var express = require('express');
|
||||
|
||||
express().get('/', function(req, res) {
|
||||
fs.readdir("/myDir", function (error, files1) {
|
||||
res.send(files1); // NOT OK
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The essence of a real world vulnerability.
|
||||
*/
|
||||
http.createServer(function (req, res) {
|
||||
|
||||
function format(files2) {
|
||||
var files3 = [];
|
||||
files2.sort(sort).forEach(function (file) {
|
||||
files3.push('<li>' + file + '</li>');
|
||||
});
|
||||
return files3.join('');
|
||||
}
|
||||
|
||||
fs.readdir("/myDir", function (error, files1) {
|
||||
res.write(files1); // NOT OK
|
||||
|
||||
var dirs = [];
|
||||
var files2 = [];
|
||||
files1.forEach(function (file) {
|
||||
files2.push(file);
|
||||
});
|
||||
res.write(files2); // NOT OK
|
||||
|
||||
var files3 = format(files2);
|
||||
|
||||
res.write(files3); // NOT OK
|
||||
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user