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

Adding security.js with 1st attempt at is_safe #4973

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 57 additions & 0 deletions IPython/html/static/base/js/security.js
@@ -0,0 +1,57 @@
//----------------------------------------------------------------------------
// Copyright (C) 2014 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------

//============================================================================
// Utilities
//============================================================================
IPython.namespace('IPython.security');

IPython.security = (function (IPython) {
"use strict";

var utils = IPython.utils;

var is_safe = function (html) {
// Is the html string safe against JavaScript based attacks. This
// detects 1) black listed tags, 2) blacklisted attributes, 3) all
// event attributes (onhover, onclick, etc.).
var black_tags = ['script', 'style', 'meta', 'iframe', 'embed'];
var black_attrs = ['style'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is CSS dangerous? Blacklisting style seems a bit dramatic.

var wrapped_html = '<div>'+html+'</div>';
// First try to parse the HTML. All invalid HTML is unsafe.
try {
var bad_elem = $(wrapped_html);
} catch (e) {
return false;
}
var safe = true;
// Detect black listed tags
$.map(black_tags, function (tag, index) {
if (bad_elem.find(tag).length > 0) {
safe = false;
}
});
// Detect black listed attributes
$.map(black_attrs, function (attr, index) {
if (bad_elem.find('['+attr+']').length > 0) {
safe = false;
}
});
bad_elem.find('*').each(function (index) {
$.map(utils.get_attr_names($(this)), function (attr, index) {
if (attr.match('^on')) {safe = false;}
});
})
return safe;
}

return {
is_safe: is_safe
};

}(IPython));

12 changes: 11 additions & 1 deletion IPython/html/static/base/js/utils.js
Expand Up @@ -493,6 +493,15 @@ IPython.utils = (function (IPython) {
}
}

var get_attr_names = function (e) {
// Get the names of all the HTML attributes of the element e.
var el = $(e)[0];
var arr = [];
for (var i=0, attrs=el.attributes, l=attrs.length; i<l; i++){
arr.push(attrs.item(i).nodeName);
}
return arr;
}

return {
regex_split : regex_split,
Expand All @@ -516,7 +525,8 @@ IPython.utils = (function (IPython) {
browser : browser,
platform: platform,
is_or_has : is_or_has,
is_focused : is_focused
is_focused : is_focused,
get_attr_names: get_attr_names
};

}(IPython));
Expand Down
16 changes: 11 additions & 5 deletions IPython/html/static/notebook/js/outputarea.js
Expand Up @@ -489,12 +489,18 @@ var IPython = (function (IPython) {
if ((json[type] !== undefined) && append) {
if (!this.trusted && !OutputArea.safe_outputs[type]) {
// not trusted show warning and do not display
var content = {
text : "Untrusted " + type + " output ignored.",
stream : "stderr"
var is_safe = false;
if (type==='text/html' || type==='text/svg') {
is_safe = IPython.security.is_safe(json[type]);
}
if (!is_safe) {
var content = {
text : "Untrusted " + type + " output ignored.",
stream : "stderr"
}
this.append_stream(content);
continue;
}
this.append_stream(content);
continue;
}
var md = json.metadata || {};
var toinsert = append.apply(this, [json[type], md, element]);
Expand Down
66 changes: 41 additions & 25 deletions IPython/html/static/notebook/js/textcell.js
Expand Up @@ -21,6 +21,7 @@ var IPython = (function (IPython) {

// TextCell base class
var key = IPython.utils.keycodes;
var security = IPython.security;

/**
* Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
Expand Down Expand Up @@ -248,6 +249,17 @@ var IPython = (function (IPython) {
this.element.find('div.text_cell_render').html(text);
};

TextCell.prototype.insert_security_warning = function() {
// Inject a security warning into the TextCell's rendered div.
var e = this.element.find('div.text_cell_render');
e.empty();
var warning = "This cell contains content that is unsafe from a security " +
"standpoint. This unsafe content includes all JavaScript code " +
"and CSS styling. To fix the problem, please edit the cell " +
"and remove the unsafe content."
e.append($('<div/>').addClass('alert alert-error').text(warning));
}

/**
* @method at_top
* @return {Boolean}
Expand Down Expand Up @@ -349,21 +361,20 @@ var IPython = (function (IPython) {
text = text_and_math[0];
math = text_and_math[1];
var html = marked.parser(marked.lexer(text));
html = $(IPython.mathjaxutils.replace_math(html, math));
// links in markdown cells should open in new tabs
html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
try {
var safe = security.is_safe(html);
if (safe) {
html = $(IPython.mathjaxutils.replace_math(html, math));
// links in markdown cells should open in new tabs
html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
this.set_rendered(html);
} catch (e) {
console.log("Error running Javascript in Markdown:");
console.log(e);
this.set_rendered($("<div/>").addClass("js-error").html(
"Error rendering Markdown!<br/>" + e.toString())
);
} else {
this.insert_security_warning();
}
this.element.find('div.text_cell_input').hide();
this.element.find("div.text_cell_render").show();
this.typeset()
if (safe) {
this.typeset();
}
};
return cont;
};
Expand Down Expand Up @@ -528,22 +539,27 @@ var IPython = (function (IPython) {
text = text_and_math[0];
math = text_and_math[1];
var html = marked.parser(marked.lexer(text));
var h = $(IPython.mathjaxutils.replace_math(html, math));
// add id and linkback anchor
var hash = h.text().replace(/ /g, '-');
h.attr('id', hash);
h.append(
$('<a/>')
.addClass('anchor-link')
.attr('href', '#' + hash)
.text('¶')
);

this.set_rendered(h);
this.typeset();
var safe = security.is_safe(html);
if (safe) {
var h = $(IPython.mathjaxutils.replace_math(html, math));
// add id and linkback anchor
var hash = h.text().replace(/ /g, '-');
h.attr('id', hash);
h.append(
$('<a/>')
.addClass('anchor-link')
.attr('href', '#' + hash)
.text('¶')
);
this.set_rendered(h);
} else {
this.insert_security_warning();
}
this.element.find('div.text_cell_input').hide();
this.element.find("div.text_cell_render").show();

if (safe) {
this.typeset();
}
};
return cont;
};
Expand Down
4 changes: 4 additions & 0 deletions IPython/html/static/notebook/less/textcell.less
Expand Up @@ -18,6 +18,10 @@ div.text_cell_render {
border-style: none;
padding: 0.5em 0.5em 0.5em @code_padding;
color: @textColor;

.alert.alert-error {
margin: 0px;
}
}

a.anchor-link:link {
Expand Down
2 changes: 1 addition & 1 deletion IPython/html/static/style/ipython.min.css
Expand Up @@ -167,7 +167,7 @@ p.p-space{margin-bottom:10px}
.rendered_html *+img{margin-top:1em}
div.text_cell{padding:5px 5px 5px 0;display:-webkit-box;-webkit-box-orient:horizontal;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:horizontal;-moz-box-align:stretch;display:box;box-orient:horizontal;box-align:stretch}
div.text_cell_input{color:#000;border:1px solid #cfcfcf;border-radius:4px;background:#f7f7f7}
div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}
div.text_cell_render{outline:none;resize:none;width:inherit;border-style:none;padding:.5em .5em .5em .4em;color:#000}div.text_cell_render .alert.alert-error{margin:0}
a.anchor-link:link{text-decoration:none;padding:0 20px;visibility:hidden}
h1:hover .anchor-link,h2:hover .anchor-link,h3:hover .anchor-link,h4:hover .anchor-link,h5:hover .anchor-link,h6:hover .anchor-link{visibility:visible}
div.cell.text_cell.rendered{padding:0}
Expand Down
2 changes: 1 addition & 1 deletion IPython/html/static/style/style.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions IPython/html/templates/notebook.html
Expand Up @@ -314,6 +314,7 @@

<script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("base/js/security.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
Expand Down
35 changes: 35 additions & 0 deletions IPython/html/tests/casperjs/test_cases/security.js
@@ -0,0 +1,35 @@
safe_tests = [
"<p>Hi there</p>",
'<h1 class="foo">Hi There!</h1>',
'<div><span>Hi There</span></div>'
];

unsafe_tests = [
"<script>alert(999);</script>",
'<a onmouseover="alert(999)">999</a>',
'<a onmouseover=alert(999)>999</a>',
'<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
'<IMG SRC=# onmouseover="alert(999)">',
'<<SCRIPT>alert(999);//<</SCRIPT>',
'<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >',
'<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">',
'<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert(999);">',
'<IFRAME SRC="javascript:alert(999);"></IFRAME>',
'<IFRAME SRC=# onmouseover="alert(document.cookie)"></IFRAME>',
'<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv MjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpOzwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" AllowScriptAccess="always"></EMBED>',
];

casper.notebook_test(function () {
this.each(safe_tests, function (self, item) {
var is_safe = self.evaluate(function (item) {
return IPython.security.is_safe(item);
}, item);
this.test.assert(is_safe, item);
});
this.each(unsafe_tests, function (self, item) {
var is_safe = self.evaluate(function (item) {
return IPython.security.is_safe(item);
}, item);
this.test.assert(!is_safe, item);
});
});