VAITP Dataset

← Back to the dataset

CVE-2026-32714

SQL Injection in the SciTokens KeyCache allows for arbitrary SQL execution.

  • CVSS 9.8
  • CWE-89
  • Input Validation and Sanitization
  • Remote

SciTokens is a reference library for generating and using SciTokens. Prior to version 1.9.6, the KeyCache class in scitokens was vulnerable to SQL Injection because it used Python's str.format() to construct SQL queries with user-supplied data (such as issuer and key_id). This allowed an attacker to execute arbitrary SQL commands against the local SQLite database. This issue has been patched in version 1.9.6.

CVSS base score
9.8
Published
2026-03-31
OWASP
A03 Injection
Orthogonal defect classification
Checking
Code defect classification
Incorrect Algorithm
Category
Input Validation and Sanitization
Subcategory
SQL Injection
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
SciTokens
Fixed by upgrading
Yes

Solution

Upgrade SciTokens to version 1.9.6 or later.

Vulnerable code sample

import sqlite3
import os

class KeyCache:
    """
    A persistent, on-disk cache for public keys.
    This is a simplified representation of the vulnerable class.
    """

    def __init__(self, cache_path=":memory:"):
        """
        Initializes the key cache.
        :param str cache_path: Path to the SQLite3 database file.
        """
        self._conn = sqlite3.connect(cache_path)
        self._setup_db()

    def _setup_db(self):
        """
        Sets up the database table if it doesn't exist.
        """
        cursor = self._conn.cursor()
        try:
            cursor.execute(
                "CREATE TABLE IF NOT EXISTS keys "
                "(issuer TEXT, key_id TEXT, public_key TEXT, "
                "PRIMARY KEY (issuer, key_id))"
            )
        finally:
            cursor.close()

    def add_key(self, issuer, key_id, public_key):
        """
        Adds a key to the cache.
        """
        cursor = self._conn.cursor()
        try:
            cursor.execute(
                "INSERT INTO keys (issuer, key_id, public_key) VALUES (?, ?, ?)",
                (issuer, key_id, public_key),
            )
            self._conn.commit()
            return True
        except sqlite3.IntegrityError:
            return False  # Key already exists
        finally:
            cursor.close()

    def get_key(self, issuer, key_id):
        """
        Retrieves a key from the cache.
        This method is vulnerable to SQL injection.
        """
        cursor = self._conn.cursor()
        try:
            # VULNERABLE LINE: Using str.format() to build the query
            query = "SELECT public_key FROM keys WHERE issuer = '{}' AND key_id = '{}'".format(
                issuer, key_id
            )
            cursor.execute(query)
            result = cursor.fetchone()
            if result:
                return result[0]
            return None
        finally:
            cursor.close()

def demonstration():
    """
    Demonstrates the SQL injection vulnerability.
    """
    print("Setting up the KeyCache and adding a legitimate key.")
    key_cache = KeyCache()
    legit_issuer = "https://demo.scitokens.org"
    legit_key_id = "key-a2"
    legit_public_key = "SAMPLE_PUBLIC_KEY_DATA"
    key_cache.add_key(legit_issuer, legit_key_id, legit_public_key)
    print(f"Added key for issuer '{legit_issuer}' with key_id '{legit_key_id}'.\n")

    # Attacker crafts a malicious key_id
    # The payload closes the string literal for key_id, then uses OR 1=1 to make
    # the WHERE clause true for any row, and -- comments out the rest of the query.
    malicious_key_id = "' OR 1=1 --"
    
    print(f"[*] Attacker attempts to retrieve a key using a malicious key_id:")
    print(f"    Issuer: {legit_issuer}")
    print(f"    Key ID: {malicious_key_id}\n")

    # The vulnerable get_key method is called
    retrieved_key = key_cache.get_key(legit_issuer, malicious_key_id)

    if retrieved_key:
        print(f"[+] Vulnerability Exploited! The first key in the database was returned:")
        print(f"    {retrieved_key}")
        # In a real scenario, the attacker might not know the key_id, but can
        # still retrieve a valid key's data by bypassing the intended logic.
    else:
        print("[-] Exploit failed.")

if __name__ == "__main__":
    demonstration()

