JS: Migrate CodeQL tests for ML-powered queries

This commit is contained in:
Henry Mercer
2022-02-01 15:22:21 +00:00
parent 7bb11b837c
commit fbcb8d6857
127 changed files with 132859 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
/**
* EndpointFeatures.ql
*
* This tests generic token-based featurization of all endpoint candidates for all of the security
* queries we support. This is in comparison to the `ExtractEndpointData.qlref` test, which tests
* just the endpoints we extract in the training data.
*/
import javascript
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures
import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters
import extraction.NoFeaturizationRestrictionsConfig
query predicate tokenFeatures(DataFlow::Node endpoint, string featureName, string featureValue) {
(
not exists(NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(XssATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
StandardEndpointFilters::isArgumentToModeledFunction(endpoint)
) and
EndpointFeatures::tokenFeatures(endpoint, featureName, featureValue)
}
query predicate invalidTokenFeatures(
DataFlow::Node endpoint, string featureName, string featureValue
) {
strictcount(string value | EndpointFeatures::tokenFeatures(endpoint, featureName, value)) > 1 and
EndpointFeatures::tokenFeatures(endpoint, featureName, featureValue)
}

View File

@@ -0,0 +1 @@
extraction/ExtractEndpointData.ql

View File

@@ -0,0 +1 @@
extraction/ExtractEndpointDataEvaluation.ql

View File

@@ -0,0 +1 @@
extraction/ExtractEndpointDataTraining.ql

View File

@@ -0,0 +1,16 @@
nosqlFilteredTruePositives
| autogenerated/NosqlAndSqlInjection/untyped/mongoose.js:111:14:111:18 | query | not a direct argument to a likely external library call or a heuristic sink |
sqlFilteredTruePositives
| autogenerated/NosqlAndSqlInjection/untyped/tst2.js:7:13:7:45 | select ... e id = | not an argument to a likely external library call or a heuristic sink |
| autogenerated/NosqlAndSqlInjection/untyped/tst2.js:7:48:7:60 | req.params.id | not an argument to a likely external library call or a heuristic sink |
taintedPathFilteredTruePositives
| autogenerated/TaintedPath/TaintedPath.js:66:26:66:31 | "SAFE" | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/TaintedPath/TaintedPath.js:71:26:71:45 | Cookie.get("unsafe") | not a direct argument to a likely external library call or a heuristic sink |
xssFilteredTruePositives
| autogenerated/Xss/DomBasedXss/d3.js:12:20:12:29 | getTaint() | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/d3.js:14:20:14:29 | getTaint() | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/express.js:7:15:7:33 | req.param("wobble") | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/jwt-server.js:11:19:11:29 | decoded.foo | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/tst.js:316:35:316:42 | location | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/typeahead.js:10:16:10:18 | loc | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/typeahead.js:25:18:25:20 | val | not a direct argument to a likely external library call or a heuristic sink |

View File

@@ -0,0 +1,47 @@
/*
* FilteredTruePositives.ql
*
* This test checks several components of the endpoint filters for each query to see whether they
* filter out any known sinks. It explicitly does not check the endpoint filtering step that's based
* on whether the endpoint is an argument to a modelled function, since this necessarily filters out
* all known sinks. However, we can test all the other filtering steps against the set of known
* sinks.
*
* Ideally, the sink endpoint filters would have perfect recall and therefore each of the predicates
* in this test would have zero results. However, in some cases we have chosen to sacrifice recall
* when we perceive the improved precision of the results to be worth the drop in recall.
*/
import semmle.javascript.security.dataflow.NosqlInjectionCustomizations
import semmle.javascript.security.dataflow.SqlInjectionCustomizations
import semmle.javascript.security.dataflow.TaintedPathCustomizations
import semmle.javascript.security.dataflow.DomBasedXssCustomizations
import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
query predicate nosqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof NosqlInjection::Sink and
reason = NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
not reason = ["argument to modeled function", "modeled sink", "modeled database access"]
}
query predicate sqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof SqlInjection::Sink and
reason = SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason != "argument to modeled function"
}
query predicate taintedPathFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof TaintedPath::Sink and
reason = TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason != "argument to modeled function"
}
query predicate xssFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof DomBasedXss::Sink and
reason = XssATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason != "argument to modeled function"
}

View File

@@ -0,0 +1,13 @@
declare module "mongodb" {
interface Collection {
find(query: any): any;
}
}
declare module "mongoose" {
interface Model {
find(query: any): any;
}
interface Query {
find(query: any): any;
}
}

View File

@@ -0,0 +1,24 @@
import * as mongodb from "mongodb";
const express = require("express") as any;
const bodyParser = require("body-parser") as any;
declare function getCollection(): mongodb.Collection;
let app = express();
app.use(bodyParser.json());
app.post("/find", (req, res) => {
let v = JSON.parse(req.body.x);
getCollection().find({ id: v }); // NOT OK
});
import * as mongoose from "mongoose";
declare function getMongooseModel(): mongoose.Model;
declare function getMongooseQuery(): mongoose.Query;
app.post("/find", (req, res) => {
let v = JSON.parse(req.body.x);
getMongooseModel().find({ id: v }); // NOT OK
getMongooseQuery().find({ id: v }); // NOT OK
});

View File

@@ -0,0 +1,13 @@
let dbClient = require("mongodb").MongoClient,
db = null;
module.exports = {
db: () => {
return db;
},
connect: fn => {
dbClient.connect(process.env.DB_URL, {}, (err, client) => {
db = client.db(process.env.DB_NAME);
return fn(err);
});
}
};

View File

@@ -0,0 +1,37 @@
import Ajv from 'ajv';
import express from 'express';
import { MongoClient } from 'mongodb';
const app = express();
const schema = {
type: 'object',
properties: {
date: { type: 'string' },
title: { type: 'string' },
},
};
const ajv = new Ajv();
const checkSchema = ajv.compile(schema);
function validate(x) {
return x != null;
}
app.post('/documents/find', (req, res) => {
MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
let doc = db.collection('doc');
const query = JSON.parse(req.query.data);
if (checkSchema(query)) {
doc.find(query); // OK
}
if (ajv.validate(schema, query)) {
doc.find(query); // OK
}
if (validate(query)) {
doc.find(query); // NOT OK - validate() doesn't sanitize
}
doc.find(query); // NOT OK
});
});

View File

@@ -0,0 +1,9 @@
const MarsDB = require("marsdb");
const myDoc = new MarsDB.Collection("myDoc");
const db = {
myDoc
};
module.exports = db;

View File

@@ -0,0 +1,15 @@
const express = require("express"),
bodyParser = require("body-parser"),
db = require('./marsdb-flow-from');
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.post("/documents/find", (req, res) => {
const query = {};
query.title = req.body.title;
// NOT OK: query is tainted by user-provided object value
db.myDoc.find(query);
});

View File

@@ -0,0 +1,17 @@
const express = require("express"),
MarsDB = require("marsdb"),
bodyParser = require("body-parser");
let doc = new MarsDB.Collection("myDoc");
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.post("/documents/find", (req, res) => {
const query = {};
query.title = req.body.title;
// NOT OK: query is tainted by user-provided object value
doc.find(query);
});

View File

@@ -0,0 +1,19 @@
const express = require("express"),
minimongo = require("minimongo"),
bodyParser = require("body-parser");
var LocalDb = minimongo.MemoryDb,
db = new LocalDb(),
doc = db.myDocs;
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.post("/documents/find", (req, res) => {
const query = {};
query.title = req.body.title;
// NOT OK: query is tainted by user-provided object value
doc.find(query);
});

View File

@@ -0,0 +1,114 @@
const express = require('express'),
mongodb = require('mongodb'),
bodyParser = require('body-parser');
const MongoClient = mongodb.MongoClient;
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/documents/find', (req, res) => {
const query = {};
query.title = req.body.title;
MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
let doc = db.collection('doc');
// NOT OK: query is tainted by user-provided object value
doc.find(query);
// OK: user-data is coerced to a string
doc.find({ title: '' + query.body.title });
// OK: throws unless user-data is a string
doc.find({ title: query.body.title.substr(1) });
let title = req.body.title;
if (typeof title === "string") {
// OK: input checked to be a string
doc.find({ title: title });
// NOT OK: input is parsed as JSON after string check
doc.find({ title: JSON.parse(title) });
}
});
});
app.get('/:id', (req, res) => {
let query = { id: req.param.id };
MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
let doc = db.collection('doc');
// OK: query is tainted, but only by string value
doc.find(query);
});
});
app.post('/documents/find', (req, res) => {
const query = {};
query.title = req.query.title;
MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
let doc = db.collection('doc');
// NOT OK: query is tainted by user-provided object value
doc.find(query);
});
});
app.post('/documents/find', (req, res) => {
const query = {};
query.title = req.query.title;
MongoClient.connect('mongodb://localhost:27017/test', (err, client) => {
let doc = client.db("MASTER").collection('doc');
// NOT OK: query is tainted by user-provided object value
doc.find(query);
});
});
app.post("/logs/count-by-tag", (req, res) => {
let tag = req.query.tag;
MongoClient.connect(process.env.DB_URL, {}, (err, client) => {
client
.db(process.env.DB_NAME)
.collection("logs")
// NOT OK: query is tainted by user-provided object value
.count({ tags: tag });
});
let importedDbo = require("./dbo.js");
importedDbo
.db()
.collection("logs")
// NOT OK: query is tainted by user-provided object value
.count({ tags: tag });
});
app.get('/:id', (req, res) => {
useParams(req.param);
});
function useParams(params) {
let query = { id: params.id };
MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
let doc = db.collection('doc');
// OK: query is tainted, but only by string value
doc.find(query);
});
}
app.post('/documents/find', (req, res) => {
useQuery(req.query);
});
function useQuery(queries) {
const query = {};
query.title = queries.title;
MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
let doc = db.collection('doc');
// NOT OK: query is tainted by user-provided object value
doc.find(query);
});
}

View File

@@ -0,0 +1,31 @@
const express = require('express'),
mongodb = require('mongodb'),
bodyParser = require('body-parser');
const MongoClient = mongodb.MongoClient;
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.post('/documents/find', (req, res) => {
const query = {};
query.title = req.body.title;
MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
let doc = db.collection('doc');
// OK: req.body is safe
doc.find(query);
});
});
app.post('/documents/find', (req, res) => {
const query = {};
query.title = req.query.title;
MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
let doc = db.collection('doc');
// NOT OK: regardless of body parser, query value is still tainted
doc.find(query);
});
});

View File

