One of the most common vulnerabilities in a web application is misconfigured authentication, including code errors that allow unauthenticated people to query data. You might think that your application is secure, but every application has bugs, especially as the application codebase continues to expand.
Open API endpoints and misconfigured access controls are two common reasons you might unknowingly disclose sensitive information to attackers. Other examples include a vulnerability named Insecure Direct Object Reference (IDOR), path traversal, and misconfigured Cross-Origin Resource Sharing (CORS). For a large application, it can take several hours to go through all your pages and penetration test it. Small applications can have the same issues, but you can check your own application with small Python scripts. While it might not be a thorough test, you can catch obvious vulnerabilities that might not be so obvious to you, but are easy for attackers to discover.
This article will show you three easy ways to test your application. Whether you’re a new cybersecurity analyst or a business owner with some scripting experience, this article will give you the Python code and explanation you need to test your web pages. If you are a business owner, you’ll need a basic understanding of code structures and the way code interacts with web elements (e.g., URLs and query strings) to follow along. Coders will find these scripts simple enough, but they can be expanded to scan several pages.
:::info
Note: System files in code examples are intentionally misspelled to avoid XSS blocks on Hackernoon.
:::
IDOR Vulnerabilities
Before explaining IDOR vulnerabilities, you need a brief explanation of query strings. When you see a URL with an extension after a question mark character, you have a query string variable. The query string variable and its value are used as input for the web application. As with any other user-generated input, it opens up vulnerabilities if not processed correctly. By “correctly,” I mean validating input to ensure that it’s not malicious.
Here is an example of a simple query string variable attached to a URL:
pinkhatcode.com?clientid=999
Everything after the question mark is a part of the query string. In this example, the variable name is “clientid” and the value is “999”.
In this example, the value is a simple number that is unlikely to cause issues, but what happens if an attacker changes the “999” value to “100”? Does your application properly reject the wrong clientid value to ensure that the current user is authorized to view the response? You could type in values yourself, or you can use a Python script to iterate through all possible clientid values.
import requests
import time
clientId = 1
iterations = 100
# Loop through client IDs as many times as iterations specifies (100 in this example)
for i in range(iterations):
print ("Checking clientId " + str(clientId))
url = "https://your-test-url.com/index?client_id=" + str(clientId)
# Get a response from the URL and parameter. I want to see if it returns a
# 200OK response, which indicates we have an IDOR vulnerability
response = requests.get(url)
# Print the response to the console with the server response
print (url + " " + str(response.status_code))
clientId += 1
# Pause the iteration by 10 seconds to avoid getting blocked for DoS
time.sleep(10)
In the above script, client IDs from 1 to 100 are tested. You could also use alphabetic or alphanumeric values to ensure that your application handles unexpected input. For example, if the application only uses numeric values and an alphanumeric value is sent to it, the code should handle it without crashing. An application crash can sometimes display sensitive information about the backend system.
IDOR vulnerabilities can result in divulging sensitive information, crash your application, or cause data corruption in your database. They are the “low hanging fruit” of penetration testing, but they are also common and should be remediated quickly.
I get more in depth on IDOR vulnerabilities and more Python scripts in my book The OWASP Top 10 Handbook: Hacking Broken Access Controls (with practical examples and code).
File Traversal and Query Strings
Some applications generate files and then let users download them. For example, your insurance company lets you download cards to print at home to prove you’re insured. Other applications let you download a CSV file after generating a list of data items. After the file is generated in any process, the web application retrieves it from a directory on the web server. The file name and directory might be in a query string variable, so again it’s user-generated input that must be validated.
I’ve already briefly explained query string variables, but here is an example of a URL with a query string variable pointing to a PDF file for download:
pinkhatcode.com/index?file=myinsuranceinfo.pdf
The URL looks innocent enough, but what happens when an attacker points the “file” query string variable to another location on your server. Let’s say the attacker uses the following URL (file misspelled intentionally to pass Hackernoon XSS protections):
pinkhatcode.com/index?file=../../../etc/passd
n The above URL points to a password file. Let’s say that you don’t have proper access controls on this directory and file. Now, the attacker has your password file.
Let’s look at another example (file misspelled intentionally to pass Hackernoon XSS protections):
pinkhatcode.com/index?file=......windowswinn.ini
The win(.)ini file holds important system information for the Windows operating system. Without access controls, an attacker could get important system information to launch additional attacks.
You can test for this vulnerability using a Python script, and here is an example:
import requests
# Open file of URLs to test
fileURLs = open("inputfiles/urls.txt", "r")
# Open file with testing input
fileInput = open("inputfiles/fileinput.txt", "r")
# Loop through each URL and then for each URL loop through
# the testing inputs
for url in fileURLs:
for input in fileInput:
testUrl = url + input
print(testUrl)
response = requests.get(testUrl)
print (response.text)
The above script opens a text file with files to test using traversal methods. I cover how to find these open files using Google and the types of files to protect in my book, but let’s assume there are dozens of files stored in the file “urls.txt”. The Python script iterates through each of these files to determine if they can be downloaded.
Unauthenticated Access on API Endpoints
Having an API is great for monthly income and to gain popularity for your brand, but an API can also open vulnerabilities. API endpoints can be the perfect opening for a data breach. Without authentication controls on your endpoints, you could be disclosing sensitive customer information without ever knowing it.
API endpoint vulnerabilities can be similar to query string vulnerabilities (IDORs), but you can introduce other types like SQL injection. For this section, I’ll stick to simple authentication vulnerabilities. Let’s say that you have an endpoint to retrieve orders for a specific customer. The endpoint request might look like the following:
pinkhatcode.com/api/orders/345
The API endpoint retrieves information for order number 345. Orders, as you can guess, are tied to customers. You wouldn’t want to retrieve order information for any other customer, especially the public internet. The API endpoint should have authentication and authorization validation before retrieving order information. To verify that your endpoint is secure, you can attempt to retrieve orders for clients without authentication.
The following is an example of a Python script that attempts to retrieve order information for clients with IDs 1 to 100.
import requests
import time
clientId = 1
iterations = 100
# Loop through number client IDs as many times as the variable “iterations”
# specifies (1 to 100 in this example)
for i in range(iterations):
print ("Checking clientId " + str(clientId))
url = "https://your-test-url.com/api/orders/" + str(clientId)
# Get a response from the URL and parameter. We assume that the
# response is JSON and the authentication message is in the 'Message'
# element
response = requests.get(url)
statusMessage = str(response['Message'])
# Print the response to the console with the server response
print (clientId + " " + statusMessage)
clientId += 1
time.sleep(10)
The above example is just for customer orders, but you would want to run this type of script on all your API endpoints that should be protected behind authentication and authorization. To do this, you need to dig into your API documentation, codebase, or rely on other scripted methods to discover endpoints. An attacker would perform the latter (e.g., fuzzing) to discover endpoints you might not even know about.
If any of these requests return an order without authentication, you have an API vulnerability. Remediating this vulnerability requires backend code changes to the API, and be aware that if the API is already public that you could already disclose sensitive data to an attacker.
Where to Go from Here?
This article covers some basic ways even a non-technical person can penetration test their application, but your application still needs a more thorough review. You can either find an automated solution that scans code or hire a penetration tester to dig deep into the application code to find any vulnerabilities.
If you’d like to get more scripts and get more information about the security of your web applications, check out my book on access control vulnerabilities with sample scripts you can use to find them.
