Add Restify/Spife support

This commit is contained in:
Alvaro Muñoz
2022-10-03 15:50:57 +02:00
committed by Alvaro Muñoz
parent 42a97b26bb
commit 6ab62da015
18 changed files with 1775 additions and 36 deletions

View File

@@ -35,4 +35,3 @@
| restify.js:9:5:9:17 | req.trailer() | header |
| restify.js:10:5:10:16 | req.header() | header |
| restify.js:11:5:11:11 | req.url | url |
| restify.js:12:5:12:15 | req.cookies | cookie |

View File

@@ -299,11 +299,13 @@ test_RemoteFlowSources
| src/http.js:30:28:30:32 | chunk |
| src/http.js:40:23:40:30 | authInfo |
| src/http.js:45:23:45:27 | error |
| src/http.js:63:17:63:33 | req.query.myParam |
| src/http.js:73:18:73:22 | chunk |
| src/http.js:82:18:82:22 | chunk |
| src/https.js:6:26:6:32 | req.url |
| src/https.js:8:3:8:20 | req.headers.cookie |
| src/https.js:9:3:9:17 | req.headers.foo |
| src/indirect2.js:10:12:10:25 | req.params.key |
| src/indirect.js:17:28:17:34 | req.url |
test_RouteHandler
| createServer.js:2:20:2:41 | functio ... res) {} | createServer.js:2:1:2:42 | https.c ... es) {}) |

View File

@@ -0,0 +1,207 @@
var restify = require('restify');
const restifyPlugins = require('restify-plugins');
var clients = require('restify-clients');
const opts = {
formatters: {
'text/plain': function(req, res, body) { // test: formatter
if (body instanceof Error) {
return '<html><body>' + body.message + '</body></html>'; // test: stackTraceExposureSink
} else {
return '<html><body>' + body + req.params.name + '</body></html>'; // test: source, stackTraceExposureSink, !xssSink, !xss
}
}
}
}
const _server = restify.createServer(opts)
const server = restify.createServer({
formatters: {
'text/html': function(req, res, body) { // test: formatter
if (body instanceof Error) {
return '<html><body>' + body.message + '</body></html>'; // test: stackTraceExposureSink, xssSink
} else {
return '<html><body>' + body + req.params.name + '</body></html>'; // test: source, stackTraceExposureSink, xssSink, xss
}
}
}
});
// The pre handler chain is executed before routing. That means these handlers will execute for an incoming request even if its for a route that you did not register.
server.pre(restify.plugins.pre.dedupeSlashes());
server.pre(function(req, res, next) { // test: handler
return next();
});
// The use handler chains is executed after a route has been chosen to service the request.
server.use(restifyPlugins.jsonBodyParser({ mapParams: true })); // TODO: prototype pollution?
server.use(restifyPlugins.acceptParser(server.acceptable));
server.use(restifyPlugins.queryParser({ mapParams: true })); // TODO: prototype pollution?
server.use(restifyPlugins.fullResponse());
server.use(function(req, res, next) { // test: handler
return next();
});
function filter(req, res, next) { // test: handler
return next();
}
function filter1(req, res, next) { // test: handler
return next();
}
function filter2(req, res, next) { // test: handler
return next();
}
function filter3(req, res, next) { // test: handler
return next();
}
function filter4(req, res, next) { // test: handler
return next();
}
function filter5(req, res, next) { // test: handler
return next();
}
function filter6(req, res, next) { // test: handler
return next();
}
const handlers = [filter5, filter6];
server.use(filter); // test: setup
server.use(filter1, filter2); // test: setup
server.use([filter3, filter4]); // test: setup
server.use(handlers); // setup
function respond(req, res, next) { // test: handler
res.send('hello ' + req.params.name); // test: source, stackTraceExposureSink
res.send('hello ' + req.params["name"]); // test: source, stackTraceExposureSink
res.send('hello ' + req.query.name); // test: source, stackTraceExposureSink
res.send('hello ' + req.params[0]); // test: source, stackTraceExposureSink
res.redirect({
hostname: req.params.name, // test: source, redirectSink
pathname: '/bar',
port: 80,
secure: true,
permanent: true,
query: {
a: 1
}
}, next);
res.redirect(301, req.params.name, next); // test: source, redirectSink
res.redirect(req.params.name, next); // test: source, redirectSink
next();
}
server.get('/hello/:name', respond); // test: setup
server.head('/hello/:name', respond); // test: setup
server.get('/', function(req, res, next) { // test: setup, handler
res.send('home')
return next();
});
server.get('/foo', // test: setup
function(req, res, next) { // test: handler
req.someData = req.params.name; // test: source
return next();
},
function(req, res, next) { // test: handler
res.header("Content-Type", "text/html");
res.send(req.someData); // test: stackTraceExposureSink, xssSink, xss
return next();
}
);
server.get('/foo2', // test: setup
[function(req, res, next) { // test: handler
req.someData = 'foo';
return next();
},
function(req, res, next) { // test: handler
res.send(req.someData); // test: stackTraceExposureSink
return next();
}]
);
function xss(req, res, next) { // test: handler
res.header("Content-Type", "text/html");
res.send('hello ' + req.query.name); // test: source, stackTraceExposureSink, xssSink, xss
next();
}
server["get"]('/xss', xss); // test: setup
function xss2(req, res, next) { // test: handler
var body = req.params.name; // test: source
res.writeHead(200, {
'Content-Length': Buffer.byteLength(body),
'Content-Type': 'text/html'
});
res.write(body); // test: stackTraceExposureSink, xssSink, xss
res.end();
next();
}
["get", "head"].forEach(method => {
server[method]('/xss2', xss2);
});
function xss3(req, res, next) { // test: handler
res.header("Content-Type", "text/html");
res.send('hello ' + req.header("foo")); // test: source, stackTraceExposureSink, xssSink, !xss
next();
}
server["get"]('/xss3', xss3); // test: setup
function sendV2(req, res, next) { // test: candidateHandler
res.set({
"Content-Type": "text/html",
"access-control-allow-origin": "*", // test: corsMiconfigurationSink
"access-control-allow-headers": "Content-Type, Authorization, Content-Length, X-Requested-With",
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
"access-control-allow-credentials": "true"
})
res.send('hello ' + req.params.name); // test: source, stackTraceExposureSink, xssSink, xss
clients.createJsonClient({
url: req.params.uri, // test: source, ssrfSink
});
clients.createJsonClient(req.params.uri); // test: source, ssrfSink
next();
}
server.get('/hello2/:name', restify.plugins.conditionalHandler([ // test: setup
{ version: ['2.0.0', '2.1.0', '2.2.0'], handler: sendV2 }
]));
server.get('/version/test', restify.plugins.conditionalHandler([ //test: setup
{
version: ['2.0.0', '2.1.0', '2.2.0'],
handler: function(req, res, next) { // test: candidateHandler
res.send(200, {
requestedVersion: req.version(),
matchedVersion: req.matchedVersion()
});
return next();
}
}
]));
server.on('InternalServer', function(req, res, err, callback) { // test: setup, handler
return callback();
});
server.on('restifyError', function(req, res, err, callback) { // test: setup, handler
return callback();
});
server.on('after', function(req, res, route, error) { // test: setup, handler
});
server.on('pre', function(req, res) { // test: setup, handler
});
server.on('routed', function(req, res, route) { // test: setup, handler
res.header("Content-Type", "text/plain")
res.send(req.params.foo) // test: source, !xssSink, !xss
});
server.on('uncaughtException', function(req, res, route, err) { // test: setup, handler
res.header("Content-Type", "text/html")
res.send(req.params.foo) // test: source, xssSink, xss
});
server.listen(8080, function() {
console.log('%s listening at %s', server.name, server.url);
});

