Writing less insecure JavaScript

FOSDEM

6th February 2022

Video and Subtitles

QR code pointing to online slides

console.log((function whoAmI(){}).name)

André Jaenisch

Matrix: @Ryuno-Ki:matrix.org

Twitter: @AndreJaenisch

Others: jaenis.ch/about

What will you learn today?

  1. What can you do as developer?
  2. What can you do as team?
  3. What can you do as company?
  4. What can you do as community?

Motivation

Focus on the people because everything else is an implementation detail

Source: similar from Ship It!

Don't try these code snippets on other people's machines. You might risk legal liability.

There is no 100 % security. That means not, that efforts are futile, though.

Choose your avatar

Female avatar
Male avatar
Book cover of Alice and Bob Learn Application Security

What can you do as developer?

It's dangerous to go alone! Take this.

Snyk logo

If you want to see code, hit ⬇️

If you want to skip to tooling, use ➡️

Assumptions

The presentations assumes JavaScript. You might get different results with TypeScript. However, even that is not a silver bullet.

You are expected to be at least somewhat familiar with the language. Terms like prototype chain are not unknown to you.

OWASP Top Ten

Read in full

  1. Broken Access Control
  2. Cryptographic Failures
  3. Injection
  4. Insecure Design
  5. Security Misconfiguration
  6. Vulnerable and Outdated Components
  7. Identification and Authentication Failures
  8. Software and Data Integrity Failures
  9. Security Logging and Monitoring Failures
  10. Server-Side Request Forgery

Attack vectors

  1. Naming variables
  2. Cross-site scripting (XSS)
  3. Malicious package (Typo-squatting)
  4. Prototype pollution
  5. Reverse tabnapping
  6. Directory traversal
  7. Exploiting postMessage
  8. ReDOS attacks

Naming variables

Use dangerously, raw or unsafe for not validated or sanitized data. Being longer to write and defaulting to a secure state is a plus.

Back to attack vectors

Cross-site scripting (XSS)

Quoting Snyk

A cross-site scripting attack occurs when the attacker tricks a legitimate web-based application or site to accept a request as originating from a trusted source.

This is done by escaping the context of the web application; the web application then delivers that data to its users along with other trusted dynamic content, without validating it. The browser unknowingly executes malicious script on the client side in order to perform actions that are otherwise typically blocked by the browser’s Same Origin Policy.

Counter measures:

  • Validate input (e.g. using ajv for JSON schema)
  • Sanitize output (e.g. using DOMPurify)
  • Use and enforce a Content Security Policy (Laboratory might help)
  • Redirect invalid requests
  • Detect simultaneous logins (including those from 2 separate IP addresses) and invalidate those sessions

Back to attack vectors

Malicious package (Typo-squatting)

Problem: Using typos for registering package with malicious code.

Counter measures:

  • Register packages with alternative spelling (empty with a hint to the correct one).
  • Use a private npm registry (e.g. Verdaccio, bit, Nexus, Artifactory or Gemfury)
  • Use npq to install packages
  • Consider using snync for Supply Chain Confusion attack mitigation

Back to attack vectors

Prototype pollution

Prototype Pollution is a vulnerability affecting JavaScript. Prototype Pollution refers to the ability to inject properties into existing JavaScript language construct prototypes, such as objects. JavaScript allows all Object attributes to be altered, including their magical attributes such as _proto_, constructor and prototype.
An attacker manipulates these attributes to overwrite, or pollute, a JavaScript application object prototype of the base object by injecting other values. Properties on the Object.prototype are then inherited by all the JavaScript objects through the prototype chain.
When that happens, this leads to either denial of service by triggering JavaScript exceptions, or it tampers with the application source code to force the code path that the attacker injects, thereby leading to remote code execution.

Example code


              function isObject(obj) {
                return ['function', 'object'].includes(typeof obj);
              }
              function merge(target, source) {
                for (let key in source) {
                  if (isObject(target[key]) && isObject(source[key])) {
                    merge(target[key], source[key]);
                  } else {
                    target[key] = source[key];
                  }
                }
                return target;
              }
              function clone(target) { return merge({}, target); }
            

Example code


              var o = {};
              console.log(o.isAdmin);
              // => undefined
              clone({'constructor': {'prototype': {isAdmin: true}}})
              console.log(o.isAdmin);
              // => true
            

Inspired by Carlos Prolop' NodeJS - __proto__ & prototype Pollution (CC BY-SA)

Counter measures:

Back to attack vectors

Reverse tabnapping

Problem: Links with target="_blank" used to expose window.opener.

This allowed the target site to manipulate the source web page.

Counter measure

Use rel="noopener noreferrer" on links opening in a new tab.

Read About rel=noopener

Back to attack vectors

Directory traversal

Problem: Exploit HTTP to gain unauthorized access to restricted files or directories.

Main characteristic is „dot-dot-slash” (or ../).

Example call


              curl --path-as-is https://jaenis.ch/hobbies/../../
              # yields a bad request, so don't try it
            

Counter measure

  • Don't rely on user input when accessing the file system if possible.
  • Normalize path information (i.e. URL-encoding) before using it. Check prefix matches directory for which user has access rights.
  • Avoid requests to file system via URL.
  • Don't store sensitive files on web server storage.

Read curl ootw: –path-as-is.

Read Zip Slip Vulnerability.

Back to attack vectors

Exploiting postMessage

Problem: Lack of validation of origin for incoming messages allows arbitrary code execution.