@@ -0,0 +1,132 @@
'use strict';
const Express = require('express');
const BodyParser = require('body-parser');
const Mongoose = require('mongoose');
Mongoose.Promise = global.Promise;
Mongoose.connect('mongodb://localhost/injectable1');
const app = Express();
app.use(BodyParser.json());
const Document = Mongoose.model('Document', {
title: {
type: String,
unique: true
},
type: String
});
app.post('/documents/find', (req, res) => {
const query = {};
query.title = req.body.title;
// NOT OK: query is tainted by user-provided object value
Document.aggregate([query]);
// NOT OK: query is tainted by user-provided object value
Document.count(query);
// NOT OK: query is tainted by user-provided object value
Document.deleteMany(query);
// NOT OK: query is tainted by user-provided object value
Document.deleteOne(query);
// NOT OK: query is tainted by user-provided object value
Document.distinct('type', query);
// NOT OK: query is tainted by user-provided object value
Document.find(query);
// NOT OK: query is tainted by user-provided object value
Document.findOne(query);
// NOT OK: query is tainted by user-provided object value
Document.findOneAndDelete(query);
// NOT OK: query is tainted by user-provided object value
Document.findOneAndRemove(query);
// NOT OK: query is tainted by user-provided object value
Document.findOneAndUpdate(query);
// NOT OK: query is tainted by user-provided object value
Document.replaceOne(query);
// NOT OK: query is tainted by user-provided object value
Document.update(query);
// NOT OK: query is tainted by user-provided object value
Document.updateMany(query);
// NOT OK: query is tainted by user-provided object value
Document.updateOne(query).then(X);
Document.findByIdAndUpdate(X, query, function(){}); // NOT OK
new Mongoose.Query(X, Y, query) // NOT OK
.and(query, function(){}) // NOT OK
;
Document.where(query) // NOT OK - `.where()` on a Model.
.where(query) // NOT OK - `.where()` on a Query.
.and(query) // NOT OK
.or(query) // NOT OK
.distinct(X, query) // NOT OK
.comment(query) // OK
.count(query) // NOT OK
.exec()
;
Mongoose.createConnection(X).count(query); // OK (invalid program)
Mongoose.createConnection(X).model(Y).count(query); // NOT OK
Mongoose.createConnection(X).models[Y].count(query); // NOT OK
Document.findOne(X, (err, res) => res.count(query)); // NOT OK
Document.findOne(X, (err, res) => err.count(query)); // OK
Document.findOne(X).exec((err, res) => res.count(query)); // NOT OK
Document.findOne(X).exec((err, res) => err.count(query)); // OK
Document.findOne(X).then((res) => res.count(query)); // NOT OK
Document.findOne(X).then(Y, (err) => err.count(query)); // OK
Document.find(X, (err, res) => res[i].count(query)); // NOT OK
Document.find(X, (err, res) => err.count(query)); // OK
Document.find(X).exec((err, res) => res[i].count(query)); // NOT OK
Document.find(X).exec((err, res) => err.count(query)); // OK
Document.find(X).then((res) => res[i].count(query)); // NOT OK
Document.find(X).then(Y, (err) => err.count(query)); // OK
Document.count(X, (err, res) => res.count(query)); // OK (res is a number)
function innocent(X, Y, query) { // To detect if API-graphs were used incorrectly.
return new Mongoose.Query("constant", "constant", "constant");
}
new innocent(X, Y, query);
function getQueryConstructor() {
return Mongoose.Query;
}
var C = getQueryConstructor();
new C(X, Y, query); // NOT OK
Document.findOneAndUpdate(X, query, function () { }); // NOT OK
let id = req.query.id, cond = req.query.cond;
Document.deleteMany(cond); // NOT OK
Document.deleteOne(cond); // NOT OK
Document.geoSearch(cond); // NOT OK
Document.remove(cond); // NOT OK
Document.replaceOne(cond, Y); // NOT OK
Document.find(cond); // NOT OK
Document.findOne(cond); // NOT OK
Document.findById(id); // NOT OK
Document.findOneAndDelete(cond); // NOT OK
Document.findOneAndRemove(cond); // NOT OK
Document.findOneAndUpdate(cond, Y); // NOT OK
Document.update(cond, Y); // NOT OK
Document.updateMany(cond, Y); // NOT OK
Document.updateOne(cond, Y); // NOT OK
Document.find({ _id: id }); // NOT OK
Document.find({ _id: { $eq: id } }); // OK
});

View File

@@ -0,0 +1,25 @@
'use strict';
const Express = require('express');
const BodyParser = require('body-parser');
const Mongoose = require('mongoose');
Mongoose.Promise = global.Promise;
Mongoose.connect('mongodb://localhost/injectable1');
const app = Express();
const Document = Mongoose.model('Document', {
title: {
type: String,
unique: true
},
type: String
});
app.get('/documents/find', (req, res) => {
const query = {};
query.title = JSON.parse(req.query.data).title;
// NOT OK: query is tainted by user-provided object value
Document.find(query);
});

View File

@@ -0,0 +1,3 @@
import mongoose from 'mongoose';
export const MyModel = mongoose.model('MyModel', getSchema());

View File

@@ -0,0 +1,14 @@
import { MyModel } from './mongooseModel';
import express from 'express';
import bodyParser from 'body-parser';
let app = express();
app.use(bodyParser.json());
app.post('/find', (req, res) => {
let v = JSON.parse(req.body.x);
MyModel.find({ id: v }); // NOT OK
MyModel.find({ id: req.body.id }); // NOT OK
MyModel.find({ id: `${req.body.id}` }); // OK
});

View File

@@ -0,0 +1,13 @@
import { IDatabase } from "pg-promise";
export class Foo {
db: IDatabase;
onRequest(req, res) {
let taint = req.params.x;
this.db.one(taint); // NOT OK
res.end();
}
}
require('express')().get('/foo', (req, res) => new Foo().onRequest(req, res));

View File

@@ -0,0 +1,66 @@
const pgp = require('pg-promise')();
require('express')().get('/foo', (req, res) => {
const db = pgp(process.env['DB_CONNECTION_STRING']);
var query = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='"
+ req.params.category + "' ORDER BY PRICE";
db.any(query); // NOT OK
db.many(query); // NOT OK
db.manyOrNone(query); // NOT OK
db.map(query); // NOT OK
db.multi(query); // NOT OK
db.multiResult(query); // NOT OK
db.none(query); // NOT OK
db.one(query); // NOT OK
db.oneOrNone(query); // NOT OK
db.query(query); // NOT OK
db.result(query); // NOT OK
db.one({
text: query // NOT OK
});
db.one({
text: 'SELECT * FROM news where id = $1', // OK
values: req.params.id, // OK
});
db.one({
text: 'SELECT * FROM news where id = $1:raw',
values: req.params.id, // NOT OK - interpreted as raw parameter
});
db.one({
text: 'SELECT * FROM news where id = $1^',
values: req.params.id, // NOT OK
});
db.one({
text: 'SELECT * FROM news where id = $1:raw AND name = $2:raw AND foo = $3',
values: [
req.params.id, // NOT OK
req.params.name, // NOT OK
req.params.foo, // OK - not using raw interpolation
]
});
db.one({
text: 'SELECT * FROM news where id = ${id}:raw AND name = ${name}',
values: {
id: req.params.id, // NOT OK
name: req.params.name, // OK - not using raw interpolation
}
});
db.one({
text: "SELECT * FROM news where id = ${id}:value AND name LIKE '%${name}:value%' AND title LIKE \"%${title}:value%\"",
values: {
id: req.params.id, // NOT OK
name: req.params.name, // OK - :value cannot break out of single quotes
title: req.params.title, // NOT OK - enclosed by wrong type of quote
}
});
db.task(t => {
return t.one(query); // NOT OK
});
db.task(
{ cnd: t => t.one(query) }, // NOT OK
t => t.one(query) // NOT OK
);
});

View File

@@ -0,0 +1,53 @@
const redis = require("redis");
const client = redis.createClient();
const Express = require('express');
const app = Express();
app.use(require('body-parser').json());
app.post('/documents/find', (req, res) => {
client.set(req.body.key, "value"); // NOT OK
var key = req.body.key;
if (typeof key === "string") {
client.set(key, "value"); // OK
client.set(["key", "value"]);
}
client.set(key, "value"); // NOT OK
client.hmset("key", "field", "value", key, "value2"); // NOT OK
// chain commands
client
.multi()
.set("constant", "value")
.set(key, "value") // NOT OK
.get(key) // OK
.exec(function (err, replies) { });
client.duplicate((err, newClient) => {
newClient.set(key, "value"); // NOT OK
});
client.duplicate().set(key, "value"); // NOT OK
});
import { promisify } from 'util';
app.post('/documents/find', (req, res) => {
const key = req.body.key;
client.set(key, "value"); // NOT OK
const setAsync = promisify(client.set).bind(client);
const foo1 = setAsync(key, "value"); // NOT OK
client.setAsync = promisify(client.set);
const foo2 = client.setAsync(key, "value"); // NOT OK
client.unrelated = promisify(() => {});
const foo3 = client.unrelated(key, "value"); // OK
const unrelated = promisify(client.foobar).bind(client);
const foo4 = unrelated(key, "value"); // OK
});

View File

@@ -0,0 +1,13 @@
// Adapted from https://github.com/mapbox/node-sqlite3/wiki/API, which is
// part of the node-sqlite3 project, which is licensed under the BSD 3-Clause
// License; see file node-sqlite3-LICENSE.
var express = require('express');
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database(':memory:');
var io = require('socket.io')();
io.on('connection', (socket) => {
socket.on('newuser', (handle) => {
db.run(`INSERT INTO users(name) VALUES ${handle}`);
});
});

View File

@@ -0,0 +1,11 @@
// Adapted from https://github.com/mapbox/node-sqlite3/wiki/API, which is
// part of the node-sqlite3 project, which is licensed under the BSD 3-Clause
// License; see file node-sqlite3-LICENSE.
var express = require('express');
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database(':memory:');
var app = express();
app.get('/post/:id', function(req, res) {
db.get('SELECT * FROM Post WHERE id = "' + req.params.id + '"');
});

View File

@@ -0,0 +1,10 @@
var express = require('express');
const sql = require('mssql');
var app = express();
app.get('/post/:id', async function(req, res) {
// OK
sql.query`select * from mytable where id = ${req.params.id}`;
// NOT OK
new sql.Request().query("select * from mytable where id = '" + req.params.id + "'");
});

View File

@@ -0,0 +1,21 @@
// Adapted from the documentation of https://github.com/brianc/node-postgres,
// which is licensed under the MIT license; see file node-postgres-LICENSE.
const pg = require('pg');
const pool = new pg.Pool(config);
function handler(req, res) {
var query1 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='"
+ req.params.category + "' ORDER BY PRICE";
pool.query(query1, [], function(err, results) { // BAD: the category might have SQL special characters in it
// process results
});
// GOOD: use parameters
var query2 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=$1"
+ " ORDER BY PRICE";
pool.query(query2, [req.params.category], function(err, results) {
// process results
});
}
require('express')().get('/foo', handler);

View File

@@ -0,0 +1,9 @@
// Adapted from https://github.com/mapbox/node-sqlite3/wiki/API, which is
// part of the node-sqlite3 project, which is licensed under the BSD 3-Clause
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database(':memory:');
angular.module('myApp', ['ngRoute'])
.controller('FindPost', function($routeParams) {
db.get('SELECT * FROM Post WHERE id = "' + $routeParams.id + '"');
});

View File

@@ -0,0 +1,7 @@
# autogenerated
This folder contains test data for the ATM endpoint CodeQL tests that has been autogenerated from the standard JS CodeQL libraries.
It is helpful, but not required, to periodically update this test data to incorporate new test data introduced in the standard JS CodeQL libraries.
To update this test data, run `python /path/to/codeql-lib/ql/javascript/test/update_endpoint_test_files.py --codeql-lib-path /path/to/codeql-lib`.
For more information, run `python /path/to/codeql-lib/ql/javascript/test/update_endpoint_test_files.py --help` or view the source code of [`update_endpoint_test_files.py`](../../update_endpoint_test_files.py).

View File

@@ -0,0 +1,11 @@
import { readFileSync } from 'fs';
import { createServer } from 'http';
import { parse } from 'url';
import { join } from 'path';
var server = createServer(function(req, res) {
let path = parse(req.url, true).query.path;
// BAD: This could read any file on the file system
res.write(readFileSync(join("public", path)));
});

View File

