Skip to content

Commit 1dcebb1

Browse files
committed
✨ automated testing
1 parent e18ff79 commit 1dcebb1

File tree

10 files changed

+600
-42
lines changed

10 files changed

+600
-42
lines changed

security/server/index.cjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const express = require('express');
2+
const app = express();
3+
const xssApp = express();
4+
const port = 3333;
5+
const xssPort = 3334;
6+
const cors = require('cors');
7+
8+
xssApp.use(cors());
9+
10+
xssApp.get('/jqueryxss', (req, res) => {
11+
// res.set('content-type', 'text/javascript');
12+
res.writeHead(200, { 'Content-Type': 'text/javascript' });
13+
res.write(`triggerCVE('2015-9251');`)
14+
res.end()
15+
});
16+
17+
xssApp.listen(xssPort, () => {
18+
console.log(`listening on port ${xssPort} (xss endpoint)`);
19+
});
20+
21+
app.use(express.static('../site'));
22+
23+
app.listen(port, () => {
24+
console.log(`listening on port ${port} (test site)`);
25+
});
26+

security/server/index.js

Lines changed: 0 additions & 17 deletions
This file was deleted.

security/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"scripts": {
4-
"serve": "node index.js"
4+
"serve": "node index.cjs"
55
},
66
"dependencies": {
77
"cors": "^2.8.5",

security/site/cve-data.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const cveMap = new Map([
2+
['2011-4969', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2'] }],
3+
['2012-6708', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3'] }],
4+
['2015-9251', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3', '1.12.4', '2.2.4'] }],
5+
['2019-11358', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3', '1.12.4', '2.2.4'] }],
6+
['2020-7656', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3'] }],
7+
['2020-11022', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3', '1.12.4', '2.2.4'] }],
8+
['2020-11023', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3', '1.12.4', '2.2.4'] }],
9+
['2020-23064', { versions: ['2.2.4'] }],
10+
]);
11+
12+
const jQueryVersions = new Set(Array.from(cveMap, ([name, value]) => (value.versions)).flat());
13+
14+
function getPatchedVersion(version){
15+
const versionParts = version.split('.');
16+
return`${versionParts[0]}.${versionParts[1]}.${Number(versionParts[2]) + 1}-sec`;
17+
}
18+
19+
export { cveMap, getPatchedVersion, jQueryVersions };

security/site/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
77
<title>jQuery Security Acceptance Tests</title>
88
<link rel="stylesheet" href="styles.css" />
9-
<script src="main.js" defer></script>
9+
<script type="module" src="main.mjs"></script>
1010
</head>
1111
<body>
1212
<header>

security/site/main.js renamed to security/site/main.mjs

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { cveMap, getPatchedVersion, jQueryVersions } from './cve-data.mjs'
2+
3+
const dis = this;
4+
15
function log(txt){
26
console.log(txt);
37
}
@@ -27,17 +31,6 @@ window.alert = function(...args) {
2731
// windowAlert(...args);
2832
};
2933

30-
const cveMap = new Map([
31-
['2011-4969', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2'] }],
32-
['2012-6708', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3'] }],
33-
['2015-9251', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3', '1.12.4', '2.2.4'] }],
34-
['2019-11358', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3', '1.12.4', '2.2.4'] }],
35-
['2020-7656', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3'] }],
36-
['2020-11022', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3', '1.12.4', '2.2.4'] }],
37-
['2020-11023', { versions: ['1.2.6', '1.3.2', '1.4.4', '1.5.2', '1.6.4', '1.7.2', '1.8.3', '1.12.4', '2.2.4'] }],
38-
['2020-23064', { versions: ['2.2.4'] }],
39-
]);
40-
4134
const cveTemplate = `
4235
<div class="cve">
4336
<div class="cve__header">
@@ -61,20 +54,19 @@ for (const cve of cveMap) {
6154
const cveID = `CVE-${cve[0]}`;
6255
const t = document.createElement('template');
6356
t.innerHTML = cveTemplate;
57+
t.content.querySelector('.cve').id = cveID;
6458
t.content.querySelector('.cve__header').textContent = cveID;
6559
const b = t.content.querySelector('button');
6660
b.textContent = cveID;
6761
b.addEventListener('click', function (){
6862
log(`called ${cveID}`)
69-
window[cveID.replaceAll('-', '_')](cve);
63+
window.doNotPolluteTheGlobalNamespace[cveID.replaceAll('-', '_')](cve);
7064
setTimeout(() => updateCVE(cve), 100);
7165
});
7266
cveButtons.push(b);
7367
cveContainer.append(t.content);
7468
}
7569

76-
const jQueryVersions = new Set(Array.from(cveMap, ([name, value]) => (value.versions)).flat());
77-
7870
for (const v of jQueryVersions) {
7971
const o = document.createElement('option');
8072
o.textContent = v;
@@ -84,12 +76,24 @@ for (const v of jQueryVersions) {
8476
const VERSION = 'VERSION';
8577
const PATCHED = 'PATCHED';
8678

87-
const sessionVersion = sessionStorage.getItem(VERSION);
88-
const sessionPatched = sessionStorage.getItem(PATCHED);
79+
const qs = (new URL(document.location)).searchParams;
80+
const qsVersion = qs.get(VERSION);
81+
const qsPatched = qs.get(PATCHED);
82+
83+
if(qsVersion) {
84+
selVersion.value = qsVersion;
85+
chkPatched.checked = qsPatched === 'true';
86+
}
87+
else {
88+
89+
const sessionVersion = sessionStorage.getItem(VERSION);
90+
const sessionPatched = sessionStorage.getItem(PATCHED);
91+
92+
if(sessionVersion) {
93+
selVersion.value = sessionVersion;
94+
chkPatched.checked = sessionPatched === 'true';
95+
}
8996

90-
if(sessionVersion) {
91-
selVersion.value = sessionVersion;
92-
chkPatched.checked = sessionPatched === 'true';
9397
}
9498

9599
changeVersion();
@@ -120,8 +124,7 @@ function changeVersion() {
120124
document.querySelectorAll('.cve').forEach(e => e.classList.remove('hide'));
121125
};
122126

123-
const versionParts = version.split('.');
124-
const loadVersion = patched ? `${versionParts[0]}.${versionParts[1]}.${Number(versionParts[2]) + 1}-sec` : version;
127+
const loadVersion = patched ? getPatchedVersion(version) : version;
125128

126129
s.onerror = function() {
127130
if(typeof jQuery !== 'undefined') {
@@ -194,6 +197,7 @@ function CVE_2011_4969(cve){
194197
handleJQuerySyntaxError(e);
195198
}
196199

200+
history.replaceState(null, null, ' '); // clear out location.hash completely
197201

198202
}
199203

@@ -219,7 +223,7 @@ function CVE_2012_6708(cve) {
219223
}
220224

221225
function CVE_2015_9251(cve) {
222-
$.get("http://localhost:4000/jqueryxss", function( content ) {
226+
$.get("http://localhost:3334/jqueryxss", function( content ) {
223227
// since we are relying on an external resource for this test, guard against regression
224228
const expected = `triggerCVE('${cve[0]}');`;
225229
const expectedContentFound = content === expected;
@@ -228,7 +232,6 @@ function CVE_2015_9251(cve) {
228232
error('CVE-2015-9251 CANNOT BE VERIFIED!');
229233
}
230234
});
231-
// log('CVE-2015-9251 is not reproducible in 1.2.6, so ignore this test');
232235
}
233236

234237
function CVE_2019_11358(cve) {
@@ -266,3 +269,15 @@ function CVE_2020_23064(cve) {
266269
// this is a duplicate of CVE-2020-11023
267270
CVE_2020_11023(cve);
268271
}
272+
273+
window.triggerCVE = triggerCVE;
274+
window.doNotPolluteTheGlobalNamespace = {
275+
CVE_2011_4969,
276+
CVE_2012_6708,
277+
CVE_2015_9251,
278+
CVE_2019_11358,
279+
CVE_2020_7656,
280+
CVE_2020_11022,
281+
CVE_2020_11023,
282+
CVE_2020_23064,
283+
};

security/test/index.mjs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import os from 'node:os';
2+
import { execSync } from 'node:child_process';
3+
import { setTimeout } from 'node:timers/promises';
4+
import assert from 'node:assert';
5+
import { JSDOM } from 'jsdom';
6+
import { cveMap, getPatchedVersion, jQueryVersions } from '../site/cve-data.mjs'
7+
8+
let failedAssertions = 0;
9+
10+
console.log('\nrunning jQuery security tests...\n');
11+
12+
const platform = os.platform();
13+
console.log(`platform detected: ${platform}`);
14+
15+
const baseURL = 'http://127.0.0.1:3333/index.html';
16+
const timeout = 5 * 1000;
17+
18+
let cmd;
19+
20+
if(platform === 'darwin'){
21+
cmd = `"/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" --headless=old --virtual-time-budget=${timeout} --run-all-compositor-stages-before-draw --dump-dom `;
22+
}
23+
else {
24+
// presume *nix
25+
}
26+
27+
for (const v of jQueryVersions) {
28+
try {
29+
test(v);
30+
test(v, true);
31+
} catch (e) {
32+
if(e instanceof assert.AssertionError) {
33+
console.error(e);
34+
failedAssertions += 1;
35+
}
36+
else {
37+
throw e;
38+
}
39+
}
40+
}
41+
42+
43+
function test(version, patched) {
44+
45+
const effectiveVersion = patched ? getPatchedVersion(version) : version;
46+
47+
console.log(`
48+
--------------------------------------------------------------------------------
49+
validating jQuery v${effectiveVersion}
50+
--------------------------------------------------------------------------------
51+
`)
52+
53+
let p;
54+
55+
try {
56+
p = execSync(`${cmd}"${baseURL}?VERSION=${version}&PATCHED=${patched}"`);
57+
} catch (error) {
58+
console.error(e);
59+
}
60+
61+
const dom = new JSDOM(p.toString());
62+
63+
const d = dom.window.document;
64+
65+
assert.strictEqual(d.querySelector('#loaded-jQuery').textContent, effectiveVersion, `loaded jQuery v${effectiveVersion}`);
66+
67+
for (const cve of cveMap) {
68+
if(cve[1].versions.includes(version)) {
69+
const status = d.querySelector(`#CVE-${cve[0]} .cve__footer-status`).textContent;
70+
console.log(status);
71+
}
72+
}
73+
74+
}
75+
76+
console.log('\n...done\n');
77+
78+
if(failedAssertions) {
79+
process.exitCode = 1;
80+
}

0 commit comments

Comments
 (0)