Chapter 13. Injection

One of the most commonly known types of attacks against a web application is “SQL Injection”. SQL Injection is a type of “injection” attack that specifically targets SQL databases—allowing a malicious user to either provide their own parameters to an existing SQL query or to escape a SQL query and provide their own query. Naturally, this typically results in a compromised database as a result of the escalated permissions the SQL interpreter is given by default.

SQL injection is the most common form of injection, but not the only form. Injection attacks have two major components: an interpreter and a payload from a user that is somehow read into the interpreter.

This means injection attacks can occur against command-line utilities like FFMPEG (a video compressor) as well as against databases (like the traditional SQL injection case).

Let’s take a look several forms of injection attack so that we can get a good understanding of what type of application architecture is required for such an attack to work, and then how a payload against a vulnerable API could be formed and delivered.

SQL Injection

injection-sql
Figure 13-1. SQL Injection—a SQL string is escaped in an HTTP payload, leading to custom SQL queries being executed on behalf of the end-user.

SQL Injection is the most classically referenced form of injection.

Traditionally many open source software packages (OSS) were built using a combination of PHP and SQL (often MySQL). Many of the most referenced SQL injection vulnerabilities throughout history occurred as a result of PHP’s relaxed view on interpolation between view, logic and data code.

Old-school PHP developers would interweave a combination of SQL, HTML and PHP into their PHP files—a organizational model supported by PHP that would be mis-used resulting an an enormous amount of vulnerable PHP code.

Let’s look at an example of a PHP code block for an old-school forum software that allows for a user to login:

<?php if ($_SERVER['REQUEST_METHOD'] != 'POST') {
  echo'
   <div class="row">
     <div class="small-12 columns">
         <form method="post" action="">
           <fieldset class="panel">
             <center>
               <h1>Sign In</h1><br>
             </center>
             <label>
               <input type="text" id="username" name="username"
               placeholder="Username">
             </label>
             <label>
              <input type="password" id="password" name="password"
              placeholder="Password">
             </label>
             <center>
               <input type="submit" class="button" value="Sign In">
             </center>
           </fieldset>
         </form>
     </div>
  </div>';
} else {
  // the user has already filled out the login form.
  // pull in database info from config.php
   $servername = getenv('IP');
   $username   = $mysqlUsername;
   $password   = $mysqlPassword;
   $database   = $mysqlDB;
   $dbport     = $mysqlPort;
   $database = new mysqli($servername, $username, $password, $database,$dbport);
   if ($database->connect_error) {
     echo "ERROR: Failed to connect to MySQL";
 	die;
   }
  $sql = "SELECT userId, username, admin, moderator FROM users WHERE username =
   '".$_POST['username']."' AND password = '".sha1($_POST['password'])."';";
  $result = mysqli_query($database, $sql);
}

As you can see in this login code, PHP, SQL and HTML are all intermixed. Furthermore the SQL query is generated based off of concatenation of query params with no sanitization occurring prior to the query string being generated.

The interweaving of HTML, PHP and SQL code most definitely made SQL injection much easier for PHP-based web applications.

Even some of the largest OSS PHP applications like WordPress have fallen victim to this in the past.

In more recent years, PHP coding standards have become much more strict and the language has implemented tools to reduce the odds of SQL injection occurring.

Furthermore, PHP as a language of choice for application developers has decreased in usage. According to the TIOBE index, an organization that measures the popularity of programming languages: PHP usage has declined several percent per year since about 2010.

The result of these developments is that there is less SQL injection across the entire web—in fact injection vulnerabilities have decreased from nearly 5% of all vulnerabilities in 2010 to less than 1% of all vulnerabilities found today according to the National Vulnerability Database.

The security lessons learned from PHP have lived on in other languages and it is much more difficult to find SQL injection vulnerabilities in today’s web applications. It is still possible however, and still common in applications that do not make use of secure coding best practices.

Let’s consider another simple NodeJS / ExpressJS server—this time one that communicates with a SQL database:

const sql = require('mssql');

/*
 * Recieve a POST request to /users, with a user_id param on the request body.
 *
 * A SQL lookup will be performed, attempting to find a user in the database
 * with the `id` provided in the `user_id` param.
 *
 * The result of the database query is sent back in the response.
 */