@@ -0,0 +1,196 @@
var fs = require('fs'),
http = require('http'),
url = require('url'),
sanitize = require('sanitize-filename'),
pathModule = require('path')
;
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
// BAD: This could read any file on the file system
res.write(fs.readFileSync(path));
// BAD: This could still read any file on the file system
res.write(fs.readFileSync("/home/user/" + path));
if (path.startsWith("/home/user/"))
res.write(fs.readFileSync(path)); // BAD: Insufficient sanitisation
if (path.indexOf("secret") == -1)
res.write(fs.readFileSync(path)); // BAD: Insufficient sanitisation
if (fs.existsSync(path))
res.write(fs.readFileSync(path)); // BAD: Insufficient sanitisation
if (path === 'foo.txt')
res.write(fs.readFileSync(path)); // GOOD: Path is compared to white-list
if (path === 'foo.txt' || path === 'bar.txt')
res.write(fs.readFileSync(path)); // GOOD: Path is compared to white-list
if (path === 'foo.txt' || path === 'bar.txt' || someOpaqueCondition())
res.write(fs.readFileSync(path)); // BAD: Path is incompletely compared to white-list
path = sanitize(path);
res.write(fs.readFileSync(path)); // GOOD: Path is sanitized
path = url.parse(req.url, true).query.path;
// GOOD: basename is safe
res.write(fs.readFileSync(pathModule.basename(path)));
// BAD: taint is preserved
res.write(fs.readFileSync(pathModule.dirname(path)));
// GOOD: extname is safe
res.write(fs.readFileSync(pathModule.extname(path)));
// BAD: taint is preserved
res.write(fs.readFileSync(pathModule.join(path)));
// BAD: taint is preserved
res.write(fs.readFileSync(pathModule.join(x, y, path, z)));
// BAD: taint is preserved
res.write(fs.readFileSync(pathModule.normalize(path)));
// BAD: taint is preserved
res.write(fs.readFileSync(pathModule.relative(x, path)));
// BAD: taint is preserved
res.write(fs.readFileSync(pathModule.relative(path, x)));
// BAD: taint is preserved
res.write(fs.readFileSync(pathModule.resolve(path)));
// BAD: taint is preserved
res.write(fs.readFileSync(pathModule.resolve(x, y, path, z)));
// BAD: taint is preserved
res.write(fs.readFileSync(pathModule.toNamespacedPath(path)));
});
angular.module('myApp', [])
.directive('myCustomer', function() {
return {
templateUrl: "SAFE" // OK
}
})
.directive('myCustomer', function() {
return {
templateUrl: Cookie.get("unsafe") // NOT OK
}
})
var server = http.createServer(function(req, res) {
// tests for a few uri-libraries
res.write(fs.readFileSync(require("querystringify").parse(req.url).query)); // NOT OK
res.write(fs.readFileSync(require("query-string").parse(req.url).query)); // NOT OK
res.write(fs.readFileSync(require("querystring").parse(req.url).query)); // NOT OK
});
(function(){
var express = require('express');
var application = express();
var views_local = (req, res) => res.render(req.params[0]);
application.get('/views/*', views_local);
var views_imported = require("./views");
application.get('/views/*', views_imported);
})();
addEventListener('message', (ev) => {
Cookie.set("unsafe", ev.data);
});
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
res.write(fs.readFileSync(fs.realpathSync(path)));
fs.realpath(path,
function(err, realpath){
res.write(fs.readFileSync(realpath));
}
);
});
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
if (path) { // sanitization
path = path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''); // remove all invalid characters from states plus slashes
path = path.replace(/\.\./g, ''); // remove all ".."
}
res.write(fs.readFileSync(path)); // OK. Is sanitized above.
});
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
if (!path) {
} else { // sanitization
path = path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''); // remove all invalid characters from states plus slashes
path = path.replace(/\.\./g, ''); // remove all ".."
}
res.write(fs.readFileSync(path)); // OK. Is sanitized above.
});
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
require('send')(req, path); // NOT OK
});
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
fs.readFileSync(path); // NOT OK
var split = path.split("/");
fs.readFileSync(split.join("/")); // NOT OK
fs.readFileSync(prefix + split[split.length - 1]) // OK
fs.readFileSync(split[x]) // NOT OK
fs.readFileSync(prefix + split[x]) // NOT OK
var concatted = prefix.concat(split);
fs.readFileSync(concatted.join("/")); // NOT OK
var concatted2 = split.concat(prefix);
fs.readFileSync(concatted2.join("/")); // NOT OK
fs.readFileSync(split.pop()); // NOT OK
});
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
// Removal of forward-slash or dots.
res.write(fs.readFileSync(path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''))); // OK.
res.write(fs.readFileSync(path.replace(/[abcd]/g, ''))); // NOT OK
res.write(fs.readFileSync(path.replace(/[./]/g, ''))); // OK
res.write(fs.readFileSync(path.replace(/[foobar/foobar]/g, ''))); // OK
res.write(fs.readFileSync(path.replace(/\//g, ''))); // OK
res.write(fs.readFileSync(path.replace(/\.|\//g, ''))); // OK
res.write(fs.readFileSync(path.replace(/[.]/g, ''))); // NOT OK (can be absolute)
res.write(fs.readFileSync(path.replace(/[..]/g, ''))); // NOT OK (can be absolute)
res.write(fs.readFileSync(path.replace(/\./g, ''))); // NOT OK (can be absolute)
res.write(fs.readFileSync(path.replace(/\.\.|BLA/g, ''))); // NOT OK (can be absolute)
if (!pathModule.isAbsolute(path)) {
res.write(fs.readFileSync(path.replace(/[.]/g, ''))); // OK
res.write(fs.readFileSync(path.replace(/[..]/g, ''))); // OK
res.write(fs.readFileSync(path.replace(/\./g, ''))); // OK
res.write(fs.readFileSync(path.replace(/\.\.|BLA/g, ''))); // OK
}
// removing of "../" from prefix.
res.write(fs.readFileSync("prefix" + pathModule.normalize(path).replace(/^(\.\.[\/\\])+/, ''))); // OK
res.write(fs.readFileSync("prefix" + pathModule.normalize(path).replace(/(\.\.[\/\\])+/, ''))); // OK
res.write(fs.readFileSync("prefix" + pathModule.normalize(path).replace(/(\.\.\/)+/, ''))); // OK
res.write(fs.readFileSync("prefix" + pathModule.normalize(path).replace(/(\.\.\/)*/, ''))); // OK
res.write(fs.readFileSync("prefix" + path.replace(/^(\.\.[\/\\])+/, ''))); // NOT OK - not normalized
res.write(fs.readFileSync(pathModule.normalize(path).replace(/^(\.\.[\/\\])+/, ''))); // NOT OK (can be absolute)
});

View File

@@ -0,0 +1,48 @@
// Adapted from externs generated from TypeScript type definitions provided
// by DefinitelyTyped, which is licensed under the MIT license; see file
// DefinitelyTyped-LICENSE.
// Type definitions for Node.js 10.5.x
// Project: http://nodejs.org/
// Definitions by: Microsoft TypeScript <http://typescriptlang.org>
// DefinitelyTyped <https://github.com/DefinitelyTyped/DefinitelyTyped>
// Parambir Singh <https://github.com/parambirs>
// Christian Vaagland Tellnes <https://github.com/tellnes>
// Wilco Bakker <https://github.com/WilcoBakker>
// Nicolas Voigt <https://github.com/octo-sniffle>
// Chigozirim C. <https://github.com/smac89>
// Flarna <https://github.com/Flarna>
// Mariusz Wiktorczyk <https://github.com/mwiktorczyk>
// wwwy3y3 <https://github.com/wwwy3y3>
// Deividas Bakanas <https://github.com/DeividasBakanas>
// Kelvin Jin <https://github.com/kjin>
// Alvis HT Tang <https://github.com/alvis>
// Sebastian Silbermann <https://github.com/eps1lon>
// Hannes Magnusson <https://github.com/Hannes-Magnusson-CK>
// Alberto Schiabel <https://github.com/jkomyno>
// Klaus Meinhardt <https://github.com/ajafff>
// Huw <https://github.com/hoo29>
// Nicolas Even <https://github.com/n-e>
// Bruno Scheufler <https://github.com/brunoscheufler>
// Mohsen Azimi <https://github.com/mohsen1>
// Hoàng Văn Khải <https://github.com/KSXGitHub>
// Alexander T. <https://github.com/a-tarasyuk>
// Lishude <https://github.com/islishude>
// Andrew Makarov <https://github.com/r3nya>
// Zane Hannan AU <https://github.com/ZaneHannanAU>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/**
* @externs
*/
var fs = {};
/**
* @param {string} filename
* @param {string} encoding
* @return {string}
*/
fs.readFileSync = function(filename, encoding) {};
module.exports = fs;

View File

@@ -0,0 +1,14 @@
const fs = require('fs');
const {promisify} = require('bluebird');
const methods = [
'readFile',
'writeFile',
'readFileSync',
'writeFileSync'
];
module.exports = methods.reduce((obj, method) => {
obj[method] = promisify(fs[method]);
return obj;
}, {});

View File

@@ -0,0 +1,7 @@
exports.require = function(special) {
if (special) {
return require("fs");
} else {
return require("original-fs");
}
};

View File

@@ -0,0 +1,373 @@
var fs = require('fs'),
express = require('express'),
url = require('url'),
sanitize = require('sanitize-filename'),
pathModule = require('path')
;
let app = express();
app.get('/basic', (req, res) => {
let path = req.query.path;
fs.readFileSync(path); // NOT OK
fs.readFileSync('./' + path); // NOT OK
fs.readFileSync(path + '/index.html'); // NOT OK
fs.readFileSync(pathModule.join(path, 'index.html')); // NOT OK
fs.readFileSync(pathModule.join('/home/user/www', path)); // NOT OK
});
app.get('/normalize', (req, res) => {
let path = pathModule.normalize(req.query.path);
fs.readFileSync(path); // NOT OK
fs.readFileSync('./' + path); // NOT OK
fs.readFileSync(path + '/index.html'); // NOT OK
fs.readFileSync(pathModule.join(path, 'index.html')); // NOT OK
fs.readFileSync(pathModule.join('/home/user/www', path)); // NOT OK
});
app.get('/normalize-notAbsolute', (req, res) => {
let path = pathModule.normalize(req.query.path);
if (pathModule.isAbsolute(path))
return;
fs.readFileSync(path); // NOT OK
if (!path.startsWith("."))
fs.readFileSync(path); // OK
else
fs.readFileSync(path); // NOT OK - wrong polarity
if (!path.startsWith(".."))
fs.readFileSync(path); // OK
if (!path.startsWith("../"))
fs.readFileSync(path); // OK
if (!path.startsWith(".." + pathModule.sep))
fs.readFileSync(path); // OK
});
app.get('/normalize-noInitialDotDot', (req, res) => {
let path = pathModule.normalize(req.query.path);
if (path.startsWith(".."))
return;
fs.readFileSync(path); // NOT OK - could be absolute
fs.readFileSync("./" + path); // OK - coerced to relative
fs.readFileSync(path + "/index.html"); // NOT OK - not coerced
if (!pathModule.isAbsolute(path))
fs.readFileSync(path); // OK
else
fs.readFileSync(path); // NOT OK
});
app.get('/prepend-normalize', (req, res) => {
// Coerce to relative prior to normalization
let path = pathModule.normalize('./' + req.query.path);
if (!path.startsWith(".."))
fs.readFileSync(path); // OK
else
fs.readFileSync(path); // NOT OK
});
app.get('/absolute', (req, res) => {
let path = req.query.path;
if (!pathModule.isAbsolute(path))
return;
res.write(fs.readFileSync(path)); // NOT OK
if (path.startsWith('/home/user/www'))
res.write(fs.readFileSync(path)); // NOT OK - can still contain '../'
});
app.get('/normalized-absolute', (req, res) => {
let path = pathModule.normalize(req.query.path);
if (!pathModule.isAbsolute(path))
return;
res.write(fs.readFileSync(path)); // NOT OK
if (path.startsWith('/home/user/www'))
res.write(fs.readFileSync(path)); // OK
});
app.get('/combined-check', (req, res) => {
let path = pathModule.normalize(req.query.path);
// Combined absoluteness and folder check in one startsWith call
if (path.startsWith("/home/user/www"))
fs.readFileSync(path); // OK
if (path[0] !== "/" && path[0] !== ".")
fs.readFileSync(path); // OK
});
app.get('/realpath', (req, res) => {
let path = fs.realpathSync(req.query.path);
fs.readFileSync(path); // NOT OK
fs.readFileSync(pathModule.join(path, 'index.html')); // NOT OK
if (path.startsWith("/home/user/www"))
fs.readFileSync(path); // OK - both absolute and normalized before check
fs.readFileSync(pathModule.join('.', path)); // OK - normalized and coerced to relative
fs.readFileSync(pathModule.join('/home/user/www', path)); // OK
});
app.get('/coerce-relative', (req, res) => {
let path = pathModule.join('.', req.query.path);
if (!path.startsWith('..'))
fs.readFileSync(path); // OK
else
fs.readFileSync(path); // NOT OK
});
app.get('/coerce-absolute', (req, res) => {
let path = pathModule.join('/home/user/www', req.query.path);
if (path.startsWith('/home/user/www'))
fs.readFileSync(path); // OK
else
fs.readFileSync(path); // NOT OK
});
app.get('/concat-after-normalization', (req, res) => {
let path = 'foo/' + pathModule.normalize(req.query.path);
if (!path.startsWith('..'))
fs.readFileSync(path); // NOT OK - prefixing foo/ invalidates check
else
fs.readFileSync(path); // NOT OK
if (!path.includes('..'))
fs.readFileSync(path); // OK
});
app.get('/noDotDot', (req, res) => {
let path = pathModule.normalize(req.query.path);
if (path.includes('..'))
return;
fs.readFileSync(path); // NOT OK - can still be absolute
if (!pathModule.isAbsolute(path))
fs.readFileSync(path); // OK
else
fs.readFileSync(path); // NOT OK
});
app.get('/join-regression', (req, res) => {
let path = req.query.path;
// Regression test for a specific corner case:
// Some guard nodes sanitize both branches, but for a different set of flow labels.
// Verify that this does not break anything.
if (pathModule.isAbsolute(path)) {path;} else {path;}
if (path.startsWith('/')) {path;} else {path;}
if (path.startsWith('/x')) {path;} else {path;}
if (path.startsWith('.')) {path;} else {path;}
fs.readFileSync(path); // NOT OK
if (pathModule.isAbsolute(path))
fs.readFileSync(path); // NOT OK
else
fs.readFileSync(path); // NOT OK
if (path.includes('..'))
fs.readFileSync(path); // NOT OK
else
fs.readFileSync(path); // NOT OK
if (!path.includes('..') && !pathModule.isAbsolute(path))
fs.readFileSync(path); // OK
else
fs.readFileSync(path); // NOT OK
let normalizedPath = pathModule.normalize(path);
if (normalizedPath.startsWith('/home/user/www'))
fs.readFileSync(normalizedPath); // OK
else
fs.readFileSync(normalizedPath); // NOT OK
if (normalizedPath.startsWith('/home/user/www') || normalizedPath.startsWith('/home/user/public'))
fs.readFileSync(normalizedPath); // OK - but flagged anyway [INCONSISTENCY]
else
fs.readFileSync(normalizedPath); // NOT OK
});
app.get('/decode-after-normalization', (req, res) => {
let path = pathModule.normalize(req.query.path);
if (!pathModule.isAbsolute(path) && !path.startsWith('..'))
fs.readFileSync(path); // OK
path = decodeURIComponent(path);
if (!pathModule.isAbsolute(path) && !path.startsWith('..'))
fs.readFileSync(path); // NOT OK - not normalized
});
app.get('/replace', (req, res) => {
let path = pathModule.normalize(req.query.path).replace(/%20/g, ' ');
if (!pathModule.isAbsolute(path)) {
fs.readFileSync(path); // NOT OK
path = path.replace(/\.\./g, '');
fs.readFileSync(path); // OK
}
});
app.get('/resolve-path', (req, res) => {
let path = pathModule.resolve(req.query.path);
fs.readFileSync(path); // NOT OK
var self = something();
if (path.substring(0, self.dir.length) === self.dir)
fs.readFileSync(path); // OK
else
fs.readFileSync(path); // NOT OK - wrong polarity
if (path.slice(0, self.dir.length) === self.dir)
fs.readFileSync(path); // OK
else
fs.readFileSync(path); // NOT OK - wrong polarity
});
app.get('/relative-startswith', (req, res) => {
let path = pathModule.resolve(req.query.path);
fs.readFileSync(path); // NOT OK
var self = something();
var relative = pathModule.relative(self.webroot, path);
if(relative.startsWith(".." + pathModule.sep) || relative == "..") {
fs.readFileSync(path); // NOT OK!
} else {
fs.readFileSync(path); // OK!
}
let newpath = pathModule.normalize(path);
var relativePath = pathModule.relative(pathModule.normalize(workspaceDir), newpath);
if (relativePath.indexOf('..' + pathModule.sep) === 0) {
fs.readFileSync(newpath); // NOT OK!
} else {
fs.readFileSync(newpath); // OK!
}
let newpath = pathModule.normalize(path);
var relativePath = pathModule.relative(pathModule.normalize(workspaceDir), newpath);
if (relativePath.indexOf('../') === 0) {
fs.readFileSync(newpath); // NOT OK!
} else {
fs.readFileSync(newpath); // OK!
}
let newpath = pathModule.normalize(path);
var relativePath = pathModule.relative(pathModule.normalize(workspaceDir), newpath);
if (pathModule.normalize(relativePath).indexOf('../') === 0) {
fs.readFileSync(newpath); // NOT OK!
} else {
fs.readFileSync(newpath); // OK!
}
let newpath = pathModule.normalize(path);
var relativePath = pathModule.relative(pathModule.normalize(workspaceDir), newpath);
if (pathModule.normalize(relativePath).indexOf('../')) {
fs.readFileSync(newpath); // OK!
} else {
fs.readFileSync(newpath); // NOT OK!
}
});
var isPathInside = require("is-path-inside"),
pathIsInside = require("path-is-inside");
app.get('/pseudo-normalizations', (req, res) => {
let path = req.query.path;
fs.readFileSync(path); // NOT OK
if (isPathInside(path, SAFE)) {
fs.readFileSync(path); // OK
return;
} else {
fs.readFileSync(path); // NOT OK
}
if (pathIsInside(path, SAFE)) {
fs.readFileSync(path); // NOT OK - can be of the form 'safe/directory/../../../etc/passwd'
return;
} else {
fs.readFileSync(path); // NOT OK
}
let normalizedPath = pathModule.join(SAFE, path);
if (pathIsInside(normalizedPath, SAFE)) {
fs.readFileSync(normalizedPath); // OK
return;
} else {
fs.readFileSync(normalizedPath); // NOT OK
}
if (pathIsInside(normalizedPath, SAFE)) {
fs.readFileSync(normalizedPath); // OK
return;
} else {
fs.readFileSync(normalizedPath); // NOT OK
}
});
app.get('/yet-another-prefix', (req, res) => {
let path = pathModule.resolve(req.query.path);
fs.readFileSync(path); // NOT OK
var abs = pathModule.resolve(path);
if (abs.indexOf(root) !== 0) {
fs.readFileSync(path); // NOT OK
return;
}
fs.readFileSync(path); // OK
});
var rootPath = process.cwd();
app.get('/yet-another-prefix2', (req, res) => {
let path = req.query.path;
fs.readFileSync(path); // NOT OK
var requestPath = pathModule.join(rootPath, path);
var targetPath;
if (!allowPath(requestPath, rootPath)) {
targetPath = rootPath;
fs.readFileSync(requestPath); // NOT OK
} else {
targetPath = requestPath;
fs.readFileSync(requestPath); // OK
}
fs.readFileSync(targetPath); // OK
function allowPath(requestPath, rootPath) {
return requestPath.indexOf(rootPath) === 0;
}
});

View File

@@ -0,0 +1,53 @@
var http = require("http"),
url = require("url"),
fs = require("fs"),
gracefulFs = require("graceful-fs"),
fsExtra = require("fs-extra"),
originalFs = require("original-fs");
var server = http.createServer(function(req, res) {
var path = url.parse(req.url, true).query.path;
fs.readFileSync(path); // NOT OK
gracefulFs.readFileSync(path); // NOT OK
fsExtra.readFileSync(path); // NOT OK
originalFs.readFileSync(path); // NOT OK
getFsModule(true).readFileSync(path); // NOT OK
getFsModule(false).readFileSync(path); // NOT OK
require("./my-fs-module").require(true).readFileSync(path); // NOT OK
let flexibleModuleName = require(process.versions["electron"]
? "original-fs"
: "fs");
flexibleModuleName.readFileSync(path); // NOT OK
});
function getFsModule(special) {
if (special) {
return require("fs");
} else {
return require("original-fs");
}
}
var util = require("util");
http.createServer(function(req, res) {
var path = url.parse(req.url, true).query.path;
util.promisify(fs.readFileSync)(path); // NOT OK
require("bluebird").promisify(fs.readFileSync)(path); // NOT OK
require("bluebird").promisifyAll(fs).readFileSync(path); // NOT OK
});
const asyncFS = require("./my-async-fs-module");
http.createServer(function(req, res) {
var path = url.parse(req.url, true).query.path;
fs.readFileSync(path); // NOT OK
asyncFS.readFileSync(path); // NOT OK
});

View File

@@ -0,0 +1,18 @@
const puppeteer = require('puppeteer');
const parseTorrent = require('parse-torrent');
(async () => {
let tainted = "dir/" + parseTorrent(torrent).name + ".torrent.data";
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.pdf({ path: tainted, format: 'a4' });
const pages = await browser.pages();
for (let i = 0; i < something(); i++) {
pages[i].screenshot({ path: tainted });
}
await browser.close();
})();

View File

@@ -0,0 +1,34 @@
var fs = require('fs'),
http = require('http'),
url = require('url');
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
fs.readFileSync(path); // NOT OK
var obj = bla ? something() : path;
fs.readFileSync(obj.sub); // NOT OK
obj.sub = "safe";
fs.readFileSync(obj.sub); // OK
obj.sub2 = "safe";
if (random()) {
fs.readFileSync(obj.sub2); // OK
}
if (random()) {
obj.sub3 = "safe"
}
fs.readFileSync(obj.sub3); // NOT OK
obj.sub4 =
fs.readFileSync(obj.sub4) ? // NOT OK
fs.readFileSync(obj.sub4) : // NOT OK
fs.readFileSync(obj.sub4); // NOT OK
});
server.listen();

View File

@@ -0,0 +1,17 @@
var fs = require('fs'),
http = require('http'),
url = require('url'),
sanitize = require('sanitize-filename'),
pathModule = require('path')
;
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
res.write(fs.readFileSync(['public', path].join('/'))); // BAD - but not flagged because we have no array-steps [INCONSISTENCY]
let parts = ['public', path];
parts = parts.map(x => x.toLowerCase());
res.write(fs.readFileSync(parts.join('/'))); // BAD - but not flagged because we have no array-steps [INCONSISTENCY]
});
server.listen();

