Writing less insecure JavaScript
console.log((function whoAmI(){}).name)
console.log((function whoAmI(){}).name)
Matrix: @Ryuno-Ki:matrix.org
Twitter: @AndreJaenisch
Others: jaenis.ch/about
What will you learn today?
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

What can you do as developer?
It's dangerous to go alone! Take this.

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
- Broken Access Control
- Cryptographic Failures
- Injection
- Insecure Design
- Security Misconfiguration
- Vulnerable and Outdated Components
- Identification and Authentication Failures
- Software and Data Integrity Failures
- Security Logging and Monitoring Failures
- Server-Side Request Forgery
Attack vectors
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.
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
Malicious package (Typo-squatting)
Problem: Using typos for registering package with malicious code.
Counter measures:
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
andprototype
.
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:
- Be careful with recursive merge / deepClone
- Freeze your Object prototype (Ice Factory pattern might come in handy)
- Use immutable data structures (Immutable.js, Immer)
- Use
Object.create(null);
- Use
Map
instead ofObject
- Read “JavaScript prototype pollution attack in NodeJS application.” by Oliver Arteau
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
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.
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.
ReDOS attacks
- Don't use a RegEx. Sometimes using
startsWith()
orendsWith()
are just fine. Or applying some Functional Programming. This sidesteps a whole can of worms :-) - Limit input size. Checking the length of a string is comparably cheap. Error early if the input is too large.
- Pick into before RegEx. I assume that a
find()
orindexOf()
isn't too expensive. If an e-mail string does not contain a @ your RegEx will fail anyway. - Anchor your RegEx.
^
,$
and\b
can go a long way.
- 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.
- 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.
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
VS Code extensions
- Plugin for ESLint
- Plugin for Gremlins
- Plugin for Snyk Vulnerability Scanner
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
Test data
- Use the Big List of Naughty Strings as test data for user controlled input.
- Test your forms using Bug Magnet web extension.
Define Content Security Policy
Deploy Content Security Policy (Laboratory might help)
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
Software architecture
- Learn about 12-Factor app
- Consider using Hexagonal architecture or Domain Driven Design to outline the boundaries of your application.
- Use immutable data structures (Immutable.js, Immer).
- Familiarize yourself with AuthN and AuthZ.
- Separate customer data from app ones for stricter security measures
- Track the flow of data (monitoring as well as architecture diagram)
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
Testing
- Use a SAST
- Fuzz your code
- Use Helmet for Express
- Use HTTP header to guard against click-jacking
- Follow security best practices
- Check for Website Vulnerability
Paperwork
- Maintain a SBOM
- Write ADRs
- Prepare run-books in case of emergency (Get inspired by Magento incident response plan, c.f. Episode 22 of We Hack Purple podcast)
- Print run-books in case of ransomware
- Think about how to inform customers
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?
- Encourage use of SECURITY.md
Where to go now?
- Set up Snyk.
- Subscribe to the We Hack Purple podcast.
- Subscribe to Troy Hunt's podcast.
- Study OWASP Cheat Sheet Series.
Image credits
- Alice and Bob Learn Application Security
- Snyk logo on solid background by Snyk Press-Kit
- Water drop photo by Herbert Goetsch on Unsplash
- Super Tux! by Adam Piontek on Flickr (CC BY)
- Choose your weapon by dommi-fresh (CC BY-NC)
- Kjelle fair fighter pop02 by Markfeh (CC BY-NC-SA)
- Alice and Bob generated using avataaars generator
- Keep Calm and Here We Go by Mike Egan (CC BY-SA)
- Reveal.js logo (MIT)
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
Thank you
Writing less insecure JavaScript
by André Jaenisch
is licensed under
CC BY 4.0