Skip to content

Commit

Permalink
Enforce Snow integration with CSP (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
weizman committed Jul 17, 2023
1 parent 93758f5 commit 3684e04
Show file tree
Hide file tree
Showing 27 changed files with 513 additions and 446 deletions.
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ to **non extension javascript with the same privileges as the web app**.

> _Read more about Snow and the motivation behind it [here](https://github.com/lavamoat/snow/wiki/Introducing-Snow)_
## 🚨 IMPORTANT UPDATE 🚨

Starting Version [1.6.0](https://github.com/LavaMoat/snow/pull/76) Snow officially doesn't support vulnerabilities that
can be protected against by disallowing `unsafe-inline` completely and by correctly using the `object-src` directive to not allow `self`.

To learn more why is that, see [section 3](#install).

## [Demo](https://lavamoat.github.io/snow/demo/#self-xss-challenge-msg) - The Snow Challenge! 🏆

<div align="center">
Expand Down Expand Up @@ -102,6 +109,15 @@ in order for it to play its role securely.
the modified version might contain flaws that attackers might use to cancel its effect (for further
explanation see [natives](https://github.com/lavamoat/snow/wiki/Introducing-Snow#natives) section).

3. **Most importantly, it's highly vulnerable without minimal help from CSP** - As of version 1.6.0 the project will
seize to attempt to defend against vulnerabilities that aren't possible to exploit when
(a) `unsafe-inline` isn't allowed and (b) `object-src` to `self` isn't allowed.
This is because (a) defending against string-JS attacks is basically an endless task and probably impossible, and
(b) `object`/`embed` elements behaviour is also too unpredictable while these elements shouldn't be even used in the
first place. Snow will do its best regardless of what CSP is applied - **use at your own risk!**
1. please learn more about this ☝️ at [#118](https://github.com/LavaMoat/snow/pull/118/)


`SNOW` API can also be required as part of a bundle instead of a script tag:

```
Expand All @@ -122,13 +138,11 @@ Until `snow` becomes a platform builtin API, we have to attempt to overcome seve

### Support

`snow` should support Chrome, Firefox, Safari and all other Chromium based browsers (Opera, Edge, Brave, etc).

Although, when running on Firefox please pay attention to [issue-59](https://github.com/LavaMoat/snow/issues/59).
`snow` supports Chrome, Firefox, Safari and all other Chromium based browsers (Opera, Edge, Brave, etc).

### Performance

Achieving an hermetic solution costs in performance. Injecting this script into some major
Achieving a hermetic solution costs in performance. Injecting this script into some major
websites went smoothly while with some others it caused them some performance issues.

### Security
Expand Down
2 changes: 2 additions & 0 deletions chrome.wdio.conf.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
global.BROWSER = 'CHROME';
exports.config = {
automationProtocol: 'devtools',
//
// ====================
// Runner Configuration
Expand Down Expand Up @@ -64,6 +65,7 @@ exports.config = {
'goog:chromeOptions': {
args: [
'--headless',
'-auto-open-devtools-for-tabs',
'disable-gpu',
'--enable-features=DocumentPictureInPictureAPI'
],
Expand Down
78 changes: 3 additions & 75 deletions demo/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none';">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>❄️</text></svg>">
<title> Snow </title>
<script src="../snow.js"></script>
Expand All @@ -12,6 +13,7 @@
background-color: #f2f2f2;
}
.msg {
display: none;
opacity:1;
padding: 30px;
background-color:#fcd8db;
Expand Down Expand Up @@ -98,9 +100,6 @@ <h4>Hall of Fame</h4>
<span>3. </span>
</div>
</div>
<script>
!location.href.includes('self-xss-challenge-msg') && (msg.style.display = 'none');
</script>
<div>
<div id="testdiv">
<div id="testdiv1"></div>
Expand Down Expand Up @@ -156,77 +155,6 @@ <h3><i>~ Can you bypass Snow?</i></h3>
Invented and developed by <a href="https://weizmangal.com/">Gal Weizman 👋🏻</a>
</blockquote>

<script>
function bypass(wins) {
(wins || []).forEach(w => w.alert.call(top, 'snow bypass attempt'));
}

function run(js) {
const script = document.createElement('script');
script.textContent = '{' + js + '}';
document.head.appendChild(script);
}

location.search.includes('disable') || SNOW((win) => {
win.alert = (msg) => {
console.log('Snow: ', 'alert API is disabled, message is printed to console instead: ', msg);
}
});

ta.value = JSON.parse(localStorage.code_snow || '""') ||
`
/*
attempts to bypass Snow after running:
SNOW((win) => {
win.alert = (msg) => {
console.log('Snow: ', 'alert API is disabled, message is printed to console instead: ', msg);
}
});
*/
debugger;
// insertion bypass attempt
(function(){
const ifr = document.createElement('iframe');
document.head.appendChild(ifr);
ifr.contentWindow.alert.call(top, 'alert bypass using insertion')
}());
// innerHTML bypass attempt
(function(){
const a = document.createElement('a');
a.innerHTML = '<iframe id="xxx"></iframe>';
document.head.appendChild(a);
a.firstChild.contentWindow.alert.call(top, 'alert bypass using innerHTML')
}());
// onload attribute bypass attempt
(function(){
const ifr = document.createElement('iframe');
ifr.onload = () => ifr.contentWindow.alert.call(top, 'alert bypass using onload attribute')
document.head.appendChild(ifr);
}());
// onload listener bypass attempt
(function(){
const ifr = document.createElement('iframe');
ifr.addEventListener('load', () => ifr.contentWindow.alert.call(top, 'alert bypass using onload listener'))
document.head.appendChild(ifr);
}());
`;

bt.addEventListener('click', () => {
localStorage.code_snow = JSON.stringify(ta.value);
run(ta.value);
});

window.addEventListener('keydown', (event) => {
if ((event.ctrlKey || event.metaKey) && event.code === 'Enter') {
bt.click();
}
})
</script>
<script src="./util.js"></script>
</body>
</html>
74 changes: 74 additions & 0 deletions demo/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
function bypass(wins) {
(wins || []).forEach(w => w.alert.call(top, 'snow bypass attempt'));
}

function run(js) {
const script = document.createElement('script');
script.textContent = '{' + js + '}';
document.head.appendChild(script);
}

(function(){
location.href.includes('self-xss-challenge-msg') && (msg.style.display = 'block');

location.search.includes('disable') || SNOW((win) => {
win.alert = (msg) => {
console.log('Snow: ', 'alert API is disabled, message is printed to console instead: ', msg);
}
});

ta.value = JSON.parse(localStorage.code_snow || '""') ||
`
/*
attempts to bypass Snow after running:
SNOW((win) => {
win.alert = (msg) => {
console.log('Snow: ', 'alert API is disabled, message is printed to console instead: ', msg);
}
});
*/
debugger;
// insertion bypass attempt
(function(){
const ifr = document.createElement('iframe');
document.head.appendChild(ifr);
ifr.contentWindow.alert.call(top, 'alert bypass using insertion')
}());
// innerHTML bypass attempt
(function(){
const a = document.createElement('a');
a.innerHTML = '<iframe id="xxx"></iframe>';
document.head.appendChild(a);
a.firstChild.contentWindow.alert.call(top, 'alert bypass using innerHTML')
}());
// onload attribute bypass attempt
(function(){
const ifr = document.createElement('iframe');
ifr.onload = () => ifr.contentWindow.alert.call(top, 'alert bypass using onload attribute')
document.head.appendChild(ifr);
}());
// onload listener bypass attempt
(function(){
const ifr = document.createElement('iframe');
ifr.addEventListener('load', () => ifr.contentWindow.alert.call(top, 'alert bypass using onload listener'))
document.head.appendChild(ifr);
}());
`;

bt.addEventListener('click', () => {
localStorage.code_snow = JSON.stringify(ta.value);
run(ta.value);
});

window.addEventListener('keydown', (event) => {
if ((event.ctrlKey || event.metaKey) && event.code === 'Enter') {
bt.click();
}
});
}());
5 changes: 4 additions & 1 deletion firefox.wdio.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ exports.config = {
//
browserName: 'firefox',
'moz:firefoxOptions': {
args: ['--headless', 'disable-gpu'],
args: [
'--headless',
'disable-gpu',
],
},
acceptInsecureCerts: true
// If outputDir is provided WebdriverIO can capture driver session logs
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
"devDependencies": {
"@babel/core": "^7.13.15",
"@babel/preset-env": "^7.13.15",
"@wdio/cli": "^7.5.3",
"@wdio/local-runner": "^7.5.5",
"@wdio/mocha-framework": "^7.5.3",
"@wdio/spec-reporter": "^7.5.3",
"@wdio/cli": "^8.12.2",
"@wdio/local-runner": "^8.12.1",
"@wdio/mocha-framework": "^8.12.1",
"@wdio/spec-reporter": "^8.12.2",
"babel-loader": "^8.2.2",
"chromedriver": "^113.0.0",
"geckodriver": "^3.2.0",
"wdio-chromedriver-service": "7.0.0",
"wdio-chromedriver-service": "^8.1.1",
"wdio-safaridriver-service": "^2.0.0",
"wdio-geckodriver-service": "^4.0.0",
"webpack": "^5.33.2",
Expand Down
7 changes: 3 additions & 4 deletions snow.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ function makeStringHook(asFrame, asHtml, arg) {
return hook;
}
function dropDeclarativeShadows(shadow, html) {
warn(WARN_DECLARATIVE_SHADOWS, shadow, html);
warn(WARN_DECLARATIVE_SHADOWS, html);
remove(shadow);
return true;
}
Expand Down Expand Up @@ -636,10 +636,9 @@ function warn(msg, a, b) {
let bail;
switch (msg) {
case WARN_DECLARATIVE_SHADOWS:
const shadow = a,
html = b;
const html = a;
bail = false;
console.warn('SNOW:', 'removing html string representing a declarative shadow:', shadow, '\n', `"${html}"`, '.', '\n', 'if this prevents your application from running correctly, please visit/report at', 'https://github.com/LavaMoat/snow/issues/32#issuecomment-1239273328', '.');
console.warn('SNOW:', 'removing html string representing a declarative shadow:', '\n', `"${html}"`, '.', '\n', 'if this prevents your application from running correctly, please visit/report at', 'https://github.com/LavaMoat/snow/issues/32#issuecomment-1239273328', '.');
break;
case WARN_OPEN_API_URL_ARG_JAVASCRIPT_SCHEME:
const url2 = a,
Expand Down
2 changes: 1 addition & 1 deletion snow.prod.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function makeStringHook(asFrame, asHtml, arg) {
}

function dropDeclarativeShadows(shadow, html) {
warn(WARN_DECLARATIVE_SHADOWS, shadow, html);
warn(WARN_DECLARATIVE_SHADOWS, html);
remove(shadow);
return true;
}
Expand Down
4 changes: 2 additions & 2 deletions src/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ function warn(msg, a, b) {
let bail;
switch (msg) {
case WARN_DECLARATIVE_SHADOWS:
const shadow = a, html = b;
const html = a;
bail = false;
console.warn('SNOW:',
'removing html string representing a declarative shadow:', shadow, '\n', `"${html}"`, '.', '\n',
'removing html string representing a declarative shadow:', '\n', `"${html}"`, '.', '\n',
'if this prevents your application from running correctly, please visit/report at',
'https://github.com/LavaMoat/snow/issues/32#issuecomment-1239273328', '.',
);
Expand Down

0 comments on commit 3684e04

Please sign in to comment.