View File

@@ -0,0 +1,8 @@
var express = require('express');
var app = express();
app.get('/some/path', function(req, res) {
// BAD: loading a module based on un-sanitized query parameters
var m = require(req.param("module"));
});

View File

@@ -0,0 +1,28 @@
var express = require('express');
let path = require('path');
var app = express();
app.get('/some/path/:x', function(req, res) {
// BAD: sending a file based on un-sanitized query parameters
res.sendFile(req.param("gimme"));
// BAD: same as above
res.sendfile(req.param("gimme"));
// GOOD: ensures files cannot be accessed outside of root folder
res.sendFile(req.param("gimme"), { root: process.cwd() });
// GOOD: ensures files cannot be accessed outside of root folder
res.sendfile(req.param("gimme"), { root: process.cwd() });
// BAD: doesn't help if user controls root
res.sendFile(req.param("file"), { root: req.param("dir") });
let homeDir = path.resolve('.');
res.sendFile(homeDir + '/data/' + req.params.x); // OK: sendFile disallows ../
res.sendfile('data/' + req.params.x); // OK: sendfile disallows ../
res.sendFile(path.resolve('data', req.params.x)); // NOT OK
res.sendfile(path.join('data', req.params.x)); // NOT OK
res.sendFile(homeDir + path.join('data', req.params.x)); // kinda OK - can only escape from 'data/'
});

View File

@@ -0,0 +1,31 @@
var fs = require('fs'),
http = require('http'),
url = require('url');
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
fs.readFileSync(path.substring(i, j)); // OK
fs.readFileSync(path.substring(4)); // NOT OK
fs.readFileSync(path.substring(0, i)); // NOT OK
fs.readFileSync(path.substr(4)); // NOT OK
fs.readFileSync(path.slice(4)); // NOT OK
fs.readFileSync(path.concat(unknown)); // NOT OK
fs.readFileSync(unknown.concat(path)); // NOT OK
fs.readFileSync(unknown.concat(unknown, path)); // NOT OK
fs.readFileSync(path.trim()); // NOT OK
fs.readFileSync(path.toLowerCase()); // NOT OK
fs.readFileSync(path.split('/')); // OK (readFile throws an exception when the filename is an array)
fs.readFileSync(path.split('/')[0]); // OK -- for now
fs.readFileSync(path.split('/')[i]); // NOT OK
fs.readFileSync(path.split(/\//)[i]); // NOT OK
fs.readFileSync(path.split("?")[0]); // NOT OK
fs.readFileSync(path.split(unknown)[i]); // NOT OK -- but not yet flagged [INCONSISTENCY]
fs.readFileSync(path.split(unknown).whatever); // OK -- but still flagged [INCONSISTENCY]
fs.readFileSync(path.split(unknown)); // NOT OK
fs.readFileSync(path.split("?")[i]); // NOT OK -- but not yet flagged [INCONSISTENCY]
});
server.listen();

View File

@@ -0,0 +1,8 @@
const parseTorrent = require('parse-torrent'),
fs = require('fs');
function getTorrentData(dir, torrent){
let name = parseTorrent(torrent).name,
loc = dir + "/" + name + ".torrent.data";
return fs.readFileSync(loc); // NOT OK
}

View File

@@ -0,0 +1,34 @@
var fs = require('fs'),
http = require('http'),
url = require('url'),
sanitize = require('sanitize-filename'),
pathModule = require('path')
;
var server = http.createServer(function(req, res) {
let path = url.parse(req.url, true).query.path;
// BAD: This could read any file on the file system
res.write(fs.readFileSync(path));
if (path === 'foo.txt')
res.write(fs.readFileSync(path)); // GOOD: Path is compared to white-list
let path2 = path;
path2 ||= res.write(fs.readFileSync(path2)); // GOOD: path is falsy
let path3 = path;
path3 &&= res.write(fs.readFileSync(path3)); // BAD: path is truthy
let path4 = path;
path4 ??= res.write(fs.readFileSync(path4)); // GOOD - path is null or undefined - but we don't capture that. [INCONSISTENCY]
let path5 = path;
path5 &&= "clean";
res.write(fs.readFileSync(path5)); // GOOD: path is either falsy or "clean";
let path6 = path;
path6 ||= "clean";
res.write(fs.readFileSync(path6)); // BAD: path can still be tainted
});

