Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce Snow integration with CSP #118

Merged
merged 20 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading