mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
JS: Migrate CodeQL tests for ML-powered queries
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
extraction/ExtractEndpointData.ql
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
extraction/ExtractEndpointDataEvaluation.ql
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
extraction/ExtractEndpointDataTraining.ql
|
||||
@@ -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 |
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
const MarsDB = require("marsdb");
|
||||
|
||||
const myDoc = new MarsDB.Collection("myDoc");
|
||||
|
||||
const db = {
|
||||
myDoc
|
||||
};
|
||||
|
||||
module.exports = db;
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export const MyModel = mongoose.model('MyModel', getSchema());
|
||||
@@ -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
|
||||
});
|
||||
@@ -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));
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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}`);
|
||||
});
|
||||
});
|
||||
@@ -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 + '"');
|
||||
});
|
||||
@@ -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 + "'");
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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 + '"');
|
||||
});
|
||||
@@ -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).
|
||||
@@ -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)));
|
||||
});
|
||||
@@ -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)
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}, {});
|
||||
@@ -0,0 +1,7 @@
|
||||
exports.require = function(special) {
|
||||
if (special) {
|
||||
return require("fs");
|
||||
} else {
|
||||
return require("original-fs");
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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();
|
||||
})();
|
||||
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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"));
|
||||
});
|
||||
@@ -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/'
|
||||
});
|
||||
@@ -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();
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = (req, res) => res.render(req.params[0]);
|
||||
@@ -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'}));
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
function test() {
|
||||
let loc = window.location.href;
|
||||
$('<a href="' + encodeURIComponent(loc) + '">click</a>'); // OK
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
document.getElementById('my-id').onclick = function() {
|
||||
this.parentNode.innerHTML = '<h2><a href="' + location.href + '">A link</a></h2>'; // NOT OK
|
||||
};
|
||||
@@ -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" });
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
@@ -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" });
|
||||
});
|
||||
});
|
||||
@@ -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]
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export let MyContext = createContext({root: null});
|
||||
@@ -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
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { MyContext } from './react-create-context';
|
||||
|
||||
export function renderMain() {
|
||||
return <MyContext.Provider value={{root: document.body}}></MyContext.Provider>
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
function escapeHtml(s) {
|
||||
var amp = /&/g, lt = /</g, gt = />/g;
|
||||
return s.toString()
|
||||
.replace(amp, '&')
|
||||
.replace(lt, '<')
|
||||
.replace(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
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
@@ -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)
|
||||
@@ -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')]);
|
||||
})();
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -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
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
function test(elt) {
|
||||
var tainted = document.location.search.substring(1);
|
||||
WinJS.Utilities.setInnerHTMLUnsafe(elt, tainted);
|
||||
WinJS.Utilities.setOuterHTMLUnsafe(elt, tainted);
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -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;
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 = '"';
|
||||
break;
|
||||
case 38: // &
|
||||
escape = '&';
|
||||
break;
|
||||
case 39: // '
|
||||
escape = ''';
|
||||
break;
|
||||
case 60: // <
|
||||
escape = '<';
|
||||
break;
|
||||
case 62: // >
|
||||
escape = '>';
|
||||
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 += '&';
|
||||
break;
|
||||
case '<':
|
||||
buf += '<';
|
||||
break;
|
||||
case '\"':
|
||||
buf += '"';
|
||||
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('&');
|
||||
break;
|
||||
case XMLChars.QUOT:
|
||||
parts.push('"');
|
||||
break;
|
||||
case XMLChars.LT:
|
||||
parts.push('<');
|
||||
break;
|
||||
case XMLChars.GT:
|
||||
parts.push('>');
|
||||
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 += '&';
|
||||
break;
|
||||
case '<':
|
||||
buf += '<';
|
||||
break;
|
||||
case '\"':
|
||||
buf += '"';
|
||||
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
|
||||
});
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function handler(req, res) {
|
||||
res.send(req.url);
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
}));
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user