View File

@@ -0,0 +1 @@
module.exports = (req, res) => res.render(req.params[0]);

View File

@@ -0,0 +1,17 @@
this.addEventListener('message', function(event) {
document.write(event.data); // NOT OK
})
this.addEventListener('message', function({data}) {
document.write(data); // NOT OK
})
function test() {
function foo(x, event, y) {
document.write(x.data); // OK
document.write(event.data); // NOT OK
document.write(y.data); // OK
}
window.addEventListener("message", foo.bind(null, {data: 'items'}));
}

View File

@@ -0,0 +1,46 @@
import { Component, OnInit, DomSanitizer as DomSanitizer2 } from '@angular/core';
import { ɵgetDOM } from '@angular/common';
import { ActivatedRoute, ActivatedRouteSnapshot, Router } from '@angular/router';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'my-app';
constructor(
private route: ActivatedRoute,
private sanitizer: DomSanitizer,
private router: Router,
private sanitizer2: DomSanitizer2
) {}
ngOnInit() {
this.sanitizer.bypassSecurityTrustHtml(ɵgetDOM().getLocation().href); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.params.foo); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.queryParams.foo); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.fragment); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.paramMap.get('foo')); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.queryParamMap.get('foo')); // NOT OK
this.route.paramMap.subscribe(map => {
this.sanitizer.bypassSecurityTrustHtml(map.get('foo')); // NOT OK
});
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.url[1].path); // NOT OK - though depends on route config
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.url[1].parameters.x); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.url[1].parameterMap.get('x')); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.route.snapshot.url[1].parameterMap.params.x); // NOT OK
this.sanitizer.bypassSecurityTrustHtml(this.router.url); // NOT OK
this.sanitizer2.bypassSecurityTrustHtml(this.router.url); // NOT OK
}
someMethod(routeSnapshot: ActivatedRouteSnapshot) {
this.sanitizer.bypassSecurityTrustHtml(routeSnapshot.paramMap.get('foo')); // NOT OK
}
}

View File

@@ -0,0 +1,16 @@
import classNames from 'classnames';
import classNamesD from 'classnames/dedupe';
import classNamesB from 'classnames/bind';
import clsx from 'clsx';
function main() {
document.body.innerHTML = `<span class="${classNames(window.name)}">Hello<span>`; // NOT OK
document.body.innerHTML = `<span class="${classNamesD(window.name)}">Hello<span>`; // NOT OK
document.body.innerHTML = `<span class="${classNamesB(window.name)}">Hello<span>`; // NOT OK
let unsafeStyle = classNames.bind({foo: window.name});
document.body.innerHTML = `<span class="${unsafeStyle('foo')}">Hello<span>`; // NOT OK
let safeStyle = classNames.bind({});
document.body.innerHTML = `<span class="${safeStyle(window.name)}">Hello<span>`; // NOT OK
document.body.innerHTML = `<span class="${safeStyle('foo')}">Hello<span>`; // OK
document.body.innerHTML = `<span class="${clsx(window.name)}">Hello<span>`; // NOT OK
}

View File

@@ -0,0 +1,22 @@
const d3 = require('d3');
function getTaint() {
return window.name;
}
function doSomething() {
d3.select('#main')
.attr('width', 100)
.style('color', 'red')
.html(getTaint()) // NOT OK
.html(d => getTaint()) // NOT OK
.call(otherFunction)
.html(d => getTaint()); // NOT OK
}
function otherFunction(selection) {
selection
.attr('foo', 'bar')
.html(getTaint()); // NOT OK
}

View File

@@ -0,0 +1,19 @@
import dateFns from 'date-fns';
import dateFnsFp from 'date-fns/fp';
import dateFnsEsm from 'date-fns/esm';
import moment from 'moment';
import dateformat from 'dateformat';
function main() {
let time = new Date();
let taint = decodeURIComponent(window.location.hash.substring(1));
document.body.innerHTML = `Time is ${dateFns.format(time, taint)}`; // NOT OK
document.body.innerHTML = `Time is ${dateFnsEsm.format(time, taint)}`; // NOT OK
document.body.innerHTML = `Time is ${dateFnsFp.format(taint)(time)}`; // NOT OK
document.body.innerHTML = `Time is ${dateFns.format(taint, time)}`; // OK - time arg is safe
document.body.innerHTML = `Time is ${dateFnsFp.format(time)(taint)}`; // OK - time arg is safe
document.body.innerHTML = `Time is ${moment(time).format(taint)}`; // NOT OK
document.body.innerHTML = `Time is ${moment(taint).format()}`; // OK
document.body.innerHTML = `Time is ${dateformat(time, taint)}`; // NOT OK
}

View File

@@ -0,0 +1,4 @@
function test() {
let loc = window.location.href;
$('<a href="' + encodeURIComponent(loc) + '">click</a>'); // OK
}

View File

@@ -0,0 +1,3 @@
document.getElementById('my-id').onclick = function() {
this.parentNode.innerHTML = '<h2><a href="' + location.href + '">A link</a></h2>'; // NOT OK
};

View File

@@ -0,0 +1,11 @@
var express = require('express');
var app = express();
import { JSDOM } from "jsdom";
app.get('/some/path', function (req, res) {
// NOT OK
new JSDOM(req.param("wobble"), { runScripts: "dangerously" });
// OK
new JSDOM(req.param("wobble"), { runScripts: "outside-only" });
});

View File

@@ -0,0 +1,59 @@
// Adapted from the Google Closure externs; original copyright header included below.
/*
* Copyright 2008 The Closure Compiler Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @externs
*/
/**
* @interface
*/
function EventTarget() {}
/**
* Stub for the DOM hierarchy.
*
* @constructor
* @extends {EventTarget}
*/
function DomObjectStub() {}
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.body;
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.value;
/**
* @type {!DomObjectStub}
*/
var document;
/**
* @constructor
* @implements {EventTarget}
*/
function Node() {}
/**
* @type {Node}
*/
Node.prototype.parentNode;

View File

@@ -0,0 +1,17 @@
function test() {
var tainted = document.location.search
$(tainted); // OK - location.search starts with '?'
$("body", tainted); // OK
$("." + tainted); // OK
$("<div id=\"" + tainted + "\">"); // NOT OK
$("body").html("XSS: " + tainted); // NOT OK
$(window.location.hash); // OK - location.hash starts with '#'
$("<b>" + location.toString() + "</b>"); // NOT OK
// Not related to jQuery, but the handling of $() should not affect this sink
let elm = document.getElementById('x');
elm.innerHTML = decodeURIComponent(window.location.hash); // NOT OK
elm.innerHTML = decodeURIComponent(window.location.search); // NOT OK
elm.innerHTML = decodeURIComponent(window.location.toString()); // NOT OK
}

View File

@@ -0,0 +1,13 @@
var express = require('express');
var app = express();
import jwt from "jsonwebtoken";
import { JSDOM } from "jsdom";
app.get('/some/path', function (req, res) {
var taint = req.param("wobble");
jwt.verify(taint, 'my-secret-key', function (err, decoded) {
// NOT OK
new JSDOM(decoded.foo, { runScripts: "dangerously" });
});
});

View File

@@ -0,0 +1,7 @@
import jwt_decode from "jwt-decode";
import $ from "jquery"
$.post(loginUrl(), {data: "foo"}, (data, xhr) => {
var decoded = jwt_decode(data);
$.jGrowl(decoded); // NOT OK - but only flagged with additional sources [INCONSISTENCY]
});

View File

@@ -0,0 +1,15 @@
let nodemailer = require('nodemailer');
let express = require('express');
let app = express();
let backend = require('./backend');
app.post('/private_message', (req, res) => {
let transport = nodemailer.createTransport({});
transport.sendMail({
from: 'webmaster@example.com',
to: backend.getUserEmail(req.query.receiver),
subject: 'Private message',
text: `Hi, you got a message from someone. ${req.query.message}.`, // OK
html: `Hi, you got a message from someone. ${req.query.message}.`, // NOT OK
});
});

View File

@@ -0,0 +1,46 @@
function test() {
var target = document.location.search
$('myId').html(sanitize ? DOMPurify.sanitize(target) : target); // OK
$('myId').html(target); // NOT OK
var tainted = target;
$('myId').html(tainted); // NOT OK
if (sanitize) {
tainted = DOMPurify.sanitize(tainted);
}
$('myId').html(tainted); // OK
inner(target);
function inner(x) {
$('myId').html(x); // NOT OK
if (sanitize) {
x = DOMPurify.sanitize(x);
}
$('myId').html(x); // OK
}
}
function badSanitizer() {
var target = document.location.search
function sanitizeBad(x) {
return x; // No sanitization;
}
var tainted2 = target;
$('myId').html(tainted2); // NOT OK
if (sanitize) {
tainted2 = sanitizeBad(tainted2);
}
$('myId').html(tainted2); // NOT OK
var tainted3 = target;
$('myId').html(tainted3); // NOT OK
if (sanitize) {
tainted3 = sanitizeBad(tainted3);
}
$('myId').html(tainted3); // NOT OK
$('myId').html(sanitize ? sanitizeBad(target) : target); // NOT OK
}

View File

@@ -0,0 +1,3 @@
import { createContext } from 'react';
export let MyContext = createContext({root: null});

View File

@@ -0,0 +1,10 @@
import express from 'express';
import { WebView } from 'react-native';
var app = express();
app.get('/some/path', function(req, res) {
let tainted = req.param("code");
<WebView html={tainted}/>; // NOT OK
<WebView source={{html: tainted}}/>; // NOT OK
});

View File

@@ -0,0 +1,5 @@
import { MyContext } from './react-create-context';
export function renderMain() {
return <MyContext.Provider value={{root: document.body}}></MyContext.Provider>
}

View File

@@ -0,0 +1,20 @@
import { useContext, Component } from 'react';
import { MyContext } from './react-create-context';
function useMyContext() {
return useContext(MyContext);
}
export function useDoc1() {
let { root } = useMyContext();
root.appendChild(window.name); // NOT OK
}
class C extends Component {
foo() {
let { root } = this.context;
root.appendChild(window.name); // NOT OK
}
}
C.contextType = MyContext;

View File

@@ -0,0 +1,33 @@
import { useState } from 'react';
function initialState() {
let [state, setState] = useState(window.name);
return <div dangerouslySetInnerHTML={{__html: state}}></div>; // NOT OK
}
function setStateValue() {
let [state, setState] = useState('foo');
setState(window.name);
return <div dangerouslySetInnerHTML={{__html: state}}></div>; // NOT OK
}
function setStateValueLazy() {
let [state, setState] = useState('foo');
setState(() => window.name);
return <div dangerouslySetInnerHTML={{__html: state}}></div>; // NOT OK
}
function setStateValueLazy() {
let [state, setState] = useState('foo');
setState(prev => {
document.body.innerHTML = prev; // NOT OK
})
setState(() => window.name);
}
function setStateValueSafe() {
let [state, setState] = useState('foo');
setState('safe');
setState(() => 'also safe');
return <div dangerouslySetInnerHTML={{__html: state}}></div>; // OK
}

View File

