CVE-2023-48712: Incorrect Authorization (CWE-863)
value: Warpgate is an open source SSH, HTTPS and MySQL bastion host for Linux. In affected versions there is a privilege escalation vulnerability through a non-admin user's account. Limited users can impersonate another user's account if only single-factor authentication is configured. If a user knows an admin username, opens the login screen and attempts to authenticate with an incorrect password they can subsequently enter a valid non-admin username and password they will be logged in as the admin user. All installations prior to version 0.9.0 are affected. All users are advised to upgrade. There are no known workarounds for this vulnerability.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 8.8
Published: 2023-11-24T17:15Z
Repository: https://github.com/warp-tech/warpgate
Language: Rust
Files changed: warpgate-protocol-http/src/api/auth.rs, warpgate-protocol-http/src/common.rs
Commit date: 2023-11-23 18:35:27 +0100
Commit message: fixed GHSA-c94j-vqr5-3mxr - privilege escalation during web auth

Diff: diff --git a/warpgate-protocol-http/src/api/auth.rs b/warpgate-protocol-http/src/api/auth.rs
index 6988bda..dbbe307 100644
--- a/warpgate-protocol-http/src/api/auth.rs
+++ b/warpgate-protocol-http/src/api/auth.rs
@@ -127,11 +127,11 @@ impl Api {
 
         let mut cp = services.config_provider.lock().await;
 
         let password_cred = AuthCredential::Password(Secret::new(body.password.clone()));
         if cp
-            .validate_credential(&body.username, &password_cred)
+            .validate_credential(&state.username(), &password_cred)
             .await?
         {
             state.add_valid_credential(password_cred);
         }
 
diff --git a/warpgate-protocol-http/src/common.rs b/warpgate-protocol-http/src/common.rs
index b7c9ab0..0056003 100644
--- a/warpgate-protocol-http/src/common.rs
+++ b/warpgate-protocol-http/src/common.rs
@@ -192,20 +192,24 @@ pub async fn get_auth_state_for_request(
         if !store.contains_key(&id.0) {
             session.remove(AUTH_STATE_ID_SESSION_KEY)
         }
     }
 
-    match session.get_auth_state_id() {
-        Some(id) => Ok(store.get(&id.0).ok_or(WarpgateError::InconsistentState)?),
-        None => {
-            let (id, state) = store
-                .create(None, username, crate::common::PROTOCOL_NAME)
-                .await?;
-            session.set(AUTH_STATE_ID_SESSION_KEY, AuthStateId(id));
-            Ok(state)
+    if let Some(id) = session.get_auth_state_id() {
+        let state = store.get(&id.0).ok_or(WarpgateError::InconsistentState)?;
+
+        let existing_matched = state.lock().await.username() == username;
+        if existing_matched {
+            return Ok(state);
         }
     }
+
+    let (id, state) = store
+        .create(None, username, crate::common::PROTOCOL_NAME)
+        .await?;
+    session.set(AUTH_STATE_ID_SESSION_KEY, AuthStateId(id));
+    Ok(state)
 }
 
 pub async fn authorize_session(req: &Request, username: String) -> poem::Result<()> {
     let session_middleware: Data<&Arc<Mutex<SessionStore>>> =
         <_>::from_request_without_body(req).await?;

Vulnerable code:             .validate_credential(&body.username, &password_cred)
    match session.get_auth_state_id() {
        Some(id) => Ok(store.get(&id.0).ok_or(WarpgateError::InconsistentState)?),
        None => {
            let (id, state) = store
                .create(None, username, crate::common::PROTOCOL_NAME)
                .await?;
            session.set(AUTH_STATE_ID_SESSION_KEY, AuthStateId(id));
            Ok(state)
Fixed code:             .validate_credential(&state.username(), &password_cred)
    if let Some(id) = session.get_auth_state_id() {
        let state = store.get(&id.0).ok_or(WarpgateError::InconsistentState)?;

        let existing_matched = state.lock().await.username() == username;
        if existing_matched {
            return Ok(state);

    let (id, state) = store
        .create(None, username, crate::common::PROTOCOL_NAME)
        .await?;
    session.set(AUTH_STATE_ID_SESSION_KEY, AuthStateId(id));
    Ok(state)