Skip to main content

Insecure Use of Dangerous Function

Fixing Code Injection

About Code Injection

What is code injection?

Code injection is a security vulnerability that occurs when an application generates code dynamically and does not properly validate or sanitize user input before using it to generate the code.

The main difference between command injection and code injection is that command injection focuses on executing arbitrary system commands, while code injection focuses on injecting and executing arbitrary code within a program or system.

Code Injection is limited by the functionality of the injected language itself.

Check out this video for a high-level explanation:

What is the impact of code injection?

A successful code injection attack can have a wide-ranging impact, depending on the system and the attacker's goals. Here are a few potential impacts:

  • Unauthorized access: An attacker may be able to gain unauthorized access to a system or application, giving them access to sensitive data or functionality.
  • Data theft: An attacker may be able to steal data from the system, including personally identifiable information, financial data, or other sensitive data.
  • Malware installation: An attacker may be able to install malware on the system, allowing them to further compromise the system or use it as a launching point for other attacks.
  • Denial of service: An attacker may be able to launch a denial-of-service attack by executing code that overwhelms the system's resources.
  • System compromise: In some cases, a successful code injection attack can lead to the complete compromise of the system.

How to prevent code injection?

Some measures that can help prevent code injection attacks are:

  • Input validation and sanitization: Ensure that user input is validated and sanitized before it is used to generate code.
  • Perform regular security audits: Regularly audit your system and application for security vulnerabilities, including code injection vulnerabilities. Use automated tools and manual testing to identify potential issues and fix them before they can be exploited.
  • Educate your development team: Educate your development team about the risks of code injection attacks and the measures that can be taken to prevent them.

References

Taxonomies

Explanation & Prevention

Training

Dynamic evaluation of untrusted input (Backend)

If you have untrusted data coming from HTTP requests it's important to validate, filter, and sanitize the data before any further processing. That includes passing the data or a property of it to the following functions, which are responsible for dynamically evaluating code.

  • The eval function takes a string and passes it to the JavaScript compiler, then executes the resulting byte code. You can read more about the different forms of eval() usage at MDN.
  • The setInterval() and setTimeout() functions take either a function or a string as their first argument. When a string is provided as the first argument, both of these functions act as eval.
  • The Function() constructor (used with or without the new keyword) is susceptible in similar ways.

For all of the above, too much authority is granted to the eval'd text, so that the code that is passed as a string to eval is executed with the privileges of the caller. This allows untrusted code to be run in the same scope (if direct eval) as the eval function.

Option A: Use of alternative functions

  1. Go through the issues that GuardRails identified in the PR

  2. Identify usages of the above functions that take untrusted user input as arguments.

  3. Replace the insecure function with safer alternatives to achieve the same outcome. For example, parse JSON with hapi/bourne Bourne.parse(). Another example is shown below:

    var preTax = eval(req.body.preTax);

    with:

    var preTax = parseInt(req.body.preTax);
  4. Test it and ensure the functionality works as expected

  5. Ship it 🚢 and relax 🌴

Option B: Validate input with Joi

  1. Install Joi

  2. Go through the issues that GuardRails identified in the PR

  3. Replace the code that looks like:

    var preTax = eval(req.body.preTax);

    with:

    const Joi = require("joi");

    const tax_schema = Joi.object().keys({
    preTax: Joi.number().precision(2);
    });
    Joi.validate({ preTax: req.body.preTax }, tax_schema, function(error, val) {
    if (error == null) {
    var preTax = eval(req.body.preTax);
    //do stuff
    } else {
    //catch validation error
    }
    });
  4. Test it

  5. Ship it 🚢 and relax 🌴

Dynamic evaluation of untrusted input (Frontend)

There are many techniques for accepting user input in the browser, and many more for users of your browser-based application attempting to insert malicious content.

