Challenge 2: Directory Traversal

As mentioned in the previous section, we'll be creating a challenge focused around directory traversal. To complete this challenge, the attacker must access one of the Foundation's secret pages, containing all of the safehouses and facilities that they own.

What is directory traversal?

A directory traversal (or path traversal, as OWASP calls it) attack attempts to access files and directories that are stored outside of the web root folder. These files can contain source code, database schemas or even password files.

Directory traversal is performed by manipulating variables to include parent directory references (the dot-dot-slash "../" characters), or absolute file references (e.g. "~/etc/passwd").

Example of a directory traversal attack

http://some-site.com/get-file?file=rules.html -- A legitimate URL
http://some-site.com/get-file?file=../../../../../etc/passwd -- An exploit, with parent directory references
http://some-site.com/get-file?file=/etc/passwd - Same exploit, but with absolute path

Depending on how the query is parsed, you may be able to insert files and scripts from another website, to run malicious code

Creating the vulnerable system (and the vulnerability)

To start, lets create a simple page to indicate to the attacker that they have accessed the correct page

<-- \templates\protected\facilities\earth.html -->
<-- This is just a temporary placeholder page while we develop the challenge, you can edit this however you like -->
{% extends 'base.html' %}

{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='/css/facilities.css') }}">
{% endblock %}

{% block title %}Facilities and Safehouses{% endblock %}


{% block content %}
secret page
{% endblock %}


{% block scripts %}
{% endblock %}

To make this challenge work, we'll have to add some code to our routes.py script

# core/routes.py
@app.route("/query", methods=["POST"])
def query():
    if request.method == "POST":
        page = request.form["page"]
        if page:
            return render_template(page)

We can check that our page works by starting up our Flask server and curling the page

Starting up the Flask server
Curling the query endpoint

On paper, this is the end of the challenge guide. We have an endpoint that accepts user input, and opens the respective page on it.

However, there isn't really a way for the attacker to figure out that there is a query endpoint, outside of pure brute forcing of the URL. To fix this, we can rewrite the buttons in our index page to query the new endpoint.

<div class="col-sm-4 project-1 project-content">
    <p class="project h6">
                The Sanctuary super-fleet, launched in colaboration with the
                Trans-Supercluster Research and Expeditions Committee (TSREC), spearheads Foundation operations with its
                flagship supercarrier and support dreadnoughts and carriers. <br>
        <button id="proj-1" class="btn btn-secondary m-3 rounded learn-more">Learn More</button>
    </p>
</div>
<-- Repeat for the other buttons in templates\index.html -->
...
<script src="{{ url_for('static', filename="/js/query.js") }}"></script>
# core/routes.py
@app.route("/query", methods=["GET"])
def query():
    if request.method == "GET":
        page = request.args.get("page")
        if page:
            try:
                return render_template(page)
            except exceptions.TemplateNotFound as e:
                referrer = request.referrer
                return redirect(referrer)
    return redirect("/")

We need to add a JS script too to enable the event listeners on the buttons

// static/js/query.js
// Select all buttons with the class 'get-button'
const buttons = document.querySelectorAll('.learn-more');

// Add event listeners to each button
buttons.forEach(button => {
    button.addEventListener('click', function() {
        const page = button.getAttribute('id');
        console.log(page)

        const url = `/query?page=${encodeURIComponent(page)}`;

        // Redirect the user to the new URL
        window.location.href = url;
    });
});

// don't forget to import it
// use <script src="{{ url_for('static', filename="/js/query.js") }}"></script> to import

Performing our test in Burp Suite, we can see the URL that the button will query when you click on it

We can modify our page parameter to point to our protected page

Remember to URL encode!
Our response came back!

And there we have it! A basic directory traversal vulnerability that the attacker can exploit to gain access to the Foundation's top secret facilities. However, since the page cannot be discovered through normal manipulation of the URL endpoint, we'll have to implement some method of letting attackers know of this secret page.

One way we can do this is through the robots.txt page, which most websites have. To keep it brief, a robots.txt file tells web crawlers which pages and subdirectories they are allowed or not allowed to access. Typically, administrative pages will be added to this robots.txt and marked as "Disallow"

An example of a robots.txt file

To implement this, we can use static files and Flask's send_from_directory() function

<-- static\robots.txt -->
User-agent: *
Allow: /
Disallow: /admin/
Disallow: /protected/
Disallow: /protected/facilities
# core/routes.py
@app.route("/robots.txt")
def static_from_root():
    return send_from_directory(app.static_folder, request.path[1:])
curl the new endpoint to test that it is working

And we're done!

Protecting actual files

Unfortunately, we cannot be certain that the attacker will only attempt to access the "supposed" target. They may decide that they want to directly access our set of flags, perhaps stored in the root directory, or even our login credentials to access the server directly. To protect against this, we can do several things

The simplest method would be to sanitize the user input, to strip any parent directory references. To do so, we can add this code to our route

# core/routes.py
@app.route("/query", methods=["GET"])
def query():
    if request.method == "GET":
        page = request.args.get("page")
        if page:
            if ".." in page or "%2F" in page or page.startswith("/"):
                return redirect("/")
            try:
                return render_template(page)
            except exceptions.TemplateNotFound as e:
                referrer = request.referrer
                return redirect(referrer)
    return redirect("/")

This is a relatively simple sanitization method, that prevents the attacker from attempting directory traversal. There are possible workarounds to this, such as double URL encoding ("/" is encoded to "%2F", which is then encoded to "%252F"), so this is by no means bullet-proof.

Alternatively, you can set up a web root directory, which dictates which files can be accessed by the public, and keeping your sensitive files outside of the directory. However, we will not be going in depth on this. To learn more, you can refer to the following resource

Conclusion

This concludes the directory traversal challenge creation guide. In the next section, we'll be attempting to create a server-side template injection vulnerability, and attempt to get the environmental variables stored on the host server.

The code for this section can be found here

Last updated