@@ -0,0 +1,49 @@
function escapeHtml(s) {
var amp = /&/g, lt = /</g, gt = />/g;
return s.toString()
.replace(amp, '&amp;')
.replace(lt, '&lt;')
.replace(gt, '&gt;');
}
function escapeAttr(s) {
return s.toString()
.replace(/'/g, '%22')
.replace(/"/g, '%27');
}
function test() {
var tainted = window.name;
var elt = document.createElement();
elt.innerHTML = "<a href=\"" + escapeAttr(tainted) + "\">" + escapeHtml(tainted) + "</a>"; // OK
elt.innerHTML = "<div>" + escapeAttr(tainted) + "</div>"; // NOT OK, but not flagged - [INCONSISTENCY]
const regex = /[<>'"&]/;
if (regex.test(tainted)) {
elt.innerHTML = '<b>' + tainted + '</b>'; // NOT OK
} else {
elt.innerHTML = '<b>' + tainted + '</b>'; // OK
}
if (!regex.test(tainted)) {
elt.innerHTML = '<b>' + tainted + '</b>'; // OK
} else {
elt.innerHTML = '<b>' + tainted + '</b>'; // NOT OK
}
if (regex.exec(tainted)) {
elt.innerHTML = '<b>' + tainted + '</b>'; // NOT OK
} else {
elt.innerHTML = '<b>' + tainted + '</b>'; // OK
}
if (regex.exec(tainted) != null) {
elt.innerHTML = '<b>' + tainted + '</b>'; // NOT OK
} else {
elt.innerHTML = '<b>' + tainted + '</b>'; // OK
}
if (regex.exec(tainted) == null) {
elt.innerHTML = '<b>' + tainted + '</b>'; // OK
} else {
elt.innerHTML = '<b>' + tainted + '</b>'; // NOT OK
}
elt.innerHTML = tainted.replace(/<\w+/g, ''); // NOT OK
}

View File

@@ -0,0 +1,30 @@
(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
var href = localStorage.getItem('local');
$('myId').html("<a href=\"" + href + ">foobar</a>"); // NOT OK
if (href.indexOf("\"") !== -1) {
return;
}
$('myId').html("<a href=\"" + href + "/>"); // OK
var href2 = localStorage.getItem('local');
if (href2.indexOf("\"") !== -1) {
return;
}
$('myId').html("\n<a href=\"" + href2 + ">foobar</a>"); // OK
var href3 = localStorage.getItem('local');
if (href3.indexOf("\"") !== -1) {
return;
}
$('myId').html('\r\n<a href="/' + href3 + '">' + "something" + '</a>'); // OK
});

View File

@@ -0,0 +1,12 @@
document.write(document.location.href.charCodeAt(0)); // OK
document.write(document.location); // NOT OK
document.write(document.location.href); // NOT OK
document.write(document.location.href.valueOf()); // NOT OK
document.write(document.location.href.sup()); // NOT OK
document.write(document.location.href.toUpperCase()); // NOT OK
document.write(document.location.href.trimLeft()); // NOT OK
document.write(String.fromCharCode(document.location.href)); // NOT OK
document.write(String(document.location.href)); // NOT OK
document.write(escape(document.location.href)); // OK (for now)
document.write(escape(escape(escape(document.location.href)))); // OK (for now)

View File

@@ -0,0 +1,12 @@
(function() {
var translate = {
"own goal": "backpass",
"fumble": "feint"
};
var target = document.location.search
var searchParams = new URLSearchParams(target.substring(1));
// NOT OK
$('original-term').html(searchParams.get('term'));
// OK
$('translated-term').html(translate[searchParams.get('term')]);
})();

View File

@@ -0,0 +1,433 @@
function test() {
var target = document.location.search
// NOT OK
$('myId').html(target)
// NOT OK
document.write("<OPTION value=1>"+document.location.href.substring(document.location.href.indexOf("default=")+8)+"</OPTION>");
document.write("<OPTION value=2>English</OPTION>");
// NOT OK
$('<div style="width:' + target + 'px">');
$('<div style="width:' + +target + 'px">'); // OK
$('<div style="width:' + parseInt(target) + 'px">'); // OK
let params = (new URL(document.location)).searchParams;
$('name').html(params.get('name')); // NOT OK
var searchParams = new URLSearchParams(target.substring(1));
$('name').html(searchParams.get('name')); // NOT OK
}
function foo(target) {
// NOT OK
$('myId').html(target);
}
foo(document.location.search);
function bar() {
return document.location.search;
}
// NOT OK
$('myId').html(bar());
function baz(x) {
return x;
}
// NOT OK
$('myId').html(baz(document.location.search));
function wrap(s) {
return "<div>" + s + "</div>";
}
// NOT OK
$('myId').html(wrap(document.location.search));
function chop(s) {
if (s)
return s.substr(1);
return "";
}
// NOT OK
$('myId').html(chop(document.location.search));
// NOT OK (duplicated to test precision of flow tracking)
$('myId').html(chop(document.location.search));
// NOT OK
$('myId').html(wrap(chop(bar())));
function dangerouslySetInnerHtml(s) {
// NOT OK
$('myId').html(s);
}
dangerouslySetInnerHtml(document.location.search);
dangerouslySetInnerHtml(document.location.search);
// NOT OK
$('myId').html(bar());
[,document.location.search].forEach(function(x) {
if (x)
// NOT OK
$('myId').html(x);
});
// NOT OK
let s = <span dangerouslySetInnerHTML={{__html: document.location.search}}/>;
angular.module('myApp', [])
.service("myService", function($sce, $other) {
$sce.trustAsHtml(document.location.search); // NOT OK
$sce.trustAsCss(document.location.search); // NOT OK
$sce.trustAsUNKNOWN(document.location.search); // OK
$sce.trustAs($sce.HTML, document.location.search); // NOT OK
$sce.trustAs($sce.CSS, document.location.search); // NOT OK
$sce.trustAs(UNKNOWN, document.location.search); // OK
$other.trustAsHtml(document.location.search); // OK
})
.service("myService2", function() {
angular.element('<div>').html(document.location.search); // NOT OK
angular.element('<div>').html('SAFE'); // OK
})
.directive('myCustomer', function() {
return {
link: function(scope, element){
element.html(document.location.search); // NOT OK
element.html('SAFE'); // OK
}
};
})
.service("myService3", function() {
angular.element(document.location.search); // NOT OK
angular.element('SAFE'); // OK
})
function tst() {
var v = document.location.search.substr(1);
// NOT OK
document.write(v);
if (/^\d+$/.test(v)) {
// OK
document.write(v);
}
if ((m = /^\d+$/.exec(v))) {
// OK
document.write(v);
}
if (v.match(/^\d+$/)) {
// OK
document.write(v);
}
if (v.match("^\\d+$")) {
// OK
document.write(v);
}
if (!(/\d+/.test(v))) // not effective - matches "123<script>...</script>"
return;
// NOT OK
document.write(v);
if (!(/^\d+$/.test(v)))
return;
// OK
document.write(v);
}
function angularJSServices() {
angular.module('myApp', [])
.factory("xssSource_to_service", ["xssSinkService1", function(xssSinkService1) {
xssSinkService1(window.location.search);
}])
.factory("xssSinkService1", function(){
return function(v){ $("<div>").html(v); } // NOT OK
})
.factory("xssSource_from_service", ["xssSourceService", function(xssSourceService){
$("<div>").html(xssSourceService()); // NOT OK
}])
.factory("xssSourceService", function(){
return function() { return window.location.search };
})
.factory("innocentSource_to_service", ["xssSinkService2", function(xssSinkService2) {
xssSinkService2("innocent");
}])
.factory("xssSinkService2", function(){
return function(v){ $("<div>").html(v); } // OK
})
.factory("innocentSource_from_service", ["innocentSourceService", function(innocentSourceService){
$("<div>").html(innocentSourceService()); // OK
}])
.factory("innocentSourceService", function(){
return function() { return "innocent" };
})
}
function testDOMParser() {
var target = document.location.search
var parser = new DOMParser();
parser.parseFromString(target, "application/xml"); // NOT OK
}
function references() {
var tainted = document.location.search;
document.body.innerHTML = tainted; // NOT OK
document.createElement().innerHTML = tainted; // NOT OK
createElement().innerHTML = tainted; // NOT OK
document.getElementsByClassName()[0].innerHTML = tainted; // NOT OK
getElementsByClassName()[0].innerHTML = tainted; // NOT OK
getElementsByClassName().item().innerHTML = tainted; // NOT OK
}
function react(){
var tainted = document.location.search;
React.createElement("div", {dangerouslySetInnerHTML: {__html: tainted}}); // NOT OK
React.createFactory("div")({dangerouslySetInnerHTML: {__html: tainted}}); // NOT OK
class C1 extends React.Component {
constructor() {
this.state.tainted1 = tainted;
this.state.notTainted = dbLookup();
this.setState(() => ({ tainted2: tainted }))
this.state = { tainted3: tainted }
this.state.tainted4 = tainted;
}
test() {
$('myId').html(this.state.tainted1); // NOT OK
$('myId').html(this.state.tainted2); // NOT OK
$('myId').html(this.state.tainted3); // NOT OK
$('myId').html(this.state.notTainted); // OK
this.setState(prevState => {
$('myId').html(prevState.tainted4) // NOT OK
});
}
}
class C2 extends React.Component {
test() {
$('myId').html(this.props.tainted1); // NOT OK
$('myId').html(this.props.tainted2); // NOT OK
$('myId').html(this.props.tainted3); // NOT OK
$('myId').html(this.props.notTainted); // OK
this.setState((prevState, prevProps) => {
$('myId').html(prevProps.tainted4) // NOT OK
});
}
}
C2.defaultProps = { tainted1: tainted };
(<C2 tainted2={tainted}/>);
new C2({tainted3: tainted});
new C2({tainted4: tainted});
// realistic example
class C3 extends React.Component {
constructor(props) {
super(props);
this.state.stateTainted = props.propTainted;
}
render() {
return <span dangerouslySetInnerHTML={{__html: this.state.stateTainted}}/>;
}
}
(<C3 propTainted={tainted}/>);
}
function windowName() {
$(window.name); // NOT OK
$(name); // NOT OK
}
function windowNameAssigned() {
for (name of ['a', 'b']) {
$(window.name); // NOT OK
$(name); // OK
}
}
function jqueryLocation() {
$(location); // OK
$(window.location); // OK
$(document.location); // OK
var loc1 = location;
var loc2 = window.location;
var loc3 = document.location;
$(loc1); // OK
$(loc2); // OK
$(loc3); // OK
$("body").append(location); // NOT OK
}
function testCreateContextualFragment() {
var tainted = window.name;
var range = document.createRange();
range.selectNode(document.getElementsByTagName("div").item(0));
var documentFragment = range.createContextualFragment(tainted); // NOT OK
document.body.appendChild(documentFragment);
}
function flowThroughPropertyNames() {
var obj = {};
obj[Math.random()] = window.name;
for (var p in obj)
$(p); // OK
}
function basicExceptions() {
try {
throw location;
} catch(e) {
$("body").append(e); // NOT OK
}
try {
try {
throw location
} finally {}
} catch(e) {
$("body").append(e); // NOT OK
}
}
function handlebarsSafeString() {
return new Handlebars.SafeString(location); // NOT OK!
}
function test2() {
var target = document.location.search
// OK
$('myId').html(target.length)
}
function getTaintedUrl() {
return new URL(document.location);
}
function URLPseudoProperties() {
let params = getTaintedUrl().searchParams;
$('name').html(params.get('name')); // NOT OK
let myUrl = getTaintedUrl();
$('name').html(myUrl.get('name')); // OK (.get is not defined on a URL)
}
function hash() {
function getUrl() {
return new URL(document.location);
}
$(getUrl().hash.substring(1)); // NOT OK
}
function growl() {
var target = document.location.search
$.jGrowl(target); // NOT OK
}
function thisNodes() {
var pluginName = "myFancyJQueryPlugin";
var myPlugin = function () {
var target = document.location.search
this.html(target); // NOT OK. (this is a jQuery object)
this.innerHTML = target // OK. (this is a jQuery object)
this.each(function (i, e) {
this.innerHTML = target; // NOT OK. (this is a DOM-node);
this.html(target); // OK. (this is a DOM-node);
e.innerHTML = target; // NOT OK.
});
}
$.fn[pluginName] = myPlugin;
}
function test() {
var target = document.location.search
// NOT OK
$('myId').html(target)
// OK
$('myid').html(document.location.href.split("?")[0]);
}
function test() {
var target = document.location.search
$('myId').html(target); // NOT OK
$('myId').html(target.taint); // NOT OK
target.taint2 = 2;
$('myId').html(target.taint2); // OK
target.taint3 = document.location.search;
$('myId').html(target.taint3); // NOT OK
target.sub.taint4 = 2
$('myId').html(target.sub.taint4); // OK
$('myId').html(target.taint5); // NOT OK
target.taint5 = "safe";
target.taint6 = 2;
if (random()) {return;}
$('myId').html(target.taint6); // OK
if (random()) {target.taint7 = "safe";}
$('myId').html(target.taint7); // NOT OK
target.taint8 = target.taint8;
$('myId').html(target.taint8); // NOT OK
target.taint9 = (target.taint9 = "safe");
$('myId').html(target.taint9); // OK
}
function hash2() {
var payload = window.location.hash.substr(1);
document.write(payload); // NOT OK
let match = window.location.hash.match(/hello (\w+)/);
if (match) {
document.write(match[1]); // NOT OK
}
document.write(window.location.hash.split('#')[1]); // NOT OK
}
function nonGlobalSanitizer() {
var target = document.location.search
$("#foo").html(target.replace(/<metadata>[\s\S]*<\/metadata>/, '<metadata></metadata>')); // NOT OK
$("#foo").html(target.replace(/<|>/g, '')); // OK
}

View File

@@ -0,0 +1,15 @@
var foo = document.getElementById("foo");
var data = JSON.parse(decodeURIComponent(window.location.search.substr(1)));
foo.setAttribute("src", data.src); // NOT OK
foo.setAttribute("HREF", data.p); // NOT OK
foo.setAttribute("width", data.w); // OK
foo.setAttribute("xlink:href", data.p) // NOT OK
foo.setAttributeNS('xlink', 'href', data.p); // NOT OK
foo.setAttributeNS('foobar', 'href', data.p); // NOT OK
foo.setAttributeNS('baz', 'width', data.w); // OK
for (var p in data)
foo.setAttribute(p, data[p]); // not flagged since attribute name is unknown

View File

@@ -0,0 +1,30 @@
(function () {
var autocompleter = new Bloodhound({
prefetch: remoteUrl
})
autocompleter.initialize();
$('.typeahead').typeahead({}, {
source: autocompleter.ttAdapter(),
templates: {
suggestion: function(loc) {
return loc; // NOT OK - but only flagged when `AdditionalSources` are imported [INCONSISTENCY].
}
}
})
$('.typeahead').typeahead({},
{
name: 'dashboards',
source: function (query, cb) {
var target = document.location.search
cb(target);
},
templates: {
suggestion: function(val) {
return val; // NOT OK
}
}
}
)
})

View File

@@ -0,0 +1,22 @@
function test() {
let tainted = document.location.search;
$("<div>" + tainted + "</div>"); // NOT OK
$(`<div>${tainted}</div>`); // NOT OK
$("<div>".concat(tainted).concat("</div>")); // NOT OK
$(["<div>", tainted, "</div>"].join()); // NOT OK
$("<div id=\"" + tainted + "\"/>"); // NOT OK
$(`<div id="${tainted}"/>`); // NOT OK
$("<div id=\"".concat(tainted).concat("/>")); // NOT OK
$(["<div id=\"", tainted, "\"/>"].join()); // NOT OK
function indirection1(attrs) {
return '<div align="' + (attrs.defaultattr || 'left') + '">' + content + '</div>';
}
function indirection2(attrs) {
return '<div align="'.concat(attrs.defaultattr || 'left').concat('">'.concat(content)).concat('</div>');
}
$(indirection1(document.location.search.attrs)); // NOT OK
$(indirection2(document.location.search.attrs)); // NOT OK
};

View File

@@ -0,0 +1,5 @@
function test(elt) {
var tainted = document.location.search.substring(1);
WinJS.Utilities.setInnerHTMLUnsafe(elt, tainted);
WinJS.Utilities.setOuterHTMLUnsafe(elt, tainted);
}

View File

@@ -0,0 +1,16 @@
$(document).ready(function () {
var xhr = new XMLHttpRequest();
var url = "{{ some_url }}"
xhr.open("GET", url, true)
xhr.setRequestHeader("Content-Type", "application/json")
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) { return }
var json = JSON.parse(xhr.responseText)
$("#myThing").html(json.message);
}
try {
xhr.send()
} catch (error) {
console.log(error)
}
})