In the DOM there is a collection of properties that can access untrusted values, values that could be modified by an attacker.

  • The window.location object contains information about the current URL. This includes the protocol, hostname, port, pathname, search parameters, and hash fragment
  • The window.document object contains information about the currently loaded HTML document. This object can be used to access and manipulate the elements on the page, including form input elements that may contain untrusted values
  • The window.history object contains information about the browser's history. This object can be used to retrieve URLs previously visited, which may contain untrusted values in the query string or hash fragment
  • The window.event object contains information about the current event. This is the event that triggered the current JavaScript code to run. You can use this object to access the target element of the event and any untrusted values that may be associated with it

It's important to note that the above properties and their associated objects can be accessed and manipulated by JavaScript code running on the page, so you should always validate, filter, and sanitize any untrusted values retrieved from them before using them. That includes passing them to the following functions, which are responsible for dynamically evaluating code and function bodies as strings.

For all of the above, too much authority is granted to the eval'd text, so that the code that is passed as a string to eval is executed with the privileges of the caller. This allows untrusted code to be run in the same scope (if direct eval) as the eval function.

Specific references:

  • Built-in browser functions, such as encodeURIComponent() for encoding URLs.
  • Libraries such as DOMPurify can help prevent malicious code from being executed.

Option A: Use of alternative functions (Frontend)

  1. Go through the issues that GuardRails identified in the PR
  2. Identify usages of the above functions that take untrusted user input as arguments.
  3. Replace the insecure function with safer alternatives to achieve the same outcome. For example, parse JSON with hapi/bourne Bourne.parse() and not with eval().
  4. Test it and ensure the functionality works as expected
  5. Ship it 🚢 and relax 🌴

Dynamically Requiring Untrusted Input

This vulnerability allows untrusted input to define a package that is going to be unsafely imported (required) without validation.

If an attacker can create a malicious NPM package and have the package name/location inserted into a running application, then directly into a require() function with faulty validation, or without validation at all, they can have their malicious code executed within the context of the application code requiring it.

Option A: Validate, filter, and sanitize untrusted data

If dynamically requiring an NPM package is necessary, any untrusted data entering or passing through your software that will be used in a require function should instead be properly validated, and used to select a proxy source to be required that is approved and trusted by your organization.

  1. Locate a vulnerable pattern similar to the following:

    Requiring a package based on unvalidated untrusted input:

    import express from 'express';
    const app = express();

    app.get('/users', async (req, res) => {
    try {
    const untrustedPackage = require(req.body.package)

    // Utilising the untrusted package...

    res.send('injected');
    client.close();
    } catch (err) {
    console.error(err);
    res.status(500).send({ error: 'An error occurred' });
    }
    });

    app.listen(3000, function() {
    console.log('Listening on port 3000');
    });
  2. Replace the vulnerable code with something similar to the following:

    First, validate the untrusted input, then use the validated input to select a pre-vetted package:

    import express from 'express';
    const app = express();

    app.get('/users', async (req, res) => {
    try {
    const validatedPackage = validatePackage(req.body.package);

    const safePackage = {
    packageA: 'package-a',
    packageB: 'package-b',
    packageC: '../package-c'
    }[validatedPackage];

    const package = require(safePackage);

    // Utilise the now validated package...

    res.send('injected');
    client.close();
    } catch (err) {
    console.error(err);
    res.status(500).send({ error: 'An error occurred' });
    }
    });

    app.listen(3000, function() {
    console.log('Listening on port 3000');
    });
  3. Test it

  4. Ship it 🚢 and relax 🌴

Fixing Command Injection

About Command Injection

What is command injection?

Command injection is a type of security vulnerability that allows an attacker to execute arbitrary commands on a target system. It occurs when an application accepts user input as part of a command or query that is executed by the system without proper validation or sanitization.

For example, consider a web application that allows users to search for products by entering a product name. If the application does not properly sanitize user input and passes the input directly to the operating system's command shell, an attacker can insert malicious code into the input that could be executed by the command shell.

