So far in this series, I've discussed general security principles and some considerations around handling inputted and outputted data. In this article, I want to give an overview of the last segment of the web security course I recently completed, a list of the most common security attacks (and some tips on how to protect against them). The list included in this article is the attacks covered in this one course. This is by no means a comprehensive list of all attacks or defenses against them. For the last information on the current top ten application security risks, a great resource is The OWASP Foundation's Top Ten List. I particularly enjoyed this part of the course because I felt that it tied everything together, showing how the principles and concepts covered in previous sections can be applied to your application's threat model.
Credential attacks occur when an attacker gains access to a user's login credentials and uses that to access the user's account. There are several different types of credential attacks, but this course covered three in depth: credential theft, brute force attacks, and dictionary attacks.
Credential theft is when attackers gain access to a user's username and password. This can happen through human error, such as leaving usernames and passwords lying around unprotected (admit it, you've done that once or twice ... I know I have), but most often it happens through a data breach. When a company's database is breached, usernames and passwords are available to hackers, and while the data is safer if it is encrypted, hackers can often get past the encryption given enough time.
A brute force attack is when a hacker uses software to try every possible password - attempting to discover a password through trial-and-error. It is impossible to know how long it will take a brute force attack to discover a password because there's no way to know when the software will hit on the right password. Using longer passwords increases the maximum time that the attack could take, but because we don't know the order in which the software will attempt character combinations, even a long password could potentially be discovered quickly by a brute force attack.
Wouldn't it be crazy if your password was
bsUa8752!@nhJp039 and the software guessed it on the first try? With a brute force attack it's not likely, but it is possible because the order of attempts is random.
A dictionary attack is a type of brute force attack that prioritizes dictionary words. Working off the assumption that most people will use dictionary words in their passwords (mostly because they're easier to remember and nobody likes having to remember long, bizarre passwords), these attacks try character combinations including dictionary words before moving on to non-word combinations. While
bsUa8752!@nhJp039 is safer from a dictionary attack than a password that uses dictionary words (for example,
Yellow20!), it still can be discovered eventually.
While the maximum time for a dictionary attack to discover a password is the same as a brute force attack (after all, they are working from the same set of potential passwords), in real-world situations dictionary attacks tend to find passwords faster because they are prioritizing common and known passwords.
Credential stuffing uses username/password combinations that have been exposed through data breaches and tries them on other websites. This is why it is so important to make passwords unique - if your password for one website is exposed, as long as you don't reuse that password on a different website, any attempt to access your accounts through credential stuffing will fail.
The best way to protect against credential attacks is to have a safe password (and to not reuse that safe password). The most secure passwords are at least 12 characters long, with 15 or more characters being ideal. Having variety in your password, including uppercase/lowercase, numbers, and special characters, is helpful but should be used in combination with a long password. Avoid using dictionary words or common patterns, as those are likely to be discovered in a dictionary attack. For extra security, include multi-factor authentication in your app to ensure that hackers need more than just a username and password to access the account. Your users may not always love it (I know it drives me crazy sometimes), but they'll appreciate you making their account more secure.
URL Manipulation is when an attacker edits the URL in the browser's location bar to probe a website. Editing the URL can lead an attacker to a private page that should not be accessible to that user, could reveal database information, or could give the attacker more information about the technology behind the website (for example, if an attacker discovers an
admin.php page, even if they learn nothing from the content of that page, they now know that the website is using PHP and can target their attacks using known PHP vulnerabilities). If you suddenly notice a lot of hits to URLs that don't exist on your site (or that have no way for the user to reach them), an attacker may be trying a URL manipulation attack on your site.
One of the most dangerous consequences of URL manipulation is insecure direct object reference (IDOR), when code accesses a restricted resource based on user input without verifying the user's authorization to access that resource. If an attacker can find the URL for an insecure resource, they may be able to access private information or access resources they should not have access to.
IDOR is one of the most dangerous consequences of URL manipulation, but there are ways to prevent it. Some of the prevention measures discussed in this course include:
- When developing a website, take into consideration the fact that URLs are exposed and easily editable. Even if you never link to a particular URL, it could still be found by an attacker.
- Consider all edge cases and expect the unexpected (this is a good general security principle that also applies here).
- Define an allow list for accepted URL parameters and reject all other input.
- Handle errors and unfound URLs gracefully and vaguely - having an error page is important, but don't display any specific feedback on the error page that could be helpful to a potential attacker.
- Validate a user's information before allowing access to protected resources - make the user log in and then verify that they are logged in and allowed to access that resource.
- Change direct object reference to indirect. One way to do this is to provide a list of options for the users to choose from and then use an option number for the URL (and have code that maps the option number to the item you're trying to reference). For example, on an e-commerce website's order history page, list 4 orders for which the user can view invoices, and have the URLs be something like "/order-3", and then in your code map "order-3" to the third of the four invoices that could be chosen (which may, in fact, have been invoice #89578 - but we don't want an attacker to know that because that gives them too much information about how we structure and use order numbers). By not directly linking to invoice #89578, we ensure that only someone who has access to the order history page will have access to that invoice (which could contain personal data such as name, address, or phone number).
SQL injection is an attack that occurs when untrusted data is used to construct a SQL query. When raw data is inserted into a SQL query string (for example, if SQL is inserted as part of an email address during login), it allows the attacker to execute arbitrary requests to a SQL database. These attacks can be used to probe the structure of the database, to steal data, to add or change database information, to destroy data, or to bypass authentication. One of my favorite examples of SQL injection is a popular xkcd comic where young Bobby Tables's school loses an entire year's worth of records because they inserted an unsanitized student name into the database.
Blind SQL injection is a type of SQL injection where a vulnerability exists but it does not affect the browser. An attacker who attempts to exploit this vulnerability will not receive any visual confirmation that their attack was successful. Because attackers have no visual confirmation of the success of an attack, one technique that is often used is injecting a SQL query that will cause the database to pause or slow return times (one common way to do this is with SQL's sleep command). If the attacker sees a slowed or paused response, that indicates a successful attack, and the attacker will often attempt to further exploit that same vulnerability.
Several strategies can be used to protect against SQL injection:
- Use the principle of least privilege when deciding what access your database should have. Your database will most likely need to read and write, so those actions should be permitted. It probably doesn't need to modify schema or drop tables, so it should not have access to those actions.
- Sanitize data. As we learned from Bobby Tables, unsanitized query strings can have unintended consequences. Sanitizing your input before passing it to the database (by either escaping or replacing powerful characters) prevents rogue code from causing damage.
- Use a SQL prepared statement. A SQL prepared statement is a SQL query that is prepared in advance with placeholders for dynamic data. The data being inputted must match certain requirements, and there's no concern about modifying the query because it has already been written. For example, using a PostgreSQL database, you may have a prepared statement that says
PREPARE finduser AS SELECT name FROM users WHERE id=$1. You could then execute it using
EXECUTE finduser('1234-5678-9098-7654'). Other databases may have slightly different syntaxes, but the general principle is the same - because you're inserting variables, not the full command, you can be more restrictive about what data is accepted and know that the query is not being altered.
- Allow lists and validation can be used together with the strategies listed above as an extra layer of protection (defense in depth).
XSS is often ranked as one of the top 10 web security threats.
There are several types of cross-site scripting attacks. The ones covered in this course are reflected XSS attacks, stored XSS attacks, and DOM-based XSS attacks.
Stored and DOM-based attacks use the same principle as reflected attacks, but are not immediate and do not originate from links.
Cross-site scripting attacks are common, but there are ways to protect against them. A good place to start is by mapping data passageways and exposure points to understand where your site could be vulnerable to an XSS attack. Pay particular attention to areas where data is outputted (as this is where the script will do the most damage), but its important to also look at areas where data is inputted, transferred, and stored to ensure that you're seeing the complete picture. Once you've determined where your website and data is vulnerable, protect your data by following best practices:
- Validate that all data matches expectations. Use allow lists (when possible) to confirm that the data is allowed - for example, if your user is inputting a PIN, you may want to validate that the data is a four-digit number, no more, no less, and no other types.
- Sanitize all data before output. The exact sanitization you use will differ from case to case, but your language of choice should have sanitization options.
HTTPOnlycookies. These cookies protect the cookie from being accessed by client-side scripts.
HTTPOnlyshould also be used on cookies used for session management.
- Define a content security policy (CSP). A CSP provides instructions to the browser about what types of resources can be used and where those resources can come from. Best practice is to send the policy in the header of an HTTP response. The CSP is essentially an allow list for the browser, telling it what content is allowed on the page and what content is not allowed.
A cross-site request forgery attack (CSRF) occurs when an attacker tricks a user's browser into sending a request to a different site. A CSRF attack is not dependent on a user clicking a link, rather, the URL for the request is in the HTML of the page (often the
src attribute of an image), and when the page loads, a request is made to that URL. The user may see a broken image link, but would not know that the forged request was sent.
A CSRF attack could also take advantage of a user's logged-in state on a website. If a CSRF attack sends a request to a website where the user is logged in, the request may include their authenticated status and could give the attacker access to their account. Attackers generally do not see the direct results of the attack (they don't see the user's browser making the request), but they may see an indirect result, depending on what the attack was used for (for example, an attack that made fraudulent votes in a poll would lead the attacker to see an increase in votes, but they wouldn't see each individual vote happen).
There are several ways to protect against CSRF attacks:
- The simplest defense against a CSRF attack is to limit what requests can be made from a page. A page that is just loading data without submitting anything should only allow
POSTrequests should be allowed only on pages that are submitting forms or performing other actions that make changes.
- A more effective defense is to use CSRF tokens. A token can be generated and stored in the user's session data, and when the user submits a form, verify that the token is authentic before allowing the request. If the token sent with the request does not match the stored token, then the request is rejected. However, if your site is subject to XSS attacks, this defense would not work, as the attacker could be able to access the token.
- Other defenses that can be used as part of defense in depth include matching the referrer in a form submission, setting a custom header in the XMLHttpRequest and verifying that the header is present, or using the SameSite cookie attribute (which is not fully supported in all browsers).
- For very sensitive actions, such as changing usernames and passwords, the best protection is to require a second action to confirm the change, which could be a confirmation page (something you often see in online shopping), a CAPTCHA image, or re-authentication, all of which a CSRF attack could not respond to.
These attacks are just a small number of potential attacks that could be part of your threat model. In my next article, I will discuss a number of additional threats to consider, as discussed in the last portion of this course.