View File

@@ -0,0 +1,13 @@
import express from 'express';
import Ajv from 'ajv';
let app = express();
let ajv = new Ajv();
ajv.addSchema({type: 'object', additionalProperties: {type: 'number'}}, 'pollData');
app.post('/polldata', (req, res) => {
if (!ajv.validate('pollData', req.body)) {
res.send(ajv.errorsText()); // NOT OK
}
});

View File

@@ -0,0 +1,216 @@
(function () {
var foo = document.location;
function inner(x) {
unknown(x);
}
try {
unknown(foo);
} catch (e) {
$('myId').html(e); // NOT OK!
}
try {
inner(foo);
} catch (e) {
$('myId').html(e); // NOT OK!
}
try {
unknown(foo + "bar");
} catch (e) {
$('myId').html(e); // NOT OK!
}
try {
unknown({ prop: foo });
} catch (e) {
$('myId').html(e); // NOT OK! - but not detected due to not tainting object that have a tainted propety. [INCONSISTENCY]
}
try {
unknown(["bar", foo]);
} catch (e) {
$('myId').html(e); // NOT OK!
}
function deep(x) {
deep2(x);
}
function deep2(x) {
inner(x);
}
try {
deep("bar" + foo);
} catch (e) {
$('myId').html(e); // NOT OK!
}
try {
var tmp = "bar" + foo;
} catch (e) {
$('myId').html(e); // OK
}
function safe(x) {
var foo = x + "bar";
}
try {
safe(foo);
} catch (e) {
$('myId').html(e); // OK
}
try {
safe.call(null, foo);
} catch (e) {
$('myId').html(e); // OK
}
var myWeirdInner;
try {
myWeirdInner = function (x) {
inner(x);
}
} catch (e) {
$('myId').html(e); // OK
}
try {
myWeirdInner(foo);
} catch (e) {
$('myId').html(e); // NOT OK!
}
$('myId').html(foo); // Direct leak, reported by other query.
try {
unknown(foo.match(/foo/));
} catch (e) {
$('myId').html(e); // NOT OK!
}
try {
unknown([foo, "bar"]);
} catch (e) {
$('myId').html(e); // NOT OK!
}
try {
try {
unknown(foo);
} finally {
// nothing
}
} catch (e) {
$('myId').html(e); // NOT OK!
}
});
var express = require('express');
var app = express();
app.get('/user/:id', function (req, res) {
try {
unknown(req.params.id);
} catch (e) {
res.send("Exception: " + e); // NOT OK!
}
});
(function () {
sessionStorage.setItem('exceptionSession', document.location.search);
try {
unknown(sessionStorage.getItem('exceptionSession'));
} catch (e) {
$('myId').html(e); // NOT OK
}
})();
app.get('/user/:id', function (req, res) {
unknown(req.params.id, (error, res) => {
if (error) {
$('myId').html(error); // NOT OK
return;
}
$('myId').html(res); // OK (for now?)
});
});
(function () {
var foo = document.location.search;
new Promise(resolve => unknown(foo, resolve)).catch((e) => {
$('myId').html(e); // NOT OK
});
try {
null[foo];
} catch (e) {
$('myId').html(e); // NOT OK
}
try {
unknown()[foo];
} catch (e) {
$('myId').html(e); // OK. We are not sure that `unknown()` is null-ish.
}
try {
"foo"[foo]
} catch (e) {
$('myId').html(e); // OK
}
function inner(tainted, resolve) {
unknown(tainted, resolve);
}
new Promise(resolve => inner(foo, resolve)).catch((e) => {
$('myId').html(e); // NOT OK
});
})();
app.get('/user/:id', function (req, res) {
unknown(req.params.id, (error, res) => {
if (error) {
$('myId').html(error); // NOT OK
}
$('myId').html(res); // OK - does not contain an error, and `res` is otherwise unknown.
});
});
app.get('/user/:id', function (req, res) {
try {
res.send(req.params.id);
} catch(err) {
res.send(err); // OK (the above `res.send()` is already reported by js/xss)
}
});
var fs = require("fs");
(function () {
var foo = document.location.search;
try {
// A series of functions does not throw tainted exceptions.
Object.assign(foo, foo)
_.pick(foo, foo);
[foo, foo].join(join);
$.val(foo);
JSON.parse(foo);
/bla/.test(foo);
console.log(foo);
log.info(foo);
localStorage.setItem(foo);
} catch (e) {
$('myId').html(e); // OK
}
})();

View File

@@ -0,0 +1,48 @@
// Adapted from the Google Closure externs; original copyright header included below.
/*
* Copyright 2008 The Closure Compiler Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @externs
*/
/**
* @interface
*/
function EventTarget() {}
/**
* Stub for the DOM hierarchy.
*
* @constructor
* @extends {EventTarget}
*/
function DomObjectStub() {}
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.body;
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.value;
/**
* @type {!DomObjectStub}
*/
var document;

View File

@@ -0,0 +1,104 @@
var express = require('express');
var app = express();
app.get('/user/:id', function(req, res) {
if (!isValidUserId(req.params.id)) {
// BAD: a request parameter is incorporated without validation into the response
res.send("Unknown user: " + req.params.id);
moreBadStuff(req.params, res);
} else {
// TODO: do something exciting
;
}
});
function moreBadStuff(params, res) {
res.send("Unknown user: " + params.id); // NOT OK
}
var marked = require("marked");
app.get('/user/:id', function(req, res) {
res.send(req.body); // NOT OK
res.send(marked(req.body)); // NOT OK
});
var table = require('markdown-table')
app.get('/user/:id', function(req, res) {
res.send(req.body); // NOT OK
var mytable = table([
['Name', 'Content'],
['body', req.body]
]);
res.send(mytable); // NOT OK
});
var showdown = require('showdown');
var converter = new showdown.Converter();
app.get('/user/:id', function(req, res) {
res.send(req.body); // NOT OK
res.send(converter.makeHtml(req.body)); // NOT OK
});
var unified = require('unified');
var markdown = require('remark-parse');
var remark2rehype = require('remark-rehype');
var doc = require('rehype-document');
var format = require('rehype-format');
var html = require('rehype-stringify');
var remark = require("remark");
var sanitize = require("rehype-sanitize");
const { resetExtensions } = require('showdown');
app.get('/user/:id', function (req, res) {
res.send(req.body); // NOT OK
unified()
.use(markdown)
.use(remark2rehype)
.use(doc, { title: '👋🌍' })
.use(format)
.use(html)
.process(req.body, function (err, file) {
res.send(file); // NOT OK
});
res.send(remark().processSync(req.body).toString()); // NOT OK
res.send(remark().use(sanitize).processSync(req.body).toString()); // OK
res.send(unified().use(markdown).processSync(req.body).toString); // NOT OK
remark().process(req.body, (e, f) => {
res.send(f); // NOT OK
})
});
import snarkdown from 'snarkdown';
var snarkdown2 = require("snarkdown");
app.get('/user/:id', function (req, res) {
res.send(req.body); // NOT OK
res.send(snarkdown(req.body)); // NOT OK
res.send(snarkdown2(req.body)); // NOT OK
});
const markdownIt = require('markdown-it')({
html: true
});
const markdownIt2 = require('markdown-it')({});
const markdownIt3 = require('markdown-it')({html: true})
.use(require('markdown-it-highlightjs'));
app.get('/user/:id', function (req, res) {
res.send(req.body); // NOT OK
res.send(markdownIt.render(req.body)); // NOT OK
res.send(markdownIt2.render(req.body)); // OK - no html
res.send(markdownIt3.render(req.body)); // NOT OK
res.send(markdownIt.use(require('markdown-it-sanitizer')).render(req.body)); // OK - HTML is sanitized.
res.send(markdownIt.use(require('markdown-it-abbr')).use(unknown).render(req.body)); // NOT OK
});

View File

@@ -0,0 +1,79 @@
var express = require('express');
var app = express();
app.get('/user/:id', function (req, res) {
if (whatever) {
res.set('Content-Type', 'text/plain');
res.send("FOO: " + req.params.id); // OK - content type is plain text
} else {
res.set('Content-Type', 'text/html');
res.send("FOO: " + req.params.id); // NOT OK - content type is HTML.
}
});
app.get('/user/:id', function (req, res) {
if (whatever) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.send("FOO: " + req.params.id); // OK - content type is JSON
} else {
res.writeHead(404);
res.send("FOO: " + req.params.id); // NOT OK - content type is not set.
}
});
app.get('/user/:id', function (req, res) {
res.writeHead(200, {'Content-Type': 'application/json'});
if (whatever) {
res.send("FOO: " + req.params.id); // OK - content type is JSON
} else {
res.send("FOO: " + req.params.id); // OK - content type is still JSON
}
res.send("FOO: " + req.params.id); // OK - content type is still JSON
});
app.get('/user/:id', function (req, res) {
if (err) {
res.statusCode = 404;
res.end("FOO: " + req.params.id); // NOT OK
} else {
res.setHeader('Content-Type', 'text/plain;charset=utf8');
res.end("FOO: " + req.params.id); // OK
}
});
function textContentType() {
result = "text/plain";
}
app.get('/user/:id', function (req, res) {
if (err) {
res.header({'Content-Type': textContentType()});
res.end("FOO: " + req.params.id); // OK
} else {
res.setHeader('Content-Type', 'text/plain;charset=utf8');
res.end("FOO: " + req.params.id); // OK
}
});
app.get('/user/:id', function (req, res) {
if (err) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.send("FOO: " + req.params.id); // OK - content type is JSON
return;
}
doSomething();
somethingMore();
while(Math.random()) {};
res.writeHead(404);
res.send("FOO: " + req.params.id); // NOT OK - content type is not set.
});
app.get('/user/:id', function (req, res) {
res.header({'Content-Type': textContentType()});
myFancyFunction(() => {
res.send("FOO: " + req.params.id); // OK
});
res.end("FOO: " + req.params.id); // OK
});

View File

