Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-18110-PoC — Python PoC and Nuclei template exploiting CVE-2026-18110, an unauthenticated user enumeration flaw in Concrete CMS 9.0.0-9.5.2 via the user autocomplete endpoint. | Kitploit
Tools/GitHubGitHub/flenz00/cve-2026-18110-poc
ReconnaissanceVulnerability ScannersWeb Vulnerability ScannersVulnerability AnalysisExploitationInformation GatheringWeb SecurityPenetration TestingLearning & Education
GitHubflenz00/cve-2026-18110-poc

CVE-2026-18110-PoC

Python PoC and Nuclei template exploiting CVE-2026-18110, an unauthenticated user enumeration flaw in Concrete CMS 9.0.0-9.5.2 via the user autocomplete endpoint.

3 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository

CVE-2026-18110 — Concrete CMS Unauthenticated User Enumeration

Summary

Concrete CMS 9.0.0 through 9.5.2 does not perform an authorization check on the user selector autocomplete endpoint (/ccm/system/user/autocomplete), which backs the "Preview as User" panel and other user-selector components. This allows an unauthenticated remote attacker to enumerate the full internal user directory — including user IDs, usernames, and email addresses — without any valid session or credentials.

The vulnerability is a classic case of a CSRF token being (mis)used as an authorization control: the token proves request integrity but never answers the actual security question of whether the caller is permitted to query the user list.


Affected Versions

ProductAffectedFixed
Concrete CMS9.0.0 – 9.5.29.5.3

Vulnerability Details

Entry point

The Preview As User panel is registered in concrete/routes/panels.php under the base path /ccm/system/panels:

GET /ccm/system/panels/page/preview_as_user

Its controller (concrete/controllers/panel/page/preview_as_user.php) calls UserSelector::quickSelect() to render a <concrete-user-select> Vue component. Unlike its sibling method selectUser(), which correctly gates on canAccessUserSearch(), quickSelect() performs no permission check before minting and embedding a token:

// concrete/src/Form/Service/Widget/UserSelector.php (vulnerable — 9.5.2)
public function quickSelect(string $inputName, $userID = null, array $args = []): string
{
    $userSelectInstance = $userSelectInstanceFactory->createInstance($labelFormat, $includeAvatar);
    // No permission check here — token is rendered unconditionally
    $html = <<<EOL
    <concrete-user-select
        access-token="{$userSelectInstance->getAccessToken()}"
        label-format="{$labelFormat}"
        :include-avatar="{$includeAvatar}"
        ...
    EOL;
    return $html;
}

The server-rendered HTML response therefore exposes a valid access-token to any visitor — including unauthenticated ones — who can reach this route.

Token format

The access-token value has the format:

{unix_timestamp}:{md5_hash}

Where the hash is computed server-side as:

md5( timestamp : userID : action : pepper )
  • timestamp — UNIX time at generation, sent in plaintext as the token prefix.
  • userID — the requester's user ID at generation time; 0 for unauthenticated visitors.
  • action — the string user_select:format:{labelFormat}:avatar:{includeAvatar}, where both values are attacker-supplied via query parameters.
  • pepper — a 64-character random secret generated once at install time and stored in application/config/generated_overrides/concrete.php.

Tokens are valid for 24 hours and are fully replayable within that window — there is no single-use/nonce enforcement. There is also no lower-bound timestamp check, making the freshness check one-sided.

Exploitation chain

1. GET /ccm/system/panels/page/preview_as_user
        ↓
   Server responds with HTML containing:
   <concrete-user-select
       access-token="1738012345:a1b2c3d4e5f6..."
       label-format="auto"
       :include-avatar="true"
       ...>

2. POST /ccm/system/user/autocomplete
   Body: accessToken=1738012345:a1b2c3d4e5f6...
         &labelFormat=auto
         &includeAvatar=true
         &query=a
        ↓
   Server responds with full user list:
   [
     {"id": 1, "primary_label": "admin", "secondary_label": "[email protected]"},
     {"id": 6, "primary_label": "m.rossi",  "secondary_label": "[email protected]"},
     ...
   ]

The checkAccess() call inside view() recomputes the hash using the current request's uID (0 for guests) — which matches perfectly because the token was also generated as uID=0. The check passes, and the full user directory is returned.

Root cause (CWE-862)

The canAccess() / checkAccess() gate on the autocomplete endpoint validates token integrity (not forged, not expired) but never validates authorization (is this caller permitted to search users?). CSRF token validity is not a substitute for an authorization check. Compared with getSelectedUsers() in the same controller, which correctly calls Checker::canViewUser() per result, view() has no equivalent check.


Proof of Concept

A Python script + Nuclei detection template are included in this repository. The Python script can be used only against a single URL. If you want to test for multiple targets simultaneously, use nuclei instead. Both automate the two-step chain described above and matches on confirmed user data in the Step 2 response.

python3 CVE-2026-18110.py https://target.example.com
nuclei -t CVE-2026-18110.yaml -u https://target.example.com

Intended for use against systems you are authorized to test only.


Remediation

Upgrade to Concrete CMS 9.5.3 or later. The fix introduces an authorization check at the token-issuance stage (quickSelect()) consistent with the existing canAccessUserSearch() gate already present in selectUser(), and adds an equivalent permission check inside view() of the autocomplete controller, mirroring the Checker::canViewUser() pattern already correctly used by getSelectedUsers().

If immediate upgrade is not possible, consider blocking unauthenticated access to /ccm/system/panels/ at the web-server or WAF level as a temporary mitigation.


References

  • Concrete CMS — concretecms/concretecms on GitHub
  • Concrete CMS - Fixed Release
  • NVD — CVE-2026-18110

Disclaimer

This repository is published for educational and defensive security purposes. All testing was performed against systems under explicit authorization. The authors are not responsible for any misuse of the information or tools contained herein.

Download Tool