app.post('/users', function(req, res) {
  const user_id = req.params.user_id;

 /*
  * Connect to the SQL database (server-side).
  */
  await sql.connect('mssql://username:password@localhost/database');

  /*
   * Query the database, providing the `user_id` param from the HTTP
   * request body.
   */
  const result = await sql.query('SELECT * FROM users WHERE USER = ' + user_id);

 /*
  * Return the result of the SQL query to the requester in the
  * HTTP response.
  */
  return res.json(result);
});

In this example, a developer used direct string concatenation in order to attach the query param to the SQL query. This assumes the query param being sent over the network has not been tampered with, which we know not to be a reliable metric for legitimacy.

In the case of a valid user_id, this query will return a user object to the requester.

In the case of a more malicious user_id string, many more objects could be returned from the database. Let’s look at one example:

const user_id = '1=1'

Ah, the old truthy evaluation. Now the query says: SELECT * FROM users where USER = true which translates into “give all user objects back to the requester”.

What if we just started a new statement inside of our user_id object?

user_id = '123abc; DROP TABLE users;';

Now our query looks like this: SELECT * FROM users WHERE USER = 123abd; DROP TABLE users;. In other words, we appended another query on top of the original query. Oops, now we need to rebuild our userbase.

A more stealthy example can be something like this:

const user_id = '123abc; UPDATE users SET credits = 10000 WHERE user = 123abd;'

Now rather than requesting a list of all users, or dropping the users table we are using the second query to update our own user account in the database—in this case giving ourself more in-app credits than we should otherwise have.

There are a number of great ways to prevent these attacks from occurring, as SQL injection defenses have been in development for over two decades now. We will discuss in details how to defend against these attacks in Part III.

Code Injection

injection-cli
Figure 13-2. CLI Injection—a CLI called by an API endpoint is provided with additional unexpected commands due to lack of sanitization. These commands are executed against the CLI.

In the injection world, SQL Injection is just a subset of “injection” style attacks. SQL Injection is categorized as injection because it involves an interpreter (the SQL interpreter) being targeted by a payload which is read into the interpreter as a result of improper sanitization which should allow only specific paramaters from the user to be read into the interpreter.

SQL injection is #1 an injection attack and #2 a code injection attack. This is because the script that runs in an injection attack runs under an interpreter or CLI rather than against the host operating system (command injection).

As mentioned prior, there are many lesser known styles of code injection that do not rely on a database. These are lesser known for a number of reasons, first off almost every complex web application today relies on a database for storing and retrieving user data. So it’s much more likely you will find SQL or other database injection than injection against a less common CLI running on the server.

In addition to that, knowledge of exploiting SQL databases through injection is very common spread and SQL injection attacks are easy to research. You can perform a couple quick searches on the internet and find enough reading material on SQL injection to last you for hours, if not days.

Other forms of code injection are harder to research, largely not because they are less common (they are, but I don’t believe that’s why there is less documentation)—but instead because many times code injection is application specific. In other words, almost every web application will make use of a database (typically some type of SQL)—but not every web application will make use of other CLI / Interpreters that can be controlled via an API endpoint.

Let’s consider an image / video compression server which MegaBank has allocated for use in their customer-facing marketing campaigns.

This server is a collection of REST API’s located at https://media.mega-bank.com. In particular, it consists of a few interesting APIs:

  1. uploadImage (POST)

  2. uploadVideo (POST)

  3. getImage (GET)

  4. getVideo (GET)

The endpoint uploadImage() is a simple NodeJS endpoint that looks something akin to this:

const imagemin = require('imagemin');
const imageminJpegtran = require('imagemin-jpegtran');
const fs = require('fs');

/*
 * Attempts to upload an image provided by a user to the server.
 *
 * Makes use of imagemin for image compression to reduce impact on server
 * drive space.
 */
app.post('/uploadImage', function(req, res) {
  if (!session.isAuthenticated) { return res.sendStatus(401); }

  /*
   * Write the raw image to disk.
   */
  fs.writeFileSync(`/images/raw/${req.body.name}.png`, req.body.image);

  /*
   * Compresses a raw image, resulting in an optimized image with lower disk
   * space required.
   */
  const compressImage = async function() {
    const res = await imagemin([`/images/raw/${req.body.name}.png`],
    `/images/compressed/${req.body.name}.jpg`);

    return res;
  };

  /*
   * Compress the image provided by the requester, continue script
   * expecution when compression is complete.
   */
  const res = await compressImage();

  /*
   * Return a link to the compressed image to the client.
   */
  return res.status(200).json({url: `https://media.mega-bank.com/images/${req.body.name}.jpg` });
});