View File

@@ -0,0 +1,105 @@
passingPositiveTests
| PASSED | candidateHandler | src/index.js:150:35:150:59 | // test ... Handler |
| PASSED | candidateHandler | src/index.js:173:42:173:66 | // test ... Handler |
| PASSED | corsMiconfigurationSink | src/index.js:153:41:153:72 | // test ... ionSink |
| PASSED | handler | src/index.js:32:39:32:54 | // test: handler |
| PASSED | handler | src/index.js:41:39:41:54 | // test: handler |
| PASSED | handler | src/index.js:44:35:44:50 | // test: handler |
| PASSED | handler | src/index.js:47:36:47:51 | // test: handler |
| PASSED | handler | src/index.js:50:36:50:51 | // test: handler |
| PASSED | handler | src/index.js:53:36:53:51 | // test: handler |
| PASSED | handler | src/index.js:56:36:56:51 | // test: handler |
| PASSED | handler | src/index.js:59:36:59:51 | // test: handler |
| PASSED | handler | src/index.js:62:36:62:51 | // test: handler |
| PASSED | handler | src/index.js:71:36:71:51 | // test: handler |
| PASSED | handler | src/index.js:93:44:93:66 | // test ... handler |
| PASSED | handler | src/index.js:99:30:99:45 | // test: handler |
| PASSED | handler | src/index.js:103:30:103:45 | // test: handler |
| PASSED | handler | src/index.js:111:31:111:46 | // test: handler |
| PASSED | handler | src/index.js:115:30:115:45 | // test: handler |
| PASSED | handler | src/index.js:121:32:121:47 | // test: handler |
| PASSED | handler | src/index.js:128:33:128:48 | // test: handler |
| PASSED | handler | src/index.js:142:33:142:48 | // test: handler |
| PASSED | handler | src/index.js:183:65:183:87 | // test ... handler |
| PASSED | handler | src/index.js:187:63:187:85 | // test ... handler |
| PASSED | handler | src/index.js:190:55:190:77 | // test ... handler |
| PASSED | handler | src/index.js:192:39:192:61 | // test ... handler |
| PASSED | handler | src/index.js:194:49:194:71 | // test ... handler |
| PASSED | handler | src/index.js:198:65:198:87 | // test ... handler |
| PASSED | redirectSink | src/index.js:78:32:78:60 | // test ... ectSink |
| PASSED | redirectSink | src/index.js:87:45:87:73 | // test ... ectSink |
| PASSED | redirectSink | src/index.js:88:40:88:68 | // test ... ectSink |
| PASSED | setup | src/index.js:66:21:66:34 | // test: setup |
| PASSED | setup | src/index.js:67:31:67:44 | // test: setup |
| PASSED | setup | src/index.js:68:33:68:46 | // test: setup |
| PASSED | setup | src/index.js:91:38:91:51 | // test: setup |
| PASSED | setup | src/index.js:92:39:92:52 | // test: setup |
| PASSED | setup | src/index.js:93:44:93:66 | // test ... handler |
| PASSED | setup | src/index.js:98:20:98:33 | // test: setup |
| PASSED | setup | src/index.js:110:21:110:34 | // test: setup |
| PASSED | setup | src/index.js:126:29:126:42 | // test: setup |
| PASSED | setup | src/index.js:147:31:147:44 | // test: setup |
| PASSED | setup | src/index.js:166:66:166:79 | // test: setup |
| PASSED | setup | src/index.js:170:66:170:78 | //test: setup |
| PASSED | setup | src/index.js:183:65:183:87 | // test ... handler |
| PASSED | setup | src/index.js:187:63:187:85 | // test ... handler |
| PASSED | setup | src/index.js:190:55:190:77 | // test ... handler |
| PASSED | setup | src/index.js:192:39:192:61 | // test ... handler |
| PASSED | setup | src/index.js:194:49:194:71 | // test ... handler |
| PASSED | setup | src/index.js:198:65:198:87 | // test ... handler |
| PASSED | source | src/index.js:11:76:11:130 | // test ... k, !xss |
| PASSED | source | src/index.js:24:76:24:128 | // test ... nk, xss |
| PASSED | source | src/index.js:72:41:72:80 | // test ... reSink |
| PASSED | source | src/index.js:73:44:73:82 | // test ... ureSink |
| PASSED | source | src/index.js:74:40:74:78 | // test ... ureSink |
| PASSED | source | src/index.js:75:39:75:77 | // test ... ureSink |
| PASSED | source | src/index.js:78:32:78:60 | // test ... ectSink |
| PASSED | source | src/index.js:87:45:87:73 | // test ... ectSink |
| PASSED | source | src/index.js:88:40:88:68 | // test ... ectSink |
| PASSED | source | src/index.js:100:37:100:51 | // test: source |
| PASSED | source | src/index.js:123:40:123:92 | // test ... nk, xss |
| PASSED | source | src/index.js:129:31:129:45 | // test: source |
| PASSED | source | src/index.js:144:43:144:96 | // test ... k, !xss |
| PASSED | source | src/index.js:158:41:158:93 | // test ... nk, xss |
| PASSED | source | src/index.js:160:26:160:50 | // test ... srfSink |
| PASSED | source | src/index.js:162:45:162:69 | // test ... srfSink |
| PASSED | source | src/index.js:196:28:196:58 | // test ... k, !xss |
| PASSED | source | src/index.js:200:28:200:56 | // test ... nk, xss |
| PASSED | ssrfSink | src/index.js:160:26:160:50 | // test ... srfSink |
| PASSED | ssrfSink | src/index.js:162:45:162:69 | // test ... srfSink |
| PASSED | stackTraceExposureSink | src/index.js:9:66:9:96 | // test ... ureSink |
| PASSED | stackTraceExposureSink | src/index.js:11:76:11:130 | // test ... k, !xss |
| PASSED | stackTraceExposureSink | src/index.js:22:66:22:105 | // test ... xssSink |
| PASSED | stackTraceExposureSink | src/index.js:24:76:24:128 | // test ... nk, xss |
| PASSED | stackTraceExposureSink | src/index.js:72:41:72:80 | // test ... reSink |
| PASSED | stackTraceExposureSink | src/index.js:73:44:73:82 | // test ... ureSink |
| PASSED | stackTraceExposureSink | src/index.js:74:40:74:78 | // test ... ureSink |
| PASSED | stackTraceExposureSink | src/index.js:75:39:75:77 | // test ... ureSink |
| PASSED | stackTraceExposureSink | src/index.js:105:29:105:73 | // test ... nk, xss |
| PASSED | stackTraceExposureSink | src/index.js:116:29:116:59 | // test ... ureSink |
| PASSED | stackTraceExposureSink | src/index.js:123:40:123:92 | // test ... nk, xss |
| PASSED | stackTraceExposureSink | src/index.js:134:20:134:64 | // test ... nk, xss |
| PASSED | stackTraceExposureSink | src/index.js:144:43:144:96 | // test ... k, !xss |
| PASSED | stackTraceExposureSink | src/index.js:158:41:158:93 | // test ... nk, xss |
| PASSED | xss | src/index.js:24:76:24:128 | // test ... nk, xss |
| PASSED | xss | src/index.js:105:29:105:73 | // test ... nk, xss |
| PASSED | xss | src/index.js:123:40:123:92 | // test ... nk, xss |
| PASSED | xss | src/index.js:134:20:134:64 | // test ... nk, xss |
| PASSED | xss | src/index.js:158:41:158:93 | // test ... nk, xss |
| PASSED | xss | src/index.js:200:28:200:56 | // test ... nk, xss |
| PASSED | xssSink | src/index.js:22:66:22:105 | // test ... xssSink |
| PASSED | xssSink | src/index.js:24:76:24:128 | // test ... nk, xss |
| PASSED | xssSink | src/index.js:105:29:105:73 | // test ... nk, xss |
| PASSED | xssSink | src/index.js:123:40:123:92 | // test ... nk, xss |
| PASSED | xssSink | src/index.js:134:20:134:64 | // test ... nk, xss |
| PASSED | xssSink | src/index.js:144:43:144:96 | // test ... k, !xss |
| PASSED | xssSink | src/index.js:158:41:158:93 | // test ... nk, xss |
| PASSED | xssSink | src/index.js:200:28:200:56 | // test ... nk, xss |
failingPositiveTests
passingNegativeTests
| PASSED | !xss | src/index.js:11:76:11:130 | // test ... k, !xss |
| PASSED | !xss | src/index.js:144:43:144:96 | // test ... k, !xss |
| PASSED | !xss | src/index.js:196:28:196:58 | // test ... k, !xss |
| PASSED | !xssSink | src/index.js:11:76:11:130 | // test ... k, !xss |
| PASSED | !xssSink | src/index.js:196:28:196:58 | // test ... k, !xss |
failingNegativeTests