@@ -0,0 +1,87 @@
var escape = require('escape-html');
var express = require('express');
var app = express();
app.get('/user/:id', function(req, res) {
if (!isValidUserId(req.params.id))
// GOOD: request parameter is sanitized before incorporating it into the response
res.send("Unknown user: " + escape(req.params.id));
else
// TODO: do something exciting
;
});
app.get('/user/:id', function(req, res) {
if (!isValidUserId(req.params.id))
// GOOD: templating prevents XSS
res.render(invalidUserIdTemplate, { id: req.params.id });
});
app.get('/user/:id', function(req, res) {
if (!isValidUserId(req.params.id)) {
// GOOD: response content type set to text
res.set('Content-Type', 'text/plain');
res.send("Unknown user: " + req.params.id);
} else
// TODO: do something exciting
;
});
function textContentType() {
result = "text/plain";
}
app.get('/user/:id', function(req, res) {
if (!isValidUserId(req.params.id)) {
// GOOD: response content type set to text
res.set('Content-Type', textContentType());
res.send("Unknown user: " + req.params.id);
} else
// TODO: do something exciting
;
});
app.get('/echo', function(req, res) {
var msg = req.params.msg;
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Length', msg.length);
res.end(msg);
});
app.get('/user/:id', function(req, res) {
const url = req.params.id;
if (!/["'&<>]/.exec(url)) {
res.send(url); // OK
}
});
function escapeHtml1 (str) {
if (!/["'&<>]/.exec(str)) {
return str;
}
}
app.get('/user/:id', function(req, res) {
const url = req.params.id;
res.send(escapeHtml1(url)); // OK
});
const matchHtmlRegExp = /["'&<>]/;
function escapeHtml2 (string) {
const str = '' + string;
const match = matchHtmlRegExp.exec(str);
if (!match) {
return str;
}
}
app.get('/user/:id', function(req, res) {
const url = req.params.id;
res.send(escapeHtml2(url)); // OK
});

View File

@@ -0,0 +1,11 @@
const url = require("url");
require("http").createServer(function(req, resp) {
var target = url.parse(req.url, true);
sendTextResponse(resp, target.pathname)
});
function sendTextResponse(resp, text) {
resp.writeHead(200, {"content-type": "text/plain; charset=utf-8"});
resp.end(text);
}

View File

@@ -0,0 +1,142 @@
var express = require('express');
var app = express();
function escapeHtml1(string) {
var str = "" + string;
let escape;
let html = '';
let lastIndex = 0;
for (let index = 0; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escape = '&quot;';
break;
case 38: // &
escape = '&amp;';
break;
case 39: // '
escape = '&#39;';
break;
case 60: // <
escape = '&lt;';
break;
case 62: // >
escape = '&gt;';
break;
default:
continue;
}
if (lastIndex !== index) {
html += str.substring(lastIndex, index);
}
lastIndex = index + 1;
html += escape;
}
return lastIndex !== index
? html + str.substring(lastIndex, index)
: html;
}
function escapeHtml2(s) {
var buf = "";
while (i < s.length) {
var ch = s[i++];
switch (ch) {
case '&':
buf += '&amp;';
break;
case '<':
buf += '&lt;';
break;
case '\"':
buf += '&quot;';
break;
default:
buf += ch;
break;
}
}
return buf;
}
function escapeHtml3(value) {
var i = 0;
var XMLChars = {
AMP: 38, // "&"
QUOT: 34, // "\""
LT: 60, // "<"
GT: 62, // ">"
};
var parts = [value.substring(0, i)];
while (i < length) {
switch (ch) {
case XMLChars.AMP:
parts.push('&amp;');
break;
case XMLChars.QUOT:
parts.push('&quot;');
break;
case XMLChars.LT:
parts.push('&lt;');
break;
case XMLChars.GT:
parts.push('&gt;');
break;
}
++i;
var j = i;
while (i < length) {
ch = value.charCodeAt(i);
if (ch === XMLChars.AMP ||
ch === XMLChars.QUOT || ch === XMLChars.LT ||
ch === XMLChars.GT) {
break;
}
i++;
}
if (j < i) {
parts.push(value.substring(j, i));
}
}
return parts.join('');
}
function escapeHtml4(s) {
var buf = "";
while (i < s.length) {
var ch = s.chatAt(i++);
switch (ch) {
case '&':
buf += '&amp;';
break;
case '<':
buf += '&lt;';
break;
case '\"':
buf += '&quot;';
break;
default:
buf += ch;
break;
}
}
return buf;
}
app.get('/user/:id', function (req, res) {
const url = req.params.id;
res.send(escapeHtml1(url)); // OK
res.send(escapeHtml2(url)); // OK
res.send(escapeHtml3(url)); // OK - but FP [INCONSISTENCY]
res.send(escapeHtml4(url)); // OK
});

View File

@@ -0,0 +1,10 @@
var express = require('express'),
cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
app.get('/cookie/:name', function(req, res) {
// OK
res.send("Here, have a cookie: " + req.cookies[req.params.name]);
});

View File

@@ -0,0 +1,12 @@
let express = require('express');
let isVarName = require('is-var-name');
let app = express();
app.get("/some/path", (req, res) => {
let response = "Hello, world!";
if(req.query.jsonp && isVarName(req.query.jsonp))
response = req.query.jsonp + "(" + response + ")";
res.send(response);
});

View File

@@ -0,0 +1,48 @@
// Adapted from the Google Closure externs; original copyright header included below.
/*
* Copyright 2008 The Closure Compiler Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @externs
*/
/**
* @interface
*/
function EventTarget() {}
/**
* Stub for the DOM hierarchy.
*
* @constructor
* @extends {EventTarget}
*/
function DomObjectStub() {}
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.body;
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.value;
/**
* @type {!DomObjectStub}
*/
var document;

View File

@@ -0,0 +1,8 @@
var express = require('express');
express().get('/user/', function(req, res) {
var evil = req.query.evil;
res.send(console.log("<div>%s</div>", evil)); // OK (returns undefined)
res.send(util.format("<div>%s</div>", evil)); // NOT OK
res.send(require("printf")("<div>%s</div>", evil)); // NOT OK
});

View File

@@ -0,0 +1,3 @@
export default function handler(req, res) {
res.send(req.url);
}

View File

@@ -0,0 +1,55 @@
let express = require('express');
let underscore = require('underscore');
let lodash = require('lodash');
let R = require('ramda');
let app = express();
app.get("/some/path", (req, res) => {
function sendResponse(x, y) {
res.send(x + y); // NOT OK
}
let callback = sendResponse.bind(null, req.url);
[1, 2, 3].forEach(callback);
});
app.get("/underscore", (req, res) => {
function sendResponse(x, y) {
res.send(x + y); // NOT OK
}
let callback = underscore.partial(sendResponse, req.url);
[1, 2, 3].forEach(callback);
});
app.get("/lodash", (req, res) => {
function sendResponse(x, y) {
res.send(x + y); // NOT OK
}
let callback = lodash.partial(sendResponse, req.url);
[1, 2, 3].forEach(callback);
});
app.get("/ramda", (req, res) => {
function sendResponse(x, y) {
res.send(x + y); // NOT OK
}
let callback = R.partial(sendResponse, [req.url]);
[1, 2, 3].forEach(callback);
});
app.get("/return", (req, res) => {
function getFirst(x, y) {
return x;
}
let callback = getFirst.bind(null, req.url);
res.send(callback); // OK - the callback itself is not tainted
res.send(callback()); // NOT OK - but not currently detected [INCONSISTENCY]
res.send(getFirst("Hello")); // OK - argument is not tainted from this call site
});

View File

@@ -0,0 +1,11 @@
let express = require('express');
let app = express();
app.get("/some/path", (req, res) => {
new Promise((resolve, reject) => resolve(req.query.data))
.then(x => res.send(x)); // NOT OK
new Promise((resolve, reject) => resolve(req.query.data))
.then(x => escapeHtml(x))
.then(x => res.send(x)); // OK
});

View File

@@ -0,0 +1,24 @@
var express = require('express');
var app = express();
app.get('/user/:id', function(req, res) {
let { p, q: r } = req.params;
res.send(p); // NOT OK
res.send(r); // NOT OK
});
const aKnownValue = "foo";
app.get('/bar', function(req, res) {
let { p } = req.params;
if (p == aKnownValue)
res.send(p); // OK
res.send(p); // NOT OK
if (p != aKnownValue)
res.send(p); // NOT OK
else
res.send(p); // OK
});

View File

@@ -0,0 +1,48 @@
// Adapted from the Google Closure externs; original copyright header included below.
/*
* Copyright 2008 The Closure Compiler Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @externs
*/
/**
* @interface
*/
function EventTarget() {}
/**
* Stub for the DOM hierarchy.
*
* @constructor
* @extends {EventTarget}
*/
function DomObjectStub() {}
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.body;
/**
* @type {!DomObjectStub}
*/
DomObjectStub.prototype.value;
/**
* @type {!DomObjectStub}
*/
var document;

View File

@@ -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
});
});

View File

@@ -0,0 +1,8 @@
const parseTorrent = require('parse-torrent'),
express = require('express');
express().get('/user/:id', function(req, res) {
let torrent = parseTorrent(unknown),
name = torrent.name;
res.send(name); // NOT OK
});

View File

@@ -0,0 +1,9 @@
(function (factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery', 'jquery-ui'], factory);
} else {
factory(jQuery);
}
}(function ($) {
$("<span>" + $.trim("foo") + "</span>"); // OK
}));

View File

@@ -0,0 +1,77 @@
module.exports.xssThroughHTMLConstruction = function (s) {
const html = "<span>" + s + "</span>";// NOT OK
document.querySelector("#html").innerHTML = html;
}
module.exports.xssThroughXMLParsing = function (s) {
const doc = new DOMParser().parseFromString(s, "text/xml"); // NOT OK
document.querySelector("#xml").appendChild(doc.documentElement);
}
module.exports.xssThroughMoreComplexXMLParsing = function (s) {
const doc = new DOMParser().parseFromString(s, "text/xml"); // NOT OK
const xml = doc.documentElement;
const tmp = document.createElement('span');
tmp.appendChild(xml.cloneNode());
document.querySelector("#xml").appendChild(tmp);
}
const markdown = require('markdown-it')({html: true});
module.exports.xssThroughMarkdown = function (s) {
const html = markdown.render(s); // NOT OK
document.querySelector("#markdown").innerHTML = html;
}
const striptags = require('striptags');
module.exports.sanitizedHTML = function (s) {
const html = striptags("<span>" + s + "</span>"); // OK
document.querySelector("#sanitized").innerHTML = html;
}
module.exports.ts = require("./typed");
module.exports.jquery = require("./jquery-plugin");
module.exports.plainDOMXMLParsing = function (s) {
const doc = new DOMParser().parseFromString(s, "text/xml"); // OK - is never added to the DOM.
}
class Foo {
constructor(s) {
this.step = s;
}
doXss() {
// not called here, but still bad.
document.querySelector("#class").innerHTML = "<span>" + this.step + "</span>"; // NOT OK
}
}
module.exports.createsClass = function (s) {
return new Foo(s);
}
$.fn.xssPlugin = function (options) {
const defaults = {
name: "name"
};
const settings = $.extend(defaults, options);
return this.each(function () {
$("<b>" + settings.name + "</b>").appendTo(this); // NOT OK
});
}
module.exports.guards = function (attrVal) {
document.querySelector("#id").innerHTML = "<img alt=\"" + attrVal + "\"/>"; // NOT OK
document.querySelector("#id").innerHTML = "<img alt=\"" + attrVal.replace(/"|'/g, "") + "\"/>"; // OK
if (attrVal.indexOf("\"") === -1 && attrVal.indexOf("'") === -1) {
document.querySelector("#id").innerHTML = "<img alt=\"" + attrVal + "\"/>"; // OK
}
}
module.exports.intentionalTemplate = function (obj) {
const html = "<span>" + obj.spanTemplate + "</span>"; // OK
document.querySelector("#template").innerHTML = html;
}

View File

@@ -0,0 +1,20 @@
export function basicHtmlConstruction(s: string) {
const html = "<span>" + s + "</span>"; // NOT OK
document.body.innerHTML = html;
}
export function insertIntoCreatedDocument(s: string) {
const newDoc = document.implementation.createHTMLDocument("");
newDoc.body.innerHTML = "<span>" + s + "</span>"; // OK - inserted into document disconnected from the main DOM. [INCONSISTENCY]
}
export function id(s: string) {
return s;
}
export function notVulnerable() {
const s = id("x");
const html = "<span>" + s + "</span>"; // OK
document.body.innerHTML = html;
}

Some files were not shown because too many files have changed in this diff Show More