Example code


              window.postMessage({name: 'sync-ready'}, '*');
              window.addEventListener('message', function (ev) {
                if (ev.data === 'page-ready'){
                  // ...
                } else {
                  chrome.runtime.sendMessage(ev.data, function(response){
                  });
                }
              }, false);
            

              // Content scripts are only executed on top-level windows
              var w = window.open(
                'https://example.com/according/to/web-extension',
                '_blank', 'width=100,height=100');
              window.setTimeout(function () {
                w.postMessage({
                  method: 'not-page-ready-but-another-allowed-method',
                  data: {
                    name: 'Key is expected by Web Extension.'
                        + 'Inject XSS payload here if used via innerHTML',
                  },
                });
                window.setTimeout(function () { w.close(); }, 0);
              }, 1000);
            

Counter measures

  • It is the developer’s responsibility to check the origin attribute of any messages received to ensure that they only accept messages from origins they expect.
  • Be careful with Regular Expressions
  • /https*:\/\/www.example.com$/i vs. https://wwwXexample.com
  • /https*:\/\/www\.example\.com/i vs. https://www.example.com.attacker.com
  • event.origin.indexOf('https://www.example.com') > -1 vs. https://www.example.com.attacker.com

The requirement for validating origin holds true for iFrames, Web Extensions or Web Sockets, too.

Read Exploiting xdLocalStorage (localStorage and postMessage).

Read Wladimir Palant's Blog.

Read OWASP HTML5 Security Cheat Sheet.

Back to attack vectors

ReDOS attacks

Explain video on YouTube and my remarks

  1. Don't use a RegEx. Sometimes using startsWith() or endsWith() are just fine. Or applying some Functional Programming. This sidesteps a whole can of worms :-)
  2. Limit input size. Checking the length of a string is comparably cheap. Error early if the input is too large.
  1. Pick into before RegEx. I assume that a find() or indexOf() isn't too expensive. If an e-mail string does not contain a @ your RegEx will fail anyway.
  2. Anchor your RegEx. ^, $ and \b can go a long way.
  1. Break it up. Similar to 1. having dedicated checks might be more readable (and thus maintainable) than one RegEx to rule them all. The syntax feels arcane anyway, so not add up on it or otherwise nobody will dare to touch it.
  1. Choose your RegEx engine wisely. I only heard about it last year, but it seems that there are several engines out there. Some making guarantees. Rust's RegEx would have saved Cloudflare from CPU exhaustion in 2019 instead of the Lua one.

Back to attack vectors

Tooling

Assumptions

Use of ESLint

Use of VS Code

Use of npm or yarn as package manager

Use of Express.js or at least in communication with Back-end team for HTML generation and server configuration

Tools

ESLint plugins

Back to tools

VS Code extensions

Back to tools

Package installation

Using npm


                # Bad
                npm install
                # Good
                npm ci
                # Better, but sometimes fail
                npm ci --ignore-scripts
              

Using yarn


                # Bad
                yarn
                # Good
                yarn install --frozen-lockfile
                # Better, but sometimes fail
                yarn install --frozen-lockfile --ignore-scripts
              

Back to tools

Test data

Back to tools

Define Content Security Policy

Deploy Content Security Policy (Laboratory might help)

Back to tools

What can you do as team?

Following items focus more on coordinates efforts within a single project.

Culture

  • Implement a no-blame-culture
  • Treat incidents as process failures

Back to efforts

Software architecture

  • Separate customer data from app ones for stricter security measures
  • Track the flow of data (monitoring as well as architecture diagram)

Back to efforts

Auth

  • Look into login with Magic Links. Alternatively use OTPs
  • Study JWTs (Read about Best Practices)
  • Apply consistent auth strategies
  • Allow for long time limit on revocable refresh tokens
  • Define short limit for session tokens
  • Store environment variables and secrets in a Vault
  • Different privileges require different grades of security. Adapt accordingly

Back to efforts

Testing

Back to efforts

Paperwork

Back to efforts

What can yo do as company?

  • Security Champions
  • Implement 2FA for developers and operators
  • Match passwords against HIBP
  • Check your backups regularly
  • Monitoring and Intrusion Detection Systems
  • Chaos engineering
  • Contract penetration tester.
  • Publish security audits.
  • Add SLAs to your service providers (including Marketing).
  • Offer a Bug Bounty program and outline boundaries.
  • Provide an e-mail address for reporting security issues and process them.
  • Understand defense in depth.
  • Segment your network. Scan for open ports and close them if you don't need them.
  • Handle responsible disclosures.
  • Define your threat models.
  • Apply extra measures like VPN for distributed teams (SSH as attack vector on compromised machine).
  • Check your security certificates for expiration.
  • Check your security certificates for revoke.
  • Familiarize with ISO 27001 norm on security.
  • Register additional domains to guard against domain squatting.
  • Raise awareness for boss fraud mails.
  • Scan for tokens in your codebases.
  • Train on OWASP Juice Shop.

What can yo do as community?

Where to go now?

  1. Set up Snyk.
  2. Subscribe to the We Hack Purple podcast.
  3. Subscribe to Troy Hunt's podcast.
  4. Study OWASP Cheat Sheet Series.

Image credits

Achievements

  • Auth
  • Community
  • Company
  • Content Security Policy
  • Culture
  • Directory Traversal
  • ESLint
  • OWASP Top Ten
  • Package Manager
  • Paperwork
  • postMessage
  • Prototype Pollution
  • ReDOS
  • Reverse Tabnapping
  • Software Architecture
  • Test Data
  • Testing
  • Typo Squatting
  • Variable naming
  • VS Code extensions
  • Warning on legal implications
  • Cross-Site Scripting

Share

Thank you

Reveal.js logo

Writing less insecure JavaScript by André Jaenisch is licensed under CC BY 4.0

Code repository