Browse Source

reliably report web worker and inline script presence

pull/2/head
Raymond Hill 7 years ago
parent
commit
821e45751a
No known key found for this signature in database GPG Key ID: 25E1490B761470C2
  1. 4
      src/js/background.js
  2. 62
      src/js/contentscript-start.js
  3. 23
      src/js/contentscript.js
  4. 50
      src/js/messaging.js
  5. 69
      src/js/traffic.js

4
src/js/background.js

@ -110,7 +110,9 @@ return {
}, },
clearBrowserCacheCycle: 0, clearBrowserCacheCycle: 0,
cspNoWorkerSrc: undefined,
cspNoInlineScript: undefined,
cspNoWorker: undefined,
cspReportURI: 'about:blank',
updateAssetsEvery: 11 * oneDay + 1 * oneHour + 1 * oneMinute + 1 * oneSecond, updateAssetsEvery: 11 * oneDay + 1 * oneHour + 1 * oneMinute + 1 * oneSecond,
firstUpdateAfter: 11 * oneMinute, firstUpdateAfter: 11 * oneMinute,
nextUpdateAfter: 11 * oneHour, nextUpdateAfter: 11 * oneHour,

62
src/js/contentscript-start.js

@ -30,16 +30,68 @@
if ( typeof vAPI !== 'object' ) { return; } if ( typeof vAPI !== 'object' ) { return; }
window.addEventListener('securitypolicyviolation', function(ev) {
vAPI.reportedViolations = vAPI.reportedViolations || new Set();
var cspReportURI = 'about:blank';
var reportedViolations = vAPI.reportedViolations;
var handler = function(ev) {
if (
ev.isTrusted !== true ||
ev.originalPolicy.includes(cspReportURI) === false
) {
return false;
}
// Firefox and Chromium differs in how they fill the
// 'effectiveDirective' property. Need to normalize here.
var directive = ev.effectiveDirective;
if ( directive.startsWith('script-src') ) {
directive = 'script-src';
} else if ( directive.startsWith('worker-src') ) {
directive = 'worker-src';
} else if ( directive.startsWith('child-src') ) {
directive = 'worker-src';
} else {
return false;
}
var blockedURL;
try {
blockedURL = new URL(ev.blockedURI);
} catch(ex) {
}
blockedURL = blockedURL !== undefined ? blockedURL.href || '' : '';
// Avoid reporting same violations repeatedly.
var violationKey = (directive + ' ' + blockedURL).trim();
if ( reportedViolations.has(violationKey) ) {
return true;
}
reportedViolations.add(violationKey);
vAPI.messaging.send( vAPI.messaging.send(
'contentscript.js', 'contentscript.js',
{ {
what: 'securityPolicyViolation', what: 'securityPolicyViolation',
policy: ev.originalPolicy,
blockedURI: ev.blockedURI,
documentURI: ev.documentURI
directive: directive,
blockedURI: blockedURL,
documentURI: ev.documentURI,
blocked: ev.disposition === 'enforce'
} }
); );
});
return true;
};
document.addEventListener(
'securitypolicyviolation',
function(ev) {
if ( !handler(ev) ) { return; }
ev.stopPropagation();
ev.preventDefault();
},
true
);
})(); })();

23
src/js/contentscript.js