Patched code sample

import sqlite3
import os

# This code represents a simplified version of the SciTokens KeyCache class
# to demonstrate the fix for the SQL injection vulnerability.
# The original vulnerability (CVE-2024-32714, not CVE-2026-32714) was due to using
# string formatting (str.format()) to build SQL queries.
# The fix involves using parameterized queries, which is the standard,
# safe way to pass data to SQL queries in Python.

DB_FILE = "keycache_fixed.db"

class PatchedKeyCache:
    """
    A patched version of the KeyCache demonstrating the fix.
    """
    def __init__(self, db_path):
        self._conn = sqlite3.connect(db_path)
        self._setup_database()

    def _setup_database(self):
        """Initialize the database schema."""
        cursor = self._conn.cursor()
        cursor.execute(
            """
            CREATE TABLE IF NOT EXISTS scitokens_key_cache (
                issuer TEXT,
                key_id TEXT,
                key TEXT,
                PRIMARY KEY (issuer, key_id)
            )
            """
        )
        # Add some sample data for demonstration
        try:
            cursor.execute(
                "INSERT INTO scitokens_key_cache (issuer, key_id, key) VALUES (?, ?, ?)",
                ("https://demo.scitokens.org", "key-rs256", "SAMPLE_KEY_1")
            )
            cursor.execute(
                "INSERT INTO scitokens_key_cache (issuer, key_id, key) VALUES (?, ?, ?)",
                ("https://another.issuer.com", "key_abc", "SAMPLE_KEY_2")
            )
            self._conn.commit()
        except sqlite3.IntegrityError:
            # Data already exists
            pass

    def get_key(self, issuer, key_id):
        """
        Fetches a key from the cache using a patched, secure method.

        THE FIX:
        The vulnerable version of this function used Python's `str.format()` to
        construct the SQL query, like so:
        
        cursor.execute(
            "SELECT key FROM scitokens_key_cache WHERE issuer = '{0}' AND key_id = '{1}'".format(
                issuer, key_id
            )
        )
        
        This is vulnerable to SQL injection. The corrected version below uses
        parameterized queries (the '?' placeholders). The database driver
        safely handles the values, preventing them from being interpreted as
        SQL commands.
        """
        cursor = self._conn.cursor()
        
        # Patched code using parameterized query
        cursor.execute(
            "SELECT key FROM scitokens_key_cache WHERE issuer = ? AND key_id = ?",
            (issuer, key_id),
        )
        
        result = cursor.fetchone()
        return result[0] if result else None

    def close(self):
        """Close the database connection."""
        self._conn.close()


if __name__ == "__main__":
    # Ensure the database file doesn't exist before we start
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    cache = PatchedKeyCache(DB_FILE)

    # 1. Demonstrate a legitimate query
    print("--- 1. Legitimate Query ---")
    issuer_legit = "https://demo.scitokens.org"
    key_id_legit = "key-rs256"
    key = cache.get_key(issuer_legit, key_id_legit)
    print(f"Query for issuer='{issuer_legit}', key_id='{key_id_legit}'")
    print(f"Result: {key}\n")
    assert key == "SAMPLE_KEY_1"

    # 2. Demonstrate a failed SQL injection attempt on the patched code
    # The attacker provides a malicious key_id to try to bypass the WHERE clause.
    print("--- 2. SQL Injection Attempt (Patched) ---")
    issuer_malicious = "https://demo.scitokens.org"
    # This input would cause the vulnerable code to return all rows.
    key_id_malicious = "' OR '1'='1" 
    
    key = cache.get_key(issuer_malicious, key_id_malicious)
    print(f"Query for issuer='{issuer_malicious}', key_id='{key_id_malicious}'")
    print(f"Result: {key}\n")
    
    if key is None:
        print("SUCCESS: The injection was prevented. The query correctly found no key with the literal malicious ID.")
    else:
        print("FAILURE: The injection succeeded. The patch is ineffective.")

    # Clean up
    cache.close()
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

Payload

'; DROP TABLE keys; --

Cite this entry

@misc{vaitp:cve202632714,
  title        = {{SQL Injection in the SciTokens KeyCache allows for arbitrary SQL execution.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-32714},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-32714/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::