View File

@@ -0,0 +1,194 @@
import javascript
import semmle.javascript.security.dataflow.CleartextStorageCustomizations
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsCustomizations
import semmle.javascript.security.dataflow.StackTraceExposureCustomizations
import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
import semmle.javascript.security.dataflow.RequestForgeryCustomizations
import semmle.javascript.security.dataflow.ReflectedXssCustomizations
import semmle.javascript.security.dataflow.ReflectedXssQuery as XssConfig
import semmle.javascript.heuristics.AdditionalRouteHandlers
class InlineTest extends LineComment {
string tests;
InlineTest() { tests = this.getText().regexpCapture("\\s*test:(.*)", 1) }
string getPositiveTest() {
result = tests.trim().splitAt(",").trim() and not result.matches("!%")
}
string getNegativeTest() { result = tests.trim().splitAt(",").trim() and result.matches("!%") }
predicate hasPositiveTest(string test) { test = this.getPositiveTest() }
predicate hasNegativeTest(string test) { test = this.getNegativeTest() }
predicate inNode(DataFlow::Node n) {
this.getLocation().getFile() = n.getFile() and
this.getLocation().getStartLine() = n.getStartLine()
}
}
query predicate passingPositiveTests(string res, string expectation, InlineTest t) {
res = "PASSED" and
t.hasPositiveTest(expectation) and
(
expectation = "source" and
exists(RemoteFlowSource n | t.inNode(n))
or
expectation = "setup" and
exists(Http::RouteSetup n | t.inNode(n))
or
expectation = "handler" and
exists(Http::RouteHandler n | t.inNode(n))
or
expectation = "candidateHandler" and
exists(Http::RouteHandlerCandidate n | t.inNode(n))
or
expectation = "xssSink" and
exists(ReflectedXss::Sink n | t.inNode(n))
or
expectation = "xss" and
exists(XssConfig::Configuration cfg, DataFlow::Node sink |
cfg.hasFlow(_, sink) and t.inNode(sink)
)
or
expectation = "cleartextStorageSink" and
exists(CleartextStorage::Sink n | t.inNode(n))
or
expectation = "corsMiconfigurationSink" and
exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
or
expectation = "stackTraceExposureSink" and
exists(StackTraceExposure::Sink n | t.inNode(n))
or
expectation = "redirectSink" and
exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
or
expectation = "ssrfSink" and
exists(RequestForgery::Sink n | t.inNode(n))
)
}
query predicate failingPositiveTests(string res, string expectation, InlineTest t) {
res = "FAILED" and
t.hasPositiveTest(expectation) and
(
expectation = "source" and
not exists(RemoteFlowSource n | t.inNode(n))
or
expectation = "setup" and
not exists(Http::RouteSetup n | t.inNode(n))
or
expectation = "handler" and
not exists(Http::RouteHandler n | t.inNode(n))
or
expectation = "candidateHandler" and
not exists(Http::RouteHandlerCandidate n | t.inNode(n))
or
expectation = "xssSink" and
not exists(ReflectedXss::Sink n | t.inNode(n))
or
expectation = "xss" and
not exists(XssConfig::Configuration cfg, DataFlow::Node sink |
cfg.hasFlow(_, sink) and t.inNode(sink)
)
or
expectation = "cleartextStorageSink" and
not exists(CleartextStorage::Sink n | t.inNode(n))
or
expectation = "corsMiconfigurationSink" and
not exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
or
expectation = "stackTraceExposureSink" and
not exists(StackTraceExposure::Sink n | t.inNode(n))
or
expectation = "redirectSink" and
not exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
or
expectation = "ssrfSink" and
not exists(RequestForgery::Sink n | t.inNode(n))
)
}
query predicate passingNegativeTests(string res, string expectation, InlineTest t) {
res = "PASSED" and
t.hasNegativeTest(expectation) and
(
expectation = "!source" and
not exists(RemoteFlowSource n | t.inNode(n))
or
expectation = "!setup" and
not exists(Http::RouteSetup n | t.inNode(n))
or
expectation = "!handler" and
not exists(Http::RouteHandler n | t.inNode(n))
or
expectation = "!candidateHandler" and
not exists(Http::RouteHandlerCandidate n | t.inNode(n))
or
expectation = "!xssSink" and
not exists(ReflectedXss::Sink n | t.inNode(n))
or
expectation = "!xss" and
not exists(XssConfig::Configuration cfg, DataFlow::Node sink |
cfg.hasFlow(_, sink) and t.inNode(sink)
)
or
expectation = "!cleartextStorageSink" and
not exists(CleartextStorage::Sink n | t.inNode(n))
or
expectation = "!corsMiconfigurationSink" and
not exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
or
expectation = "!stackTraceExposureSink" and
not exists(StackTraceExposure::Sink n | t.inNode(n))
or
expectation = "!redirectSink" and
not exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
or
expectation = "!ssrfSink" and
not exists(RequestForgery::Sink n | t.inNode(n))
)
}
query predicate failingNegativeTests(string res, string expectation, InlineTest t) {
res = "FAILED" and
t.hasNegativeTest(expectation) and
(
expectation = "!source" and
exists(RemoteFlowSource n | t.inNode(n))
or
expectation = "!setup" and
exists(Http::RouteSetup n | t.inNode(n))
or
expectation = "!handler" and
exists(Http::RouteHandler n | t.inNode(n))
or
expectation = "!candidateHandler" and
exists(Http::RouteHandlerCandidate n | t.inNode(n))
or
expectation = "!xssSink" and
exists(ReflectedXss::Sink n | t.inNode(n))
or
expectation = "!xss" and
exists(XssConfig::Configuration cfg, DataFlow::Node sink |
cfg.hasFlow(_, sink) and t.inNode(sink)
)
or
expectation = "!cleartextStorageSink" and
exists(CleartextStorage::Sink n | t.inNode(n))
or
expectation = "!corsMiconfigurationSink" and
exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
or
expectation = "!stackTraceExposureSink" and
exists(StackTraceExposure::Sink n | t.inNode(n))
or
expectation = "!redirectSink" and
exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
or
expectation = "!ssrfSink" and
exists(RequestForgery::Sink n | t.inNode(n))
)
}

View File

@@ -0,0 +1,17 @@
'use strict'
const routes = require('@npm/spife/routing')
module.exports = routes`
GET / homepage
GET /test1 test1
GET /test1 test2
GET /test4 test4
GET /test5 test5
GET /test6 test6
GET /raw1 raw1
GET /raw2 raw2
POST /body parseBody
GET /redirect/:redirect_url redirect
POST /packages/new createPackage
`(require('../views'))

View File

@@ -0,0 +1,46 @@
'use strict'
const { Environment, FileSystemLoader } = require('nunjucks')
const Loader = require('@npm/spife/templates/loader')
const path = require('path')
const isDev = !new Set(['prod', 'production', 'stag', 'staging']).has(
process.env.NODE_ENV
)
const templateDirs = [path.join(__dirname, '..', 'templates')]
const nunjucksEnv = new Environment(new FileSystemLoader(templateDirs))
const nunjucksLoader = new Loader({
dirs: templateDirs,
load (resolved) {
const template = nunjucksEnv.getTemplate(resolved.path, true)
return context => {
return template.render(context)
}
}
})
module.exports = {
DEBUG: process.env.DEBUG,
ENABLE_FORM_PARSING: false,
METRICS: process.env.METRICS,
MIDDLEWARE: [
'@npm/spife/middleware/debug',
['@npm/spife/middleware/template', [
nunjucksLoader
], [
// template context processors go here
]],
'@npm/spife/middleware/common',
'@npm/spife/middleware/logging',
'@npm/spife/middleware/metrics',
'@npm/spife/middleware/monitor',
'@npm/spife/middleware/hot-reload',
['@npm/spife/middleware/csrf', { secureCookie: !isDev }]
],
NAME: 'nunjucks-example',
NODE_ENV: process.env.NODE_ENV,
PORT: 8124,
ROUTER: './routes/index.js',
HOT: true
}