@ -407,14 +407,21 @@ var collapser = (function() {
// Mind "on..." attributes. // Mind "on..." attributes.
(function() { (function() {
vAPI.messaging.send('contentscript.js', {
what: 'contentScriptSummary',
locationURL: window.location.href,
inlineScript:
document.querySelector('script:not([src])') !== null ||
document.querySelector('a[href^="javascript:"]') !== null ||
document.querySelector('[onabort],[onblur],[oncancel],[oncanplay],[oncanplaythrough],[onchange],[onclick],[onclose],[oncontextmenu],[oncuechange],[ondblclick],[ondrag],[ondragend],[ondragenter],[ondragexit],[ondragleave],[ondragover],[ondragstart],[ondrop],[ondurationchange],[onemptied],[onended],[onerror],[onfocus],[oninput],[oninvalid],[onkeydown],[onkeypress],[onkeyup],[onload],[onloadeddata],[onloadedmetadata],[onloadstart],[onmousedown],[onmouseenter],[onmouseleave],[onmousemove],[onmouseout],[onmouseover],[onmouseup],[onwheel],[onpause],[onplay],[onplaying],[onprogress],[onratechange],[onreset],[onresize],[onscroll],[onseeked],[onseeking],[onselect],[onshow],[onstalled],[onsubmit],[onsuspend],[ontimeupdate],[ontoggle],[onvolumechange],[onwaiting],[onafterprint],[onbeforeprint],[onbeforeunload],[onhashchange],[onlanguagechange],[onmessage],[onoffline],[ononline],[onpagehide],[onpageshow],[onrejectionhandled],[onpopstate],[onstorage],[onunhandledrejection],[onunload],[oncopy],[oncut],[onpaste]') !== null
});
if (
vAPI.reportedViolations === undefined ||
vAPI.reportedViolations.has('script-src') === false
) {
if ( document.querySelector('script:not([src])') !== null ) {
vAPI.messaging.send('contentscript.js', {
what: 'securityPolicyViolation',
directive: 'script-src',
documentURI: window.location.href
});
if ( vAPI.reportedViolations ) {
vAPI.reportedViolations.add('script-src');
}
}
}
collapser.addMany(document.querySelectorAll('img')); collapser.addMany(document.querySelectorAll('img'));
collapser.addIFrames(document.querySelectorAll('iframe')); collapser.addIFrames(document.querySelectorAll('iframe'));

50
src/js/messaging.js

@ -391,32 +391,23 @@ var µm = µMatrix;
/******************************************************************************/ /******************************************************************************/
var contentScriptSummaryHandler = function(tabId, details) {
// TODO: Investigate "Error in response to tabs.executeScript: TypeError:
// Cannot read property 'locationURL' of null" (2013-11-12). When can this
// happens?
if ( !details || !details.locationURL ) { return; }
// scripts
if ( details.inlineScript !== true ) {
return;
}
// https://github.com/gorhill/httpswitchboard/issues/25
var pageStore = µm.pageStoreFromTabId(tabId);
var contentScriptSummaryHandler = function(tabId, pageStore, details) {
if ( pageStore === null ) { return; } if ( pageStore === null ) { return; }
var pageHostname = pageStore.pageHostname; var pageHostname = pageStore.pageHostname;
var µmuri = µm.URI.set(details.locationURL);
var µmuri = µm.URI.set(details.documentURI);
var frameURL = µmuri.normalizedURI(); var frameURL = µmuri.normalizedURI();
var frameHostname = µmuri.hostname;
var blocked = details.blocked;
if ( blocked === undefined ) {
blocked = µm.mustBlock(pageHostname, µmuri.hostname, 'script');
}
// https://github.com/gorhill/httpswitchboard/issues/333 // https://github.com/gorhill/httpswitchboard/issues/333
// Look-up here whether inline scripting is blocked for the frame. // Look-up here whether inline scripting is blocked for the frame.
var inlineScriptBlocked = µm.mustBlock(pageHostname, frameHostname, 'script');
var url = frameURL + '{inline_script}'; var url = frameURL + '{inline_script}';
pageStore.recordRequest('script', url, inlineScriptBlocked);
µm.logger.writeOne(tabId, 'net', pageHostname, url, 'script', inlineScriptBlocked);
pageStore.recordRequest('script', url, blocked);
µm.logger.writeOne(tabId, 'net', pageHostname, url, 'script', blocked);
// https://github.com/gorhill/uMatrix/issues/225 // https://github.com/gorhill/uMatrix/issues/225
// A good place to force an update of the page title, as at this point // A good place to force an update of the page title, as at this point
@ -544,16 +535,19 @@ var onMessage = function(request, sender, callback) {
break; break;
case 'securityPolicyViolation': case 'securityPolicyViolation':
if ( request.policy !== µm.cspNoWorkerSrc ) { break; }
var url = µm.URI.hostnameFromURI(request.blockedURI) !== '' ?
request.blockedURI :
request.documentURI;
if ( pageStore !== null ) {
pageStore.hasWebWorkers = true;
pageStore.recordRequest('script', url, true);
}
if ( tabContext !== null ) {
µm.logger.writeOne(tabId, 'net', rootHostname, url, 'worker', true);
if ( request.directive === 'worker-src' ) {
var url = µm.URI.hostnameFromURI(request.blockedURI) !== '' ?
request.blockedURI :
request.documentURI;
if ( pageStore !== null ) {
pageStore.hasWebWorkers = true;
pageStore.recordRequest('script', url, true);
}
if ( tabContext !== null ) {
µm.logger.writeOne(tabId, 'net', rootHostname, url, 'worker', request.blocked);
}
} else if ( request.directive === 'script-src' ) {
contentScriptSummaryHandler(tabId, pageStore, request);
} }
break; break;

69
src/js/traffic.js

@ -299,8 +299,17 @@ var onHeadersReceived = function(details) {
var tabContext = µm.tabContextManager.lookup(tabId); var tabContext = µm.tabContextManager.lookup(tabId);
if ( tabContext === null ) { return; } if ( tabContext === null ) { return; }
var csp = [];
var csp = [],
cspReport = [];
// If javascript is not allowed, say so through a `Content-Security-Policy`
// directive.
// We block only inline-script tags, all the external javascript will be
// blocked by our request handler.
if ( µm.cspNoInlineScript === undefined ) {
µm.cspNoInlineScript =
"script-src 'unsafe-eval' blob: *;report-uri " + µm.cspReportURI;
}
if ( if (
µm.mustAllow( µm.mustAllow(
tabContext.rootHostname, tabContext.rootHostname,
@ -308,39 +317,53 @@ var onHeadersReceived = function(details) {
'script' 'script'
) !== true ) !== true
) { ) {
csp.push("script-src 'unsafe-eval' blob: *");
csp.push(µm.cspNoInlineScript);
} else {
cspReport.push(µm.cspNoInlineScript);
} }
if ( µm.cspNoWorkerSrc === undefined ) {
µm.cspNoWorkerSrc = vAPI.webextFlavor.startsWith('Mozilla-') ?
"child-src 'none'; frame-src data: blob: *" :
"worker-src 'none'" ;
// TODO: Firefox will eventually support `worker-src`:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1231788
if ( µm.cspNoWorker === undefined ) {
µm.cspNoWorker = vAPI.webextFlavor.startsWith('Mozilla-') ?
"child-src 'none'; frame-src data: blob: *;report-uri " :
"worker-src 'none';report-uri " ;
µm.cspNoWorker += µm.cspReportURI;
} }
if ( µm.tMatrix.evaluateSwitchZ('no-workers', tabContext.rootHostname) ) { if ( µm.tMatrix.evaluateSwitchZ('no-workers', tabContext.rootHostname) ) {
csp.push(µm.cspNoWorkerSrc);
csp.push(µm.cspNoWorker);
} else {
cspReport.push(µm.cspNoWorker);
} }
if ( csp.length === 0 ) { return; }
// If javascript is not allowed, say so through a `Content-Security-Policy`
// directive.
// We block only inline-script tags, all the external javascript will be
// blocked by our request handler.
var headers = details.responseHeaders,
cspDirectives, i;
var cspDirectives = csp.join(','),
headers = details.responseHeaders,
if ( csp.length !== 0 ) {
cspDirectives = csp.join(',');
i = headerIndexFromName('content-security-policy', headers); i = headerIndexFromName('content-security-policy', headers);
// A CSP header is already present: just add our own directive as a
// separate disposition (i.e. use comma).
if ( i !== -1 ) {
headers[i].value += ',' + cspDirectives;
} else {
headers.push({ name: 'Content-Security-Policy', value: cspDirectives });
if ( i !== -1 ) {
headers[i].value += ',' + cspDirectives;
} else {
headers.push({ name: 'Content-Security-Policy', value: cspDirectives });
}
if ( requestType === 'doc' ) {
µm.logger.writeOne(tabId, 'net', '', cspDirectives, 'CSP', false);
}
} }
if ( requestType === 'doc' ) {
µm.logger.writeOne(tabId, 'net', '', csp, 'CSP', false);
if ( cspReport.length !== 0 ) {
cspDirectives = cspReport.join(',');
i = headerIndexFromName('content-security-policy-report-only', headers);
if ( i !== -1 ) {
headers[i].value += ',' + cspDirectives;
} else {
headers.push({
name: 'Content-Security-Policy-Report-Only',
value: cspDirectives
});
}
} }
return { responseHeaders: headers }; return { responseHeaders: headers };

Loading…
Cancel
Save