This is a pretty simple endpoint that converts a png image to a jpg. It makes use of the imagemin library to do so, and does not take any params from the user to determine the compression type with the exception of the file name.

It may however be possible for one user to take advantage of file-name duplication and cause the imagemin library to overwrite existing images—such is the nature of filenames on most operating systems:

// on the front-page of https://www.mega-bank.com
<html>
  <!-- other tags  -->
  <img src="https://media.mega-bank.com/images/main_logo.png">
  <!-- other tags -->
</html>
const name = 'main_logo.png';
// uploadImage POST with req.body.name = main_logo.png

This doesn’t look like an injection attack, because it’s just a JS library which is converting and saving an image. In fact, it just looks like a poorly written API endpoint which did not consider a name conflict edgecase.

However, because the imagemin library invokes a CLI (imagemin-cli) this would actually be an injection attack—making use of an improperly sanitized CLI attached to an API to perform unintended actions.

This is a very simple example though, with not much exploitability left beyond the current case. Let’s look at a more detailed example of code injection outside of the SQL realm:

const exec = require('child_process').exec;
const converter = require('converter');

const defaultOptions = '-s 1280x720';

/*
 * Attempts to upload a video provided by the initiator of the HTTP post.
 *
 * The video's resolution is reduced for better streaming compatibility,
 * this is done with a library called `converter`.
 */
app.post('/uploadVideo', function(req, res) {
 if (!session.isAuthenticated) { return res.sendStatus(401); }

 // collect data from HTTP request body
 const videoData = req.body.video;
 const videoName = req.body.name;
 const options = defaultOptions + req.body.options;

 exec(`convert -d ${videoData} -n ${videoName} -o ${options}`);
});

Let’s assume this fictional “converter” library runs a CLI in it’s own context, similarly to many unix tools. In other words, after running the command convert the executor is now scoped to the commands provided by converter rather than those provided by the host OS.

In our case, a user could easily provide valid inputs—perhaps compression type and audio bitrate. These could look like this:

const options = '-c h264 -ab 192k';

On the other hand, they might be able to invoke additional commands based on the structure of the CLI:

const options = '-c h264 -ab 192k  convert -dir /videos -s 1x1';

How to inject additional commands into a CLI is based on the architecture of the CLI—some CLI’s support multiple commands in one line while others do not. Many are broken by line breaks, spaces or ampersands (&&).

In this case, we used a line break to add an additional statement to the converter CLI. This was not the developers intended use case as the additional statement allows us to redirect the converter CLI and make modifications to videos we do not have ownership of.

In the case that this CLI ran against the host OS versus in it’s own contained environment, we would have command injection instead of code injection. Imagine the following:

$ convert -d vidData.mp4 -n myVid.mp4 -o '-s 1280x720'

This command is running in bash, via the terminal of a Unix OS—as most compression software runs.

If the quotes could be escaped in the node endpoint prior to being executed against the host OS:

const options = "' && rm -rf /videos";

As a result of the ' apostrophy to break the options string, we now run into a much more dangerous form of injection which results in the following command being run against the host OS:

$ convert -d vidData.mp4 -n myVid.mp4 -o '-s 1280x720' && rm -rf /videos

As such, while code injection is limited to an interpreter or CLI—command injection exposes the entire operating system.

When interpolating between script and system-level commands it is essential to pay attention to detail in how string is sanitized prior to being executed against a host OS (linux, mac, windows, etc) or interpreter (sql, cli’s, etc) in order to prevent command injection and code injection.

Command Injection

injection-command
Figure 13-3. Command Injection—an API endpoint makes bash commands, and the request from a client is included in the command generation. A malicious user adds in custom commands, modifying the normal operation of the API endpoint.

My reasoning for introducing the CLI example using a video converter in the last subchapter was to ease into command injection.

So far we have learned that code injection involves taking advantage of an improperly written API to make an interpreter or CLI perform actions that the developer did not intend.

We have also learned that command injection is an elevated form of code injection where rather than performing unintended actions against a CLI or interpreter we are performing unintended actions against an operating system.

Let’s step back for a second and consider the implications of an attack at this level.

First off, the ability to execute commands (typically bash) against a Unix-based operating system (mac or linux) has very serious dangers attached to it.

If we have direct access to the host Unix-os (over 95% of servers are Unix-based), and our commands are interpreted as a super user—we can do anything we want to that operating system.