View File

@@ -0,0 +1,115 @@
'use strict'
const reply = require('@npm/spife/reply')
const validate = require('@npm/spife/decorators/validate')
const joi = require('@npm/spife/joi')
const concat = require('concat-stream')
const createPackageSchema = joi.object().keys({
contents: joi.string().max(200).required(),
destination: joi.any().valid([
joi.object({
name: joi.string().max(200).required(),
address: joi.string().max(200).required()
}),
joi.string().min(1)
])
})
module.exports = { homepage, parseBody, raw1, raw2, test1, test2, test3, test4, test5, test6, redirect, createPackage: validate.body(createPackageSchema, createPackage), }
function sink(obj) { console.log(obj) }
function createPackage(req, context) { // test: handler
const tainted = req.validatedBody.get('destination') // test: source
sink(taitned)
}
function homepage(req, context) { // test: handler
sink(req.cookie("test")) // test: source
sink(req.cookies().test) // test: source
sink(req.headers.test) // test: source
sink(req.rawHeaders[0]) // test: source
sink(req.raw.headers) // test: source
sink(req.url) // test: source
sink(req.urlObject.pathname) // test: source
sink(context.get('package')) // test: source
sink(context)
return reply.template('home', { target: req.query.name }) // test: source, templateInstantiation, stackTraceExposureSink
}
function raw1(req, context) { // test: handler
sink(req.query.name) // test: source
return reply(req.query.name, 200, { // test: source, xssSink, stackTraceExposureSink, xss
"content-type": "text/html",
"access-control-allow-origin": "*", // test: corsMiconfigurationSink
"access-control-allow-headers": "Content-Type, Authorization, Content-Length, X-Requested-With",
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
"access-control-allow-credentials": "true"
})
}
function redirect(req, context) { // test: handler
return reply.redirect(context.get('redirect_url')) // test: redirectSink, source, stackTraceExposureSink
}
function raw2(req, context) { // test: handler
return reply.cookie({ "test": req.query.name }, "test", req.query.name, { "httpOnly": false, "secure": false }) // test: source, cleartextStorageSink, stackTraceExposureSink
}
function test1(req, context) { // test: handler
switch (req.accept.type(['json', 'html', 'plain'])) {
case 'json':
return { "some": req.query.name } // test: source, stackTraceExposureSink
case 'html':
return reply.header('<p>' + req.query.name + '</p>', 'content-type', 'text/html') // test: source, xssSink, stackTraceExposureSink, xss
case 'plain':
return reply.header('<p>' + req.query.name + '</p>', { 'content-type': 'text/plain' }) // test: source, stackTraceExposureSink, !xssSink, !xss
}
return 'well, I guess you just want plaintext.'
}
function test2(req, context) { // test: handler
switch (req.accept.type(['json', 'html'])) {
case 'json':
return { "some": req.query.name } // test: source, stackTraceExposureSink
case 'html':
return reply.header('<p>' + req.query.name + '</p>', { 'content-type': 'text/plain' }) // test: source, stackTraceExposureSink, !xssSink, !xss
}
return 'well, I guess you just want plaintext.'
}
function test3(req, context) { // test: candidateHandler
return reply('<p>' + req.query.name + '</p>') // test: source, stackTraceExposureSink, !xssSink, !xss
}
function test4(req, context) { // test: handler
const body = req.body // test: source
const newPackument = body['package-json']
const message = `INFO: User invited to package ${newPackument._id} successfully.`
return reply(message, 200, { 'npm-notice': message }) // test: stackTraceExposureSink, !xssSink, !xss
}
function test5(req, context) { // test: handler
const body = req.body // test: source
const newPackument = body['package-json']
const message = `INFO: User invited to package ${newPackument._id} successfully.`
return reply(message, 200) // test: stackTraceExposureSink, !xssSink, !xss
}
function test6(req, context) { // test: handler
const body = req.body // test: source
const newPackument = body['package-json']
const message = `INFO: User invited to package ${newPackument._id} successfully.`
if (message.contains('foo')) {
return reply(message, 200, { 'npm-notice': message }) // test: stackTraceExposureSink, !xssSink, !xss
} else {
return reply(message, 200, { 'npm-notice': message, 'content-type': 'text/html' }) // test: stackTraceExposureSink, xssSink, xss
}
}
function parseBody(req, context) {
return req.body.then(data => { // test: source, stackTraceExposureSink
sink(data.name)
})
}

View File

@@ -0,0 +1,75 @@
passingPositiveTests
| PASSED | candidateHandler | lib/views/index.js:82:32:82:56 | // test ... Handler |
| PASSED | cleartextStorageSink | lib/views/index.js:57:115:57:175 | // test ... ureSink |
| PASSED | corsMiconfigurationSink | lib/views/index.js:45:41:45:72 | // test ... ionSink |
| PASSED | handler | lib/views/index.js:23:40:23:55 | // test: handler |
| PASSED | handler | lib/views/index.js:28:35:28:50 | // test: handler |
| PASSED | handler | lib/views/index.js:41:31:41:46 | // test: handler |
| PASSED | handler | lib/views/index.js:53:35:53:50 | // test: handler |
| PASSED | handler | lib/views/index.js:56:31:56:46 | // test: handler |
| PASSED | handler | lib/views/index.js:60:32:60:47 | // test: handler |
| PASSED | handler | lib/views/index.js:72:32:72:47 | // test: handler |
| PASSED | handler | lib/views/index.js:86:32:86:47 | // test: handler |
| PASSED | handler | lib/views/index.js:93:32:93:47 | // test: handler |
| PASSED | handler | lib/views/index.js:100:32:100:47 | // test: handler |
| PASSED | redirectSink | lib/views/index.js:54:54:54:106 | // test ... ureSink |
| PASSED | source | lib/views/index.js:24:56:24:70 | // test: source |
| PASSED | source | lib/views/index.js:29:28:29:42 | // test: source |
| PASSED | source | lib/views/index.js:30:28:30:42 | // test: source |
| PASSED | source | lib/views/index.js:31:26:31:40 | // test: source |
| PASSED | source | lib/views/index.js:32:27:32:41 | // test: source |
| PASSED | source | lib/views/index.js:33:25:33:39 | // test: source |
| PASSED | source | lib/views/index.js:34:17:34:31 | // test: source |
| PASSED | source | lib/views/index.js:35:32:35:46 | // test: source |
| PASSED | source | lib/views/index.js:36:32:36:46 | // test: source |
| PASSED | source | lib/views/index.js:38:61:38:122 | // test ... ureSink |
| PASSED | source | lib/views/index.js:42:24:42:38 | // test: source |
| PASSED | source | lib/views/index.js:43:39:43:91 | // test ... nk, xss |
| PASSED | source | lib/views/index.js:54:54:54:106 | // test ... ureSink |
| PASSED | source | lib/views/index.js:57:115:57:175 | // test ... ureSink |
| PASSED | source | lib/views/index.js:63:41:63:79 | // test ... ureSink |
| PASSED | source | lib/views/index.js:65:89:65:141 | // test ... nk, xss |
| PASSED | source | lib/views/index.js:67:94:67:148 | // test ... k, !xss |
| PASSED | source | lib/views/index.js:75:41:75:79 | // test ... ureSink |
| PASSED | source | lib/views/index.js:77:94:77:148 | // test ... k, !xss |
| PASSED | source | lib/views/index.js:83:49:83:103 | // test ... k, !xss |
| PASSED | source | lib/views/index.js:87:25:87:39 | // test: source |
| PASSED | source | lib/views/index.js:94:25:94:39 | // test: source |
| PASSED | source | lib/views/index.js:101:25:101:39 | // test: source |
| PASSED | source | lib/views/index.js:112:34:112:72 | // test ... ureSink |
| PASSED | stackTraceExposureSink | lib/views/index.js:38:61:38:122 | // test ... ureSink |
| PASSED | stackTraceExposureSink | lib/views/index.js:43:39:43:91 | // test ... nk, xss |
| PASSED | stackTraceExposureSink | lib/views/index.js:54:54:54:106 | // test ... ureSink |
| PASSED | stackTraceExposureSink | lib/views/index.js:57:115:57:175 | // test ... ureSink |
| PASSED | stackTraceExposureSink | lib/views/index.js:63:41:63:79 | // test ... ureSink |
| PASSED | stackTraceExposureSink | lib/views/index.js:65:89:65:141 | // test ... nk, xss |
| PASSED | stackTraceExposureSink | lib/views/index.js:67:94:67:148 | // test ... k, !xss |
| PASSED | stackTraceExposureSink | lib/views/index.js:75:41:75:79 | // test ... ureSink |
| PASSED | stackTraceExposureSink | lib/views/index.js:77:94:77:148 | // test ... k, !xss |
| PASSED | stackTraceExposureSink | lib/views/index.js:83:49:83:103 | // test ... k, !xss |
| PASSED | stackTraceExposureSink | lib/views/index.js:90:57:90:103 | // test ... k, !xss |
| PASSED | stackTraceExposureSink | lib/views/index.js:97:30:97:76 | // test ... k, !xss |
| PASSED | stackTraceExposureSink | lib/views/index.js:105:59:105:105 | // test ... k, !xss |
| PASSED | stackTraceExposureSink | lib/views/index.js:107:88:107:132 | // test ... nk, xss |
| PASSED | stackTraceExposureSink | lib/views/index.js:112:34:112:72 | // test ... ureSink |
| PASSED | xss | lib/views/index.js:43:39:43:91 | // test ... nk, xss |
| PASSED | xss | lib/views/index.js:65:89:65:141 | // test ... nk, xss |
| PASSED | xss | lib/views/index.js:107:88:107:132 | // test ... nk, xss |
| PASSED | xssSink | lib/views/index.js:43:39:43:91 | // test ... nk, xss |
| PASSED | xssSink | lib/views/index.js:65:89:65:141 | // test ... nk, xss |
| PASSED | xssSink | lib/views/index.js:107:88:107:132 | // test ... nk, xss |
failingPositiveTests
passingNegativeTests
| PASSED | !xss | lib/views/index.js:67:94:67:148 | // test ... k, !xss |
| PASSED | !xss | lib/views/index.js:77:94:77:148 | // test ... k, !xss |
| PASSED | !xss | lib/views/index.js:83:49:83:103 | // test ... k, !xss |
| PASSED | !xss | lib/views/index.js:97:30:97:76 | // test ... k, !xss |
| PASSED | !xssSink | lib/views/index.js:67:94:67:148 | // test ... k, !xss |
| PASSED | !xssSink | lib/views/index.js:77:94:77:148 | // test ... k, !xss |
| PASSED | !xssSink | lib/views/index.js:83:49:83:103 | // test ... k, !xss |
| PASSED | !xssSink | lib/views/index.js:97:30:97:76 | // test ... k, !xss |
failingNegativeTests
| FAILED | !xss | lib/views/index.js:90:57:90:103 | // test ... k, !xss |
| FAILED | !xss | lib/views/index.js:105:59:105:105 | // test ... k, !xss |
| FAILED | !xssSink | lib/views/index.js:90:57:90:103 | // test ... k, !xss |
| FAILED | !xssSink | lib/views/index.js:105:59:105:105 | // test ... k, !xss |

