The 33th Dojo Challenge, Windows 12, invited participants to exploit a path traversal (CWE-22) vulnerability and read a file containing the challenge flag.
We are delighted to announce the winners of Dojo Challenge #33 below.
💡 Want to create your own monthly Dojo challenge? Send us a message on Twitter!
3 BEST REPORT WRITE-UPS
Congrats to Memset, Weac and Kix29 for the best write-ups 🥳
The swag is on its way! 🎁
Subscribe to our Twitter and/or LinkedIn feeds to be notified of upcoming challenges.
Read on to find out how one of the winners managed to solve the challenge.
The challenge
We asked you to produce a qualified report explaining the logic allowing exploitation, as set out by the challenge.
This write-up serves two purposes:
- To ensure contestants actually solved the challenge themselves rather than copy-pasting the answer from elsewhere.
- To determine contestants' ability to properly describe a vulnerability and its vectors within a professionally redacted report. This gives us invaluable hints on your unique talent as a bug hunter.
OVERALL BEST WRITE-UP
We want to thank everyone who participated and reported to the Dojo challenge. Many other high quality reports were submitted alongside those of the three winners. 😉
Below is the best write-up overall. Thanks again for all your submissions and thanks for playing!
Kix29‘s Write-Up
————– START OF Kix29‘s REPORT —————
DESCRIPTION
In cybersecurity, Path traversal occurs when an application uses external data (e.g., user input) to identify a file or directory located under a restricted root directory, but does not properly control the elements entered by users, resulting in the interpretation of the resolution of a path name to a location external to the restricted root directory.
In other words, exploiting this vulnerability allows an attacker to read files outside the current directory. This could lead to sensitive files being read.
EXPLOITATION
CODE ANALYSIS
Firstly, an application whose source code was accessible was identified. The purpose of this application was to enable users to display files within an HTML page via a form in which the file name was entered.
The first step in exploiting this application was to analyze the Python code.
The following lines provide an initial source of important information. The application's current directory is set to /tmp/app. This means that the files included in the application should be in this directory:
# Python code
import re, os
from urllib.parse import unquote
from jinja2 import Environment, FileSystemLoader
from urllib.request import urlopen
os.chdir("/tmp/app")
The section of code following the first 5 lines configures a Jinja2 environment for template rendering, using the templates directory as the template source, with an initial template named index.tpl. This means that it is probably via this file that the contents of the other included files will be displayed:
env = Environment(
loader = FileSystemLoader("templates"),
autoescape = True,
)
template = env.get_template("index.tpl")
The next part of the code indicates a function named validate. This function returns a variable of type boolean, and takes as parameter a variable with name f, corresponding to the file name specified by the user:
def validate(f) -> bool:
f = f.lower()
# "file:///" is to dangerous to use:
if f.startswith("/") or f.startswith("file:///"):
return False
# Filter localhost
elif ("127" in f) or ("localhost" in f):
return False
else:
return True
This function acts as an initial check on the data sent by the user:
- The instruction
f = f.lower()
converts all uppercase letters within the variable f to lowercase. For example, "ExampLe" will become "example".
Thereafter, various checks are made via conditions:
- The first condition
if f.startswith("/") or f.startswith("file:///")
detects whether the f variable begins with "/" or "file:///". If so, the function returns False. - The second condition
elif (“127” in f) or (“localhost” in f)
is a second check to detect whether the f variable contains the strings “127” or “localhost”. This blocks the possibility of including files using a local address such as 127.0.0.1. If either of these two patterns is detected, the function also returns False. - Finally, if all conditions are not met, i.e. if the f variable does not begin with “/” or “file:///” and does not contain “127” or “localhost”, then the function returns True.
The following code then forms the core of the application, enabling the reading of local files specified by the user:
filename = re.sub(r'\s+', '', unquote("USER_INPUT"))
errMsg = ""
content = ""
if len(filename) > 0:
while "../" in filename:
filename = filename.replace("../", "")
if validate(filename) == True:
try:
if re.search(r'^[a-zA-Z]+://', filename):
content = urlopen(filename, timeout=2).read().decode('utf-8')
else:
with open("files/"+filename, 'r') as f:
content = f.read()
except:
errMsg = "File not found"
else:
errMsg = "Access denied"
else:
with open("files/welcome.txt", 'r') as f:
content = f.read()
The first line of this block of code instantiates a variable named filename, with the value of the user input.
Two variables are then defined, errMsg and content. Their values are empty strings.
3 nested conditions instructions are then implemented:
- The code
if len(filename) > 0:
checks that the length of the variable filename is strictly positive. If this condition is not met, the default file displayed is welcome.txt, located in the files directory, as indicated by the following lines in the code block :
else:
with open(“files/welcome.txt”, 'r') as f:
content = f.read()
- The welcome.txt file is therefore located in /tmp/app/files/.
If the condition is met, a filter set up by the while loop detects the presence of the string “../” in the filename variable. As long as this string is present, all occurrences of “../” in filename will be replaced by an empty string. This reduces the risk of including files. For example, “../../../secret.txt” would become “secret.txt”. - Then, the second condition uses the previously analyzed function, validate, taking the filename variable as argument. This condition is used to validate that this variable respects all the prerequisites previously mentioned in the analysis of this function. If all conditions are met, the function returns True. If this is the case, the application will try to parse the name of the filename variable using a regular expression.
If this second nested condition is not met, the errMsg variable will be set to Access denied. This value will then be displayed within the application. - The third and final nested condition provides additional control over the filename variable, via a regular expréssion. The regular expression in question is:
^[a-zA-Z]+://
. It imposes a certain format on the filename variable by detecting the presence of alphabetic characters at the beginning of the value (whether upper or lower case). It then checks for the presence of “://”.
If the regular expression is satisfied, the application will attempt to read the file. On the other hand, if the regular expression is not satisfied, the file read will be “files/**filename”. If the application is unable to read the file, the error message displayed will be “File not found”.
Finally, the last part of the code is as follows:
if "_AV_" in filename:
content = ""
errMsg = "Blocked by antivirus software"
print( template.render(output=content, errMsg=errMsg) )
This code adds a condition to check whether the string “AV” is contained in the value of the filename variable.
Finally, the last line of code displays the contents of the read file, or various error messages if all conditions are not met.
CODE EXPLOITATION
To exploit the application, the aim here is to attempt to read a file external to the authorized directory path.
To do this, the first check to be satisfied is the absence of “/” or “file:///” at the beginning of the input. As this condition is too permissive, the use of the file:// wrapper is authorized. This will enable us to read a file.
Secondly, the application checks for the presence of “127” and “localhost”. Fortunately for us, it's possible to bypass these protective measures by using a octal representation of the 127.1 address (equivalent to localhost): 0177.1.
The file path would then be file://0177.1.
Here, there's no need to use “../”. Any file can then be read. Here, a secret is present in /tmp/secrets/flag.txt.
However, a problem arises when the file flag.txt tries to be included: a blacklist is set up and replaces the string "flag" with "_AV_", which doesn't satisfy the last condition of the code:
To bypass this protection, character encoding can be used. This would result in a final load of :
file://0177.1/tmp/secrets/f%6c%61%67.txt
It is now possible to include and read any file.
POC
Here, the file /tmp/secrets/flag.txt will be displayed via the following payload:
file://0177.1/tmp/secrets/f%6c%61%67.txt
The secret is FLAG{Y0u_C4n_Acc33ss_F1l3s_Th1s_w4y?!}.
BEYOND THE CODE
When analyzing the code, we noticed that the template used was "index.tpl" located in "/tmp/app/templates/".
Here, if we try to include this file, the code will be displayed:
It's possible to notice that a file is included via the {{- output -}}
placeholder. This is how the contents of the file are displayed.
RISKS
An attacker with the ability to exploit this vulnerability could perform the following actions:
- Unauthorized access to sensitive files: if a web application is vulnerable to file inclusion or path traversal, the attacker could gain access to sensitive files such as configuration files, database files or password files. This could enable the attacker to compromise the security of the application or server.
- Violation of privacy: if a web application handles personal or sensitive data, a file inclusion or path traversal vulnerability could enable the attacker to access this data and use it for malicious purposes, such as identity theft or fraud.
Finally, in the worst case scenario, it would be possible to include remote files containing malicious code, which would be executed by the application, leading to its compromise.
REMEDIATION
To remedy this vulnerability, various control measures could be implemented:
- File rights configuration: it is necessary to configure sufficient rights on files so that the application cannot access them. This can be done through read, write and execute rights.
- Use whitelists of authorized files: instead of authorizing access to all files on the system, it is preferable to define a whitelist of authorized files, and only authorize access to these files.
- Reinforcement of controls: it is recommended to implement more controls on user input. This could involve detecting the presence of wrappers, if this is not necessary. Last but not least, character encoding controls should also be tightened.
Of course, these controls will have to be analyzed beforehand to avoid impacting the application's operation.
REFERENCES
- https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/07-Input_Validation_Testing/11.1-Testing_for_Local_File_Inclusion
- https://cwe.mitre.org/data/definitions/22.html
- https://portswigger.net/web-security/file-path-traversal
————– END OF Kix29‘s REPORT —————