A compromised OS gives the hacker access to a number of very integral files and permissions such as:

  1. /etc/passwd—keeps track of every user account on the OS

  2. /etc/shadow—contains encrypted passwords for users

  3. ~/.ssh—contains ssh keys for communicating with other systems

  4. /etc/apache2/httpd.conf—configuration for apache-based servers

  5. /etc/nginx/nginx.conf—configuration for nginx-based servers

Furthermore, command injection could potentially give us write permissions against these files in addition to read permissions.

A hole like this opens up an entire host of potential attacks where we can make use of the command injection to cause more havoc than expected:

  1. Steal data from the server (obvious)

  2. Re-write log files to hide our tracks

  3. Add an additional database user with write access for later use

  4. Delete important files on the server

  5. Wipe the server and kill it

  6. Make use of integrations with other servers / API’s, for example using a server’s Sendgrid keys to send spam mail.

  7. Change a single login form in the web app to be a phishing form that sends unencrypted passwords to your site.

  8. Lock the admins out and blackmail them

As you can see, command injection is one of the most dangerous types of attacks a hacker has in their toolkit. It is at the very top of every vulnerability risk rating scale, and will continue to be there for a long time to come even with the mitigations in place on modern webservers.

One of these mitigations on Unix-based operating systems is a robust permissions systems that may be able to mitigate some of the risk by reducing the damage that could be caused by a compromised endpoint.

Unix-based operating systems allow for detailed permissions to be applied to files, directories, users and commands. Correct setup of these permissions can potentially eliminate the risk of many of the bullet points above by forcing an API to run as an unprivileged user.

Unfortunately, most of the applications at risk for command injection do not take these steps to make advanced user permission profiles for their code.

Let’s look at how simple code injection can be with another fast and dirty example:

const exec = require('child_process').exec;
const fs = require('fs');
const safe_converter = require('safe_converter');

/*
 * Upload a video to be stored on the server.
 *
 * Makes use of the `safe_converter` library to convert the raw video,
 * prior to removing the raw video from disc and returning a HTTP 200 status
 * code to the requester.
 */
app.post('/uploadVideo', function(req, res) {
 if (!session.isAuthenticated) { return res.sendStatus(401); }

 /*
  * Write the raw video data to disk, where it can be later
  * compressed and then removed from disk.
  */
 fs.writeFileSync(`/videos/raw/${req.body.name}`, req.body.video);

 /*
  * Convert the raw, unoptimized video—resulting in an optimized
  * video being generated.
  */
 safe_converter.convert(`/videos/raw/${req.body.name}`, `'/videos/converted/${req.body.name}`)
 .then(() => {

    /*
     * Remove the raw video file when it is no longer needed.
     * Keep the optimized video file.
     */
    exec(`rm /videos/raw/${req.body.name}`);
    return res.sendStatus(200);
  });
});

In this example, several operations are occurring:

  1. We write the video data to the disk in the /videos/raw directory

  2. We convert the raw video file, writing the output to /videos/converted

  3. We remove the raw video (it is no longer needed)

This is a pretty typically compression workflow. However, in this example the line that removes the raw video file: exec(`rm /videos/raw/${req.body.name});` relies on unsanitized user input to determine the name of the video file to remove.

Furthermore, the name is not parameterized but instead concatenated to the bash command as a string. This means additional commands could be present that occur after the video is removed. Let’s evaluate a scenario that could result in this:

// name to be sent in POST request
const name = 'myVideo.mp4 && rm -rf /videos/converted/';

Similarly to the final example in the code execution, here an improperly sanitized input could result in additional command being executed against the host OS—hence “command injection”.

Summary

Injection style attacks range beyond the common SQL injection—and span into many other technologies as seen in this chapter.

Unlike XXE attacks, injection style attacks are not the result of a specific weak specification—but instead a type of vulnerability that arises when the user’s inputs are trusted too much.

Injection style attacks are great to master as a bug bounty hunter or penetration tester because while well known databases probably have defenses set up—injection attacks against parsers and CLI’s are less well documented and hence likely to have less rigid defensive mechanisms in place.

Injection attacks require a bit of understanding of an application’s function, as they typically arise as a result of server code being executed that includes text parsed from the client’s HTTP request.

These attacks are powerful, elegant and capable of accomplishing many goals—be it data theft, account takeover, permissions elevations or just causing general chaos.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.88.249