View File

@@ -0,0 +1,194 @@
import javascript
import semmle.javascript.security.dataflow.CleartextStorageCustomizations
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsCustomizations
import semmle.javascript.security.dataflow.StackTraceExposureCustomizations
import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
import semmle.javascript.security.dataflow.RequestForgeryCustomizations
import semmle.javascript.security.dataflow.ReflectedXssCustomizations
import semmle.javascript.security.dataflow.ReflectedXssQuery as XssConfig
import semmle.javascript.heuristics.AdditionalRouteHandlers
class InlineTest extends LineComment {
string tests;
InlineTest() { tests = this.getText().regexpCapture("\\s*test:(.*)", 1) }
string getPositiveTest() {
result = tests.trim().splitAt(",").trim() and not result.matches("!%")
}
string getNegativeTest() { result = tests.trim().splitAt(",").trim() and result.matches("!%") }
predicate hasPositiveTest(string test) { test = this.getPositiveTest() }
predicate hasNegativeTest(string test) { test = this.getNegativeTest() }
predicate inNode(DataFlow::Node n) {
this.getLocation().getFile() = n.getFile() and
this.getLocation().getStartLine() = n.getStartLine()
}
}
query predicate passingPositiveTests(string res, string expectation, InlineTest t) {
res = "PASSED" and
t.hasPositiveTest(expectation) and
(
expectation = "source" and
exists(RemoteFlowSource n | t.inNode(n))
or
expectation = "setup" and
exists(Http::RouteSetup n | t.inNode(n))
or
expectation = "handler" and
exists(Http::RouteHandler n | t.inNode(n))
or
expectation = "candidateHandler" and
exists(Http::RouteHandlerCandidate n | t.inNode(n))
or
expectation = "xssSink" and
exists(ReflectedXss::Sink n | t.inNode(n))
or
expectation = "xss" and
exists(XssConfig::Configuration cfg, DataFlow::Node sink |
cfg.hasFlow(_, sink) and t.inNode(sink)
)
or
expectation = "cleartextStorageSink" and
exists(CleartextStorage::Sink n | t.inNode(n))
or
expectation = "corsMiconfigurationSink" and
exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
or
expectation = "stackTraceExposureSink" and
exists(StackTraceExposure::Sink n | t.inNode(n))
or
expectation = "redirectSink" and
exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
or
expectation = "ssrfSink" and
exists(RequestForgery::Sink n | t.inNode(n))
)
}
query predicate failingPositiveTests(string res, string expectation, InlineTest t) {
res = "FAILED" and
t.hasPositiveTest(expectation) and
(
expectation = "source" and
not exists(RemoteFlowSource n | t.inNode(n))
or
expectation = "setup" and
not exists(Http::RouteSetup n | t.inNode(n))
or
expectation = "handler" and
not exists(Http::RouteHandler n | t.inNode(n))
or
expectation = "candidateHandler" and
not exists(Http::RouteHandlerCandidate n | t.inNode(n))
or
expectation = "xssSink" and
not exists(ReflectedXss::Sink n | t.inNode(n))
or
expectation = "xss" and
not exists(XssConfig::Configuration cfg, DataFlow::Node sink |
cfg.hasFlow(_, sink) and t.inNode(sink)
)
or
expectation = "cleartextStorageSink" and
not exists(CleartextStorage::Sink n | t.inNode(n))
or
expectation = "corsMiconfigurationSink" and
not exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
or
expectation = "stackTraceExposureSink" and
not exists(StackTraceExposure::Sink n | t.inNode(n))
or
expectation = "redirectSink" and
not exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
or
expectation = "ssrfSink" and
not exists(RequestForgery::Sink n | t.inNode(n))
)
}
query predicate passingNegativeTests(string res, string expectation, InlineTest t) {
res = "PASSED" and
t.hasNegativeTest(expectation) and
(
expectation = "!source" and
not exists(RemoteFlowSource n | t.inNode(n))
or
expectation = "!setup" and
not exists(Http::RouteSetup n | t.inNode(n))
or
expectation = "!handler" and
not exists(Http::RouteHandler n | t.inNode(n))
or
expectation = "!candidateHandler" and
not exists(Http::RouteHandlerCandidate n | t.inNode(n))
or
expectation = "!xssSink" and
not exists(ReflectedXss::Sink n | t.inNode(n))
or
expectation = "!xss" and
not exists(XssConfig::Configuration cfg, DataFlow::Node sink |
cfg.hasFlow(_, sink) and t.inNode(sink)
)
or
expectation = "!cleartextStorageSink" and
not exists(CleartextStorage::Sink n | t.inNode(n))
or
expectation = "!corsMiconfigurationSink" and
not exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
or
expectation = "!stackTraceExposureSink" and
not exists(StackTraceExposure::Sink n | t.inNode(n))
or
expectation = "!redirectSink" and
not exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
or
expectation = "!ssrfSink" and
not exists(RequestForgery::Sink n | t.inNode(n))
)
}
query predicate failingNegativeTests(string res, string expectation, InlineTest t) {
res = "FAILED" and
t.hasNegativeTest(expectation) and
(
expectation = "!source" and
exists(RemoteFlowSource n | t.inNode(n))
or
expectation = "!setup" and
exists(Http::RouteSetup n | t.inNode(n))
or
expectation = "!handler" and
exists(Http::RouteHandler n | t.inNode(n))
or
expectation = "!candidateHandler" and
exists(Http::RouteHandlerCandidate n | t.inNode(n))
or
expectation = "!xssSink" and
exists(ReflectedXss::Sink n | t.inNode(n))
or
expectation = "!xss" and
exists(XssConfig::Configuration cfg, DataFlow::Node sink |
cfg.hasFlow(_, sink) and t.inNode(sink)
)
or
expectation = "!cleartextStorageSink" and
exists(CleartextStorage::Sink n | t.inNode(n))
or
expectation = "!corsMiconfigurationSink" and
exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
or
expectation = "!stackTraceExposureSink" and
exists(StackTraceExposure::Sink n | t.inNode(n))
or
expectation = "!redirectSink" and
exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
or
expectation = "!ssrfSink" and
exists(RequestForgery::Sink n | t.inNode(n))
)
}

