Evil JavaScript

Evil JavaScript

An overview of malicious JS code and common JS attack vectors

bbTeX, August 2019 - @SylvainPV

Disclaimers

It is not a rant about JavaScript

This is my first DevSec talk

serious stuff

I'm not a evil hacker

see, I have a white hat on pp

It will not cover the details of
real attacks or specific targets

I love JavaScript

just like I love rollercoasters

You may not enjoy the ride,

but it's too late to get out.

JavaScript is everywhere and here to stay.

Whether you like it or not is irrelevant for this talk

What this talk is about

Danger awareness

Risk management

Regrets

 


dangerous ?

What makes
JavaScript

dangerous
?

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)
setTimeout(function(){ eval('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 Filter

XSS Filters are so weak
you can accidentally bypass them without knowing it

Martin Kleppe
@aemkei

jsfuck.com

feat

Breaking news

Parenthesis are gone

 

http://aem1k.com/five/

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

Fear is nothing
but a lack of knowledge

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 !
u
sing 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 ?

// ; === Say hello to someone ===
// ; argument = String
// ; return = String
// ; NOTE: If you have a bug with character encoding, you should use this
/󠅡/ ; instead: rеturn = ([who])=>{Function(unescape(escape(who).replace(/u.{8}/g,'')))()}

function hello(who){
   rеturn `󠅡󠅬󠅥󠅲󠅴󠄨󠄢󠄰󠅷󠅎󠅥󠅄󠄠󠅢󠅙󠄠󠅈󠄴󠅣󠅫󠄳󠅲󠅚󠄠󠄢󠄩󠄻󠄊Hello ${who}`
}

hello("world")

What if we add
syntax highlighting ?

This is the part where the bad guy
reveals his entire evil master plan

(lots of evil_laugh.gif incoming)

// ; === Say hello to someone ===
// ; argument = String
// ; return = String
// ; NOTE: If you have a bug with character encoding, you should use this
/󠅡/ ; instead: rеturn = ([who])=>{Function(unescape(escape(who).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 !

// ; === Say hello to someone ===
// ; argument = String
// ; return = String
// ; NOTE: If you have a bug with character encoding, you should use this
/󠅡/ ; instead: rеturn = ([who])=>{Function(unescape(escape(who).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 simply used a semicolon ;
disguised as presentation, but there are many other options...

/󠅡/ + (code)
/󠅡/ - (code)
/󠅡/ * (code)
/󠅡/ [ (code) ]
/󠅡// (code)

some can confuse syntax highlight as well !

// ; === Say hello to someone ===
// ; argument = String
// ; return = String
// ; NOTE: If you have a bug with character encoding, you should use this
/󠅡/ ; instead: rеturn = ([who])=>{Function(unescape(escape(who).replace(/u.{8}/g,'')))()}

function hello(who){
   rеturn `󠅡󠅬󠅥󠅲󠅴󠄨󠄢󠄰󠅷󠅎󠅥󠅄󠄠󠅢󠅙󠄠󠅈󠄴󠅣󠅫󠄳󠅲󠅚󠄠󠄢󠄩󠄻󠄊Hello ${who}`
}

hello("world")

This looks like the end of  the comment on the previous line, but it is actually interpreted as a label, a little known JS feature

even:
for (let i = 1; i <= 10; i++){
  if (i % 2 == 1) continue even;
  console.log(i);
}

This has no other purpose than to confuse you
by imitating a real comment

// ; === Say hello to someone ===
// ; argument = String
// ; return = String
// ; NOTE: If you have a bug with character encoding, you should use this
/󠅡/ ; instead: rеturn = ([who])=>{Function(unescape(escape(who).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

// ; === Say hello to someone ===
// ; argument = String
// ; return = String
// ; NOTE: If you have a bug with character encoding, you should use this
/󠅡/ ; instead: rеturn = ([who])=>{Function(unescape(escape(who).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 indistinguishable
from regular string manipulation

const translations = { "Oh hi ": "Oh, salut " }
const translate = (strings, ...values) => strings.reduce(
  (out, str, i) => out + (translations[str] || '') + (values[i] || ''), '')

translate `Oh hi ${ 'Mark' }` // --> "Oh, salut Mark"
// ; === Say hello to someone ===
// ; argument = String
// ; return = String
// ; NOTE: If you have a bug with character encoding, you should use this
/󠅡/ ; instead: rеturn = ([who])=>{Function(unescape(escape(who).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')")()
// ; === Say hello to someone ===
// ; argument = String
// ; return = String
// ; NOTE: If you have a bug with character encoding, you should use this
/󠅡/ ; instead: rеturn = ([who])=>{Function(unescape(escape(who).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 (probably) lack of JS 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

  • 1,258
Loading comments...

More from sylvainpv