This could result in the attacker gaining unauthorized access to the system or performing other malicious actions.

Check out this video for a high-level explanation:

What is the impact of command injection?

A successful command injection attack can have a wide-ranging impact, depending on the system and the attacker's goals. Here are a few potential impacts:

  • Unauthorized access: An attacker may be able to gain unauthorized access to a system or application, giving them access to sensitive data or functionality.
  • Data theft: An attacker may be able to steal data from the system, including personally identifiable information, financial data, or other sensitive data.
  • Malware installation: An attacker may be able to install malware on the system, allowing them to further compromise the system or use it as a launching point for other attacks.
  • Denial of service: An attacker may be able to launch a denial-of-service attack by executing commands that overwhelm the system's resources, or deleting important system files.
  • System compromise: In some cases, a successful command injection attack can lead to complete compromise of the system, allowing the attacker to take full control.

How to prevent command injection?

Some measures that can help prevent command injection attacks are:

  • Input validation and sanitization: Ensure that user input is validated and sanitized before it is used in any system command. Use regular expressions or input filters to remove or encode any special characters that could be used to execute arbitrary commands.
  • Use parameterized queries: When executing commands that include user input, use parameterized queries instead of string concatenation.
  • Limit permissions: Limit the permissions of the user account that runs the application to only those that are necessary to perform its functions.
  • Perform regular security audits: Regularly audit your system and application for security vulnerabilities, including command injection vulnerabilities. Use automated tools and manual testing to identify potential issues and fix them before they can be exploited.
  • Educate your development team: Educate your development team about the risks of command injection attacks and the measures that can be taken to prevent them.

References

Taxonomies

Explanation & Prevention

Training

Command Injection (child_process)

Execution of arbitrary commands with untrusted input being used to influence which command is run, or how a given command runs.

If accepting untrusted data as input for child_process methods, it should be properly validated, filtered, and sanitized.

Rule-specific references:

Option A: Provide allow list for untrusted inputs

Maintain an allow list of commands to compare the untrusted input with, and only allow the untrusted command and/or arguments to progress through execution flow if it matches an item in the allow list.

By defining allow lists for validation, you will effectively filter out any/all undesirable input including commands, arguments, and shell meta-characters in their many forms.

  1. Locate a vulnerable pattern similar to the following:

    import express from 'express';
    import cp from 'node:child_process'
    const app = express();

    app.get('/users', (req, res) => {
    try {
    const command = url.parse(req.url, true).query.path;

    const proc = cp.spawn(command, ['-a', '-b'], {})
    // Set-up proc handlers.
    } catch (err) {
    res.status(500).send({ error: 'As much as the user needs to know...' });
    })
    });

    app.listen(3000, function() {
    console.log('Listening on port 3000');
    });
  2. Replace the vulnerable code with something similar to the following:

    Validation on the client side is also a good idea for user experience so that the user knows what valid commands and their arguments are before being sent to the server.

    You need to define the acceptable and agreed on commands and their arguments.

    import express from 'express';
    import cp from 'node:child_process'
    const app = express();

    app.get('/users', (req, res) => {
    try {
    const untrusted = url.parse(req.url, true).query.path;

    const allowedCommands = {
    cmd0: { cmd: 'allowedCmd0', args: ['-a', '-b']},
    cmd1: { cmd: 'allowedCmd1', args: ['-c', '-d']},
    cmd2: { cmd: 'allowedCmd2', args: ['-e', '-f']},
    cmd3: { cmd: 'allowedCmd3', args: ['-g', '-h']}
    };

    const proc = cp.spawn(allowedCommands[untrusted].cmd, [...allowedCommands[untrusted].args], {})
    // Set-up proc handlers.
    } catch (err) {
    res.status(500).send({ error: 'As much as the user needs to know...' });
    })
    });

    app.listen(3000, function() {
    console.log('Listening on port 3000');
    });
  3. Test it

  4. Ship it 🚢 and relax 🌴