Skip to content

DotPlant2 installer completion can be triggered without installer step/session guard #402

Description

@fa1c4

1. Bug Topic

DotPlant2 installer completion endpoint writes installed.mark without verifying installer workflow state, request method, authentication, or CSRF protection.

Suggested title:

InstallerController::actionComplete missing installer step guard allows unauthenticated installed.mark write

2. Release Version / Commit Hash / Affected Range

Confirmed affected:

DevGroup-ru/dotplant2 analysed InvAudit snapshot:
0590_DevGroup-ru__dotplant2_minimal
run: 20260625-062245-8a383ec8

The public master branch contains the same vulnerable source pattern at the time of report preparation.

Affected range should be treated as:

All DotPlant2 revisions exposing installer.php where InstallerController::actionComplete()
writes @app/installed.mark without verifying installer workflow/session state.

The GitHub repository currently has no published releases, so exact release boundaries should be confirmed by maintainers.

3. Bug Type

Unauthenticated installer state change
Installer workflow bypass
Pre-install denial of service

4. CWE

Primary:

CWE-306: Missing Authentication for Critical Function

Related:

CWE-862: Missing Authorization
CWE-352: Cross-Site Request Forgery
CWE-841: Improper Enforcement of Behavioral Workflow

5. Bug Summary

InstallerController::actionComplete() writes 1 to @app/installed.mark and renders the installer completion page. The installer application disables CSRF validation in application/config/installer.php. The only relevant installer filter checks whether the marker file already contains 1; it does not verify that the requester completed the earlier installation steps.

An unauthenticated remote user can directly request the completion route while the application is not installed and force DotPlant2 into an installed state.

6. Root Cause

The completion step trusts route reachability as proof that installation has completed. It does not enforce:

1. authenticated installer session;
2. previous-step completion state;
3. one-time installer token;
4. POST-only method;
5. CSRF validation.

Affected code:

// application/modules/installer/controllers/InstallerController.php
public function actionComplete()
{
    file_put_contents(Yii::getAlias('@app/installed.mark'), '1');
    return $this->render('complete');
}
// application/config/installer.php
'request' => [
    'cookieValidationKey' => 'INSTALLER_COOKIE',
    'enableCsrfValidation' => false,
],
// application/modules/installer/components/InstallerFilter.php
if (file_get_contents(Yii::getAlias('@app/installed.mark'))==='1') {
    throw new ForbiddenHttpException("DotPlant2 is already installed");
}

7. Attack Preconditions

1. DotPlant2 is deployed but installation is not complete.
2. application/web/installer.php is reachable over HTTP.
3. The web server process can write application/installed.mark.
4. The attacker can send a GET request to the completion route.

No DotPlant2 account or admin authentication is required.

8. Impact Analysis

An attacker can create a pre-install denial of service by prematurely setting:

application/installed.mark = 1

This prevents legitimate installer access because InstallerFilter treats the application as already installed. The deployment can be left partially configured until an operator manually removes or repairs the marker file.

The impact is mainly setup integrity and availability. Severity is likely Low to Medium depending on whether the installer is exposed publicly during deployment.

9. Affected Code

application/web/installer.php
application/config/installer.php
application/modules/installer/components/InstallerFilter.php
application/modules/installer/controllers/InstallerController.php

Important lines from the analysed snapshot:

application/config/installer.php:91-94
request.enableCsrfValidation = false

application/modules/installer/components/InstallerFilter.php:11-23
InstallerFilter only checks installed.mark and returns true otherwise.

application/modules/installer/controllers/InstallerController.php:268-273
actionComplete() writes @app/installed.mark = 1.

10. PoC

Standalone Docker PoC: https://github.com/fa1c4/security-advisories/tree/main/dotplant2/unauthenticated_remote_mark

Run:

unzip dotplant2_installer_complete_missing_installer_step_guard.zip
cd dotplant2_installer_complete_missing_installer_step_guard

docker build -t poc-dotplant2-installer-complete .
docker run --rm poc-dotplant2-installer-complete

The PoC sends a simulated unauthenticated request:

GET /installer.php?r=installer/installer/complete

It verifies that the vulnerable path writes installed.mark = 1 without authentication, CSRF, POST method enforcement, or installer-step state. The PoC also includes a patched control path showing the same request being rejected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions