Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save joswr1ght/7d1f6b5b99ddac910349c757e448da9e to your computer and use it in GitHub Desktop.

Select an option

Save joswr1ght/7d1f6b5b99ddac910349c757e448da9e to your computer and use it in GitHub Desktop.
Droppy File Sharing Software Installer Controller Interface Exposure

[CRITICAL] Installer endpoints reachable on a fully installed instance

Severity: Critical Confidence: High (HTTP 200 confirmed against http://localhost:8080/install) Category: Broken Access Control / Authentication Bypass

Affected locations:

  • Files/application/controllers/Install.php (entire controller; source is hex-obfuscated but logic is clear)
  • Files/application/controllers/Install.php step 4 — creates an admin account from POSTed email + password with no auth, gated only on $_SESSION['install'] == 1
  • Files/application/controllers/Install.php step 3 — concatenates $_SESSION['base_url'] and $_SESSION['purchase_code'] directly into a SQL UPDATE droppy_settings ... statement (second-order SQL injection)
  • Files/application/controllers/Install.php step 3 — runs run_sql_file(APPPATH.'install/updatev2.sql', $this->db) against the live database, which can clobber existing tables / settings

Preconditions:

  • The application has already been installed (Droppy is configured and admin/user accounts exist in the seeded DB).
  • An attacker reaches /install. No authentication is required.

Description

The installer controller is left in place after installation. There is no installed.lock, no is_installed setting check, and no .htaccess rule restricting /install/*. Anyone who can hit the application HTTP endpoint can reach the install wizard.

Step 1 sets $_SESSION['base_url'] and clears $_SESSION['install']. Step 2 verifies a "purchase code" by HTTP-POST'ing to https://api.proxibolt.com/.... In this Dockerized build that hostname is hard-redirected to 127.0.0.1 (see docker/entrypoint.sh), and AdminLib::callProxibolt() is patched to return false — so the upstream verification cannot succeed in this build. However, a real Droppy deployment that is not running the Proxibolt blackhole patch is fully exposed: a valid purchase code, a leaked one, or a vendor-side response of "1" lets the attacker proceed to step 3 / step 4.

Step 4, when reached, calls accounts->add() with a request-supplied email and password_hash($post['password'], PASSWORD_DEFAULT). There is no authentication check on the step4 action other than $_SESSION['install'] == 1. Successfully reaching step 4 grants the attacker an admin account they control.

Step 3 issues this raw query (de-obfuscated):

$this->db->query("UPDATE droppy_settings SET site_url = '" . $_SESSION['base_url']
                . "/', purchase_code = '" . $_SESSION['purchase_code'] . "' LIMIT 1;");

base_url is built from $_SERVER['HTTP_HOST'] and $_SERVER['REQUEST_URI'] in step 1 with no sanitization, and purchase_code is whatever the attacker submitted in step 2. Both flow into raw SQL. Even without reaching step 4, an attacker who reaches step 3 can corrupt the droppy_settings row or piggy-back additional SQL.

Step 3 also unconditionally calls run_sql_file(APPPATH.'install/updatev2.sql', $this->db) against the production database when droppy_settings already exists, which can rewrite settings on a live system.

The use of hex-encoded source ("\x42\101\x53\x50...") for every string in Install.php is consistent with vendor obfuscation, not with security hardening — the controller is still loaded and routable.

Vulnerable Code (de-obfuscated)

// Install.php :: step3()
public function step3() {
    if ($_SESSION['install'] != 1) { /* redirect to step2 */ die; }

    if (...) {
        if ($this->db->table_exists('droppy_settings')) {
            run_sql_file(APPPATH . 'install/updatev2.sql', $this->db);   // clobbers live schema
        } else {
            // fetch install SQL from Proxibolt and execute it raw
            $res = (array) json_decode($this->installlibrary->post_to_url($surl, [...]));
            if ($res['type'] == 'success') {
                $this->db->query($res['response']);                       // attacker-influenced if Proxibolt is MITM'd
                run_sql_file(APPPATH . 'install/droppy.sql', $this->db);
            }
        }
    }

    // <-- raw concatenation of session-controlled strings into SQL:
    $this->db->query("UPDATE droppy_settings SET site_url = '" . $_SESSION['base_url']
                   . "/', purchase_code = '" . $_SESSION['purchase_code'] . "' LIMIT 1;");
    header('Location: ' . $_SESSION['base_url'] . '/install/step4');
    die;
}

// Install.php :: step4()
public function step4() {
    if ($_SESSION['install'] != 1) { /* redirect to step2 */ }
    $post = $this->input->post(NULL, TRUE);
    if ($post) {
        $this->load->model('accounts');
        $data = ['email' => $post['email'],
                 'password' => password_hash($post['password'], PASSWORD_DEFAULT),
                 'ip' => ''];
        if ($this->accounts->add($data)) {                                // creates admin user
            header('Location: ' . $_SESSION['base_url'] . '/install/step5');
        }
    }
}

Impact

  • On a real (non-Dockerized) Droppy deployment where the Proxibolt verification endpoint is reachable and returns "1" for a known purchase code, an attacker can complete the install flow and add an arbitrary admin account without any prior authentication. From admin they can chain to RCE via the upload-background-with-arbitrary-extension and update-from-zip vectors documented in other findings.
  • Any attacker reaching step 3 can cause destructive schema changes by triggering run_sql_file('install/updatev2.sql') against production data.
  • SQL injection at the step-3 UPDATE droppy_settings query lets an attacker tamper with the global settings row (admin email, site_url, theme, recaptcha_secret, etc.) or stack additional SQL on databases where multi-statement is enabled.
  • Even on the Dockerized build (where Proxibolt is blackholed), the install controller still leaks installation state and is a clear "left in production" hardening failure.

Attack Scenario

  1. Attacker hits GET /install/step1 — sets $_SESSION['base_url'].
  2. Attacker hits POST /install/step2 with a purchase code. On any deployment where Proxibolt's install_verify.php returns 1, $_SESSION['install'] becomes 1.
  3. Attacker is redirected to /install/step3. The unconditional run_sql_file(updatev2.sql) and the concatenated UPDATE droppy_settings query execute against the production DB.
  4. Attacker hits POST /install/step4 with email=attacker@x&password=hunter2. A new admin row is inserted into droppy_accounts.
  5. Attacker logs in at /admin/login and pivots to RCE via finding 04 (background upload) or finding 06 (admin update flow).

Validation

# Confirm the installer is reachable post-install:
curl -s -o /dev/null -w "HTTP %{http_code}\n" "http://localhost:8080/install"
# Observed: HTTP 200 (form is rendered)

# In this build the Proxibolt host is blackholed to 127.0.0.1 so step2 cannot succeed,
# but the controller and routes are clearly live and would be exploitable on any deployment
# where api.proxibolt.com responds normally — the only thing standing between an
# unauth attacker and admin account creation is whether `post_to_url` returns "1".

References

  • CWE-1188: Initialization of a Resource with an Insecure Default
  • CWE-862: Missing Authorization
  • CWE-89: SQL Injection (the step-3 UPDATE)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment