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.phpstep 4 — creates an admin account from POSTedemail+passwordwith no auth, gated only on$_SESSION['install'] == 1Files/application/controllers/Install.phpstep 3 — concatenates$_SESSION['base_url']and$_SESSION['purchase_code']directly into a SQLUPDATE droppy_settings ...statement (second-order SQL injection)Files/application/controllers/Install.phpstep 3 — runsrun_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.
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.
// 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');
}
}
}- 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_settingsquery 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.
- Attacker hits
GET /install/step1— sets$_SESSION['base_url']. - Attacker hits
POST /install/step2with a purchase code. On any deployment where Proxibolt'sinstall_verify.phpreturns1,$_SESSION['install']becomes1. - Attacker is redirected to
/install/step3. The unconditionalrun_sql_file(updatev2.sql)and the concatenatedUPDATE droppy_settingsquery execute against the production DB. - Attacker hits
POST /install/step4withemail=attacker@x&password=hunter2. A new admin row is inserted intodroppy_accounts. - Attacker logs in at
/admin/loginand pivots to RCE via finding 04 (background upload) or finding 06 (admin update flow).
# 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".- CWE-1188: Initialization of a Resource with an Insecure Default
- CWE-862: Missing Authorization
- CWE-89: SQL Injection (the step-3 UPDATE)