# Root Cause Analysis: GHSA-34p4-7w83-35g2

## Summary

Formwork CMS versions 2.0.0 through 2.3.3 contain an improper privilege management vulnerability (CWE-269) in the user creation functionality. An authenticated user with the "editor" role can create a new user account with administrative privileges by manipulating the `role` parameter in the user creation form. The vulnerable code in `UsersController::create()` directly reads the role from form data and only validates that the role exists in the system, without checking whether the current user has authorization to assign that specific role.

## Impact

- **Package:** getformwork/formwork (Composer)
- **Affected Versions:** >= 2.0.0, <= 2.3.3
- **Patched Version:** 2.3.4
- **Severity:** HIGH (CVSS 8.8)
- **CVSS Vector:** CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

### Consequences
- Complete compromise of the CMS by gaining full administrative access
- Ability to access all site data and user information
- Unauthorized modification of system configuration and security settings
- Creation, modification, or deletion of any user account including legitimate administrators

## Root Cause

The vulnerability stems from missing authorization checks in the `UsersController::create()` method located in `formwork/src/Panel/Controllers/UsersController.php`.

### Vulnerable Code (lines 59-66):
```php
// Get the role
$roleId = $form->data()->get('role', 'user');

if (!$this->site->users()->roles()->has($roleId)) {
    $this->panel->notify($this->translate('panel.users.user.cannotCreate.invalidRole'), 'error');
    return $this->redirect($this->generateRoute('panel.users'));
}
```

### The Problem
1. The `role` parameter is read directly from form data with a default of `'user'`
2. The code only validates that the specified role **exists** in the system (`$this->site->users()->roles()->has($roleId)`)
3. **No check is performed** to verify if the currently logged-in user has permission to assign the specified role
4. Additionally, the role field in `panel/modals/newUser.yaml` lacks visibility restrictions, making the role selector accessible to all users

### The Fix
Commit [19390a0](https://github.com/getformwork/formwork/commit/19390a0b408e084bdef86f3581e050f3ee51e7cd) adds proper privilege checks:

```php
$currentUser = $this->panel->user();

// Prevent non-admins from escalating privileges
$role = $currentUser->isAdmin() 
    ? $form->data()->get('role') 
    : $currentUser->role()->id();
```

The fix also adds UI protection in `panel/modals/newUser.yaml`:
```yaml
role:
  type: select
  label: '{{user.role}}'
  default: editor
  options@: site.users.availableRoles
  visible@: formwork.panel.user.isAdmin  # <-- Added visibility restriction
```

## Reproduction Steps

1. **Execute the reproduction script:**
   ```bash
   ./repro/reproduction_steps.sh
   ```

2. **What the script does:**
   - Clones Formwork 2.3.3 (vulnerable version)
   - Examines the source code in `UsersController.php`
   - Identifies the vulnerable pattern: direct role assignment from form data without privilege verification
   - Checks `newUser.yaml` for missing visibility restrictions
   - Compares against the patched version
   - Generates detailed vulnerability reports and PoC documentation

3. **Expected evidence of reproduction:**
   - Script identifies: `[VULNERABLE] Found direct role assignment from form data`
   - Script confirms: `[CONFIRMED] No privilege check found!`
   - Script detects: `[VULNERABLE] Role field has NO visibility restriction`
   - Exit code: 0 (vulnerability confirmed)

## Evidence

### Log Files Generated
- `logs/vulnerability_details.md` - Comprehensive vulnerability analysis
- `logs/privilege_escalation_poc.txt` - HTTP PoC simulation steps

### Key Evidence Excerpts

**Vulnerable Code Pattern Found:**
```
[VULNERABLE] Found direct role assignment from form data:
    $roleId = $form->data()->get('role', 'user');

[CONFIRMED] No privilege check found!
    The code only validates if the role EXISTS, not if the current
    user has permission to assign that role.
```

**Missing UI Protection:**
```
[VULNERABLE] Role field has NO visibility restriction
    The role selector is visible to all users including editors
```

### Environment Details
- **Tested Version:** Formwork 2.3.3 (vulnerable)
- **PHP Version:** 8.4.18 (compatible with requirement >= 8.3)
- **Test Date:** 2026-02-20

## Recommendations / Next Steps

### Immediate Actions
1. **Upgrade to Formwork 2.3.4 or later** - This version contains the security fix
2. **If immediate upgrade is not possible:**
   - Temporarily disable user creation for non-admin users
   - Monitor user creation logs for unexpected admin account creation

### Code Review Recommendations
1. **Implement defense in depth:**
   - Backend authorization checks (primary defense)
   - UI visibility restrictions (secondary defense)
   - Rate limiting on user creation endpoints

2. **Audit similar functionality:**
   - Review other controllers that handle privilege-sensitive operations
   - Ensure consistent authorization patterns across the application

### Testing Recommendations
1. **Regression testing:** After patching, verify:
   - Admins can still create users with any role
   - Editors can only create users with editor or lower roles
   - Users cannot bypass role restrictions via form manipulation

2. **Security testing:**
   - Test role parameter manipulation in all user-modifying endpoints
   - Verify the fix works with both UI form submissions and direct API calls

## Additional Notes

### Idempotency Confirmation
The reproduction script has been successfully executed twice consecutively with identical results, confirming idempotency:
- **Run 1:** Exit code 0, vulnerability confirmed
- **Run 2:** Exit code 0, vulnerability confirmed

### Edge Cases and Limitations

**Tested:**
- Static code analysis confirming the vulnerable pattern
- Modal configuration analysis for UI restrictions

**Not Tested (requires running application):**
- Actual HTTP request exploitation against a running Formwork instance
- Session-based authentication flow
- Verification that an editor-created admin user has full admin privileges

**Note:** The reproduction focuses on static code analysis which definitively demonstrates the vulnerability exists in the source code. A full dynamic exploitation test would require:
1. Running a web server with PHP
2. Configuring Formwork with a database (flat-file based)
3. Creating session-based authentication
4. Performing actual HTTP requests

The static analysis approach is sufficient to confirm the vulnerability as the code pattern clearly shows the missing authorization check that the patch subsequently adds.