View File

@@ -11,10 +11,16 @@ test_RequestInputAccess
| src/test.js:19:5:19:26 | request ... ('bar') | header | src/test.js:12:19:22:1 | functio ... okie;\\n} |
| src/test.js:20:5:20:25 | request ... ('baz') | header | src/test.js:12:19:22:1 | functio ... okie;\\n} |
test_RouteHandler_getAResponseHeader
| src/test.js:6:1:6:21 | functio ... er1(){} | content-type | src/test.js:6:1:6:21 | functio ... er1(){} |
| src/test.js:9:19:11:1 | functio ... ition\\n} | content-type | src/test.js:9:19:11:1 | functio ... ition\\n} |
| src/test.js:9:19:11:1 | functio ... ition\\n} | header1 | src/test.js:10:5:10:34 | respons ... 1', '') |
| src/test.js:12:19:22:1 | functio ... okie;\\n} | content-type | src/test.js:12:19:22:1 | functio ... okie;\\n} |
| src/test.js:12:19:22:1 | functio ... okie;\\n} | header2 | src/test.js:13:5:13:37 | respons ... 2', '') |
test_HeaderDefinition_defines
| src/test.js:6:1:6:21 | functio ... er1(){} | content-type | application/json |
| src/test.js:9:19:11:1 | functio ... ition\\n} | content-type | application/json |
| src/test.js:10:5:10:34 | respons ... 1', '') | header1 | |
| src/test.js:12:19:22:1 | functio ... okie;\\n} | content-type | application/json |
| src/test.js:13:5:13:37 | respons ... 2', '') | header2 | |
test_ResponseExpr
| src/test.js:9:46:9:53 | response | src/test.js:9:19:11:1 | functio ... ition\\n} |
@@ -24,14 +30,20 @@ test_ResponseExpr
| src/test.js:12:46:12:53 | response | src/test.js:12:19:22:1 | functio ... okie;\\n} |
| src/test.js:13:5:13:12 | response | src/test.js:12:19:22:1 | functio ... okie;\\n} |
test_HeaderDefinition
| src/test.js:6:1:6:21 | functio ... er1(){} | src/test.js:6:1:6:21 | functio ... er1(){} |
| src/test.js:9:19:11:1 | functio ... ition\\n} | src/test.js:9:19:11:1 | functio ... ition\\n} |
| src/test.js:10:5:10:34 | respons ... 1', '') | src/test.js:9:19:11:1 | functio ... ition\\n} |
| src/test.js:12:19:22:1 | functio ... okie;\\n} | src/test.js:12:19:22:1 | functio ... okie;\\n} |
| src/test.js:13:5:13:37 | respons ... 2', '') | src/test.js:12:19:22:1 | functio ... okie;\\n} |
test_RouteSetup_getServer
| src/test.js:7:1:7:26 | server2 ... ndler1) | src/test.js:4:15:4:36 | restify ... erver() |
| src/test.js:9:1:11:2 | server2 ... tion\\n}) | src/test.js:4:15:4:36 | restify ... erver() |
| src/test.js:12:1:22:2 | server2 ... kie;\\n}) | src/test.js:4:15:4:36 | restify ... erver() |
test_HeaderDefinition_getAHeaderName
| src/test.js:6:1:6:21 | functio ... er1(){} | content-type |
| src/test.js:9:19:11:1 | functio ... ition\\n} | content-type |
| src/test.js:10:5:10:34 | respons ... 1', '') | header1 |
| src/test.js:12:19:22:1 | functio ... okie;\\n} | content-type |
| src/test.js:13:5:13:37 | respons ... 2', '') | header2 |
test_ServerDefinition
| src/test.js:1:15:1:47 | require ... erver() |