Skip to content

Instantly share code, notes, and snippets.

@N3mes1s
Created October 29, 2025 13:09
Show Gist options
  • Select an option

  • Save N3mes1s/79849e333186cedfd3d53661cbcba719 to your computer and use it in GitHub Desktop.

Select an option

Save N3mes1s/79849e333186cedfd3d53661cbcba719 to your computer and use it in GitHub Desktop.
CVE-2025-61481 – MikroTik WebFig Default HTTP Credential Exposure

CVE-2025-61481 – MikroTik WebFig Default HTTP Credential Exposure

Advisory ID: CVE-2025-61481
Component: MikroTik RouterOS / SwOS WebFig management interface
Tested build: RouterOS CHR 7.14.2 (factory-reset image)
Prepared on: 2025-10-29


Executive Summary

Testing confirms that freshly reset RouterOS 7.14.2 instances expose the WebFig management UI exclusively over HTTP. The login form stores the administrator credentials in window.sessionStorage and transmits them in cleartext over port 80 without redirecting users to HTTPS. Packet captures show full authentication payloads travelling unencrypted. An on-path attacker can trivially steal or modify credentials, hijack sessions, and persistently compromise the device. MikroTik SwOS 2.18 reportedly shares the same WebFig component, so the risk extends to CRS326-24G-2S+ and related switches.


Impact Assessment

  • Credential theft: Attackers monitoring the management VLAN can read administrator usernames and passwords in transit. Session identifiers remain exposed for reuse.
  • Session takeover & persistence: Captured credentials enable adversaries to log in, alter routing/firewall policies, deploy custom firmware, or create backdoor accounts.
  • Integrity risks during provisioning: The issue manifests immediately after factory reset; engineers onboarding hardware in the field are vulnerable to local MITM attacks.
  • Broader exposure: Any RouterOS/SwOS deployment that leaves HTTP enabled inherits CWE-319/CWE-200 weaknesses unless operators manually force TLS.

Technical Root Cause

WebFig initialises over HTTP and relies on JavaScript to manage authentication. Key observations from the captured assets:

mikrotik-webfig-cleartext-http/automation.log:1-8
browser console: [DOM] Input elements should have autocomplete attributes (suggested: "current-password"): (More info: https://goo.gl/9p2vKq) %o
sessionStorage before submit: { name: null, password: null }
browser console: sessionStorage.setItem name admin
browser console: sessionStorage.setItem password PruvaTest1!
HTTP request: GET http://127.0.0.1:1080/webfig/
HTTP request: GET http://127.0.0.1:1080/webfig/master-d3bb55452204.css
HTTP request: GET http://127.0.0.1:1080/webfig/curve255-541e54a862be.js
HTTP request: GET http://127.0.0.1:1080/webfig/master-min-99e951c770b2.js

The decompressed WebFig bundle shows how credentials are persisted and submitted:

// mikrotik-webfig-cleartext-http/web_assets/master-min-99e951c770b2.js.decompressed:1384-1390
function doAuth(user,pwd){
  get("startup_progress").innerHTML="Loading<img alt=\"\" src=\"progress.gif\">";
  session=new Session();
  request('POST','/jsproxy',session.makeInitialRequest(),function(r){
    session.keyExchange(new Uint8Array(r));
    post({s1:user,s3:pwd},function(rep){ /* … */ },()=>logout("Authentication failed"));
  },null,true);
}
function autoLogin(){
  const user=window.sessionStorage.getItem("name");
  const password=window.sessionStorage.getItem("password");
  if(user!=undefined&&password!=undefined){
    doAuth(user,password);
    window.sessionStorage.clear();
  }
}

Because the entire login flow runs over HTTP, any passive network observer can read post({s1:user,s3:pwd}, …) payloads, and an active attacker can inject modified JavaScript before the browser evaluates it.


Reproduction Walkthrough

All steps were executed from CVE-2025-61481-mikrotik-webfig-cleartext-http/default_recipe/mikrotik-webfig-cleartext-http/.

  1. Provision RouterOS CHR 7.14.2

    sudo apt-get update
    sudo apt-get install -y qemu-system-x86 qemu-utils unzip expect tcpdump mitmproxy socat nodejs chromium-browser
    curl -L https://download.mikrotik.com/routeros/7.14.2/chr-7.14.2.img.zip -o prepped/chr-7.14.2.img.zip
    unzip -o prepped/chr-7.14.2.img.zip -d prepped/

    Result: prepped/chr-7.14.2.img extracted successfully.

  2. Boot CHR with HTTP forwarded to localhost:1080

    qemu-system-x86_64 \
      -name mikrotik-chr \
      -m 512 -smp 1 \
      -drive file=chr_vm/chr-7.14.2.img,if=ide,format=raw \
      -netdev user,id=net0,hostfwd=tcp::1080-192.168.88.1:80 \
      -device e1000,netdev=net0 \
      -display none \
      -chardev socket,id=serial0,path=chr_vm/serial.sock,server=on,wait=off,logfile=chr_vm/serial.log \
      -serial chardev:serial0 -daemonize

    Result: Serial console recorded the default boot sequence; the CHR interface listens on 192.168.88.1:80.

  3. Automate initial setup and assign the management IP

    python3 scripts/routeros_initial_setup.py chr_vm/serial.sock 'PruvaTest1!'
    python3 scripts/routeros_cli.py chr_vm/serial.sock "/ip address add address=192.168.88.1/24 interface=ether1"

    Result: Default password set and HTTP service left enabled; ether1 bound to 192.168.88.1/24.

  4. Capture the login traffic and browser behaviour

    sudo tcpdump -i lo -w captures/webfig_login.pcap 'tcp port 1080' &
    node puppeteer/login.js | tee automation.log
    • automation.log records the browser storing credentials in sessionStorage and requesting WebFig assets via HTTP.
  5. Collect supporting artifacts

    curl -s http://127.0.0.1:1080/script.js -o web_assets/script.js
    curl -s http://127.0.0.1:1080/webfig/master-min-99e951c770b2.js -o web_assets/master-min-99e951c770b2.js
    gzip -dc web_assets/master-min-99e951c770b2.js > web_assets/master-min-99e951c770b2.js.decompressed

    Result: JavaScript bundle analysed locally to confirm credential handling logic.

  6. Inspect the packet capture for HTTP credential submission

    tool_307_run_shell.log excerpt (captures/webfig_login.pcap)
    POST /jsproxy HTTP/1.1
    Host: 127.0.0.1:1080
    Content-Length: 68
    Referer: http://127.0.0.1:1080/webfig/
    payload hex: 00000005000000015239d7eea069936b843a76b0eaf8a4c90a1c7270baa981c384fc8a2f75d37534fa55ab554f2aa4a45229464b390f3712030397a649b830c69294b058
    

    The RouterOS message format is binary, but the payload travels without TLS. An attacker controlling the HTTP response could inject logging code or strip the key-exchange entirely.


Evidence Collected

  • mikrotik-webfig-cleartext-http/automation.log — Browser console log confirming sessionStorage.setItem for username/password while loading WebFig assets over HTTP.
  • mikrotik-webfig-cleartext-http/web_assets/master-min-99e951c770b2.js.decompressed — Decompiled JavaScript showing doAuth sending {s1:user,s3:pwd} via HTTP POST and autoLogin sourcing credentials from sessionStorage.
  • mikrotik-webfig-cleartext-http/captures/webfig_login.pcap — Packet capture of the HTTP session containing multiple POST /jsproxy transactions in cleartext.
  • chr_vm/serial.log — Boot log demonstrating the router reset and default configuration with HTTP enabled.

Reproduction Timeline

Step Action Key Output Outcome
1 Install tooling & download CHR image chr-7.14.2.img unpacked into prepped/ Ready to boot RouterOS 7.14.2 locally
2 Launch QEMU with port-forward 1080→80 chr_vm/serial.log shows RouterOS boot HTTP listener reachable at http://127.0.0.1:1080/
3 Run serial initialisation scripts /ip service print confirms WebFig HTTP enabled Device reset state replicated
4 Start tcpdump & drive browser login automation.log captures sessionStorage.setItem entries and HTTP asset loads Credentials stored client-side in cleartext environment
5 Capture WebFig assets & decode JS Decompressed master-min-99e951c770b2.js exposes post({s1:user,s3:pwd}, …) Confirms insecure credential handling logic
6 Parse PCAP for login POSTs POST /jsproxy HTTP/1.1 entries without TLS Proves credential exchange transits unencrypted

Attacker Playbook

  1. Position on the management network (compromised host, rogue Wi-Fi AP, or malicious switch port).
  2. Observe HTTP traffic from administrators resetting or managing MikroTik devices.
  3. Harvest credentials by decoding captured POST bodies or by injecting modified JavaScript that exfiltrates sessionStorage contents.
  4. Hijack the session to alter routing policies, enable remote access services, or install malicious firmware.
  5. Persist by creating privileged accounts, altering firewall rules, or planting reverse tunnels.

Because the login script caches credentials in sessionStorage, attackers modifying the HTTP response can instruct browsers to beacon sensitive information even after the initial login succeeds.


CWE Mapping

  • CWE-1188 – Initialization of a Resource with an Insecure Default (WebFig starts with HTTP-on by default after reset).
  • CWE-319 – Cleartext Transmission of Sensitive Information (credentials traverse the network without encryption).
  • CWE-200 – Exposure of Sensitive Information to an Unauthorized Actor (attackers on-path can read and reuse admin credentials).

Mitigation & Recommendations

  1. Enforce HTTPS immediately after provisioning MikroTik devices. Disable HTTP (/ip service disable [find name=www]) or configure WebFig to redirect all traffic to HTTPS.
  2. Isolate management interfaces on dedicated, trusted networks or VLANs. Apply strict ACLs to block untrusted hosts from reaching port 80/443 on the device.
  3. Use encrypted management channels such as SSH or VPN tunnels for configuration tasks, especially during initial deployment.
  4. Monitor for HTTP usage by flagging connections to WebFig over port 80. Unexpected cleartext sessions indicate misconfiguration or downgrade attacks.
  5. Coordinate with MikroTik for firmware updates that default WebFig to HTTPS and eliminate credential caching in sessionStorage.

Appendix: Reproduction Artifacts

  • Packet capture: mikrotik-webfig-cleartext-http/captures/webfig_login.pcap
  • JavaScript bundle: mikrotik-webfig-cleartext-http/web_assets/master-min-99e951c770b2.js (compressed) & .decompressed
  • Automation log: mikrotik-webfig-cleartext-http/automation.log
  • CHR boot log: mikrotik-webfig-cleartext-http/chr_vm/serial.log

These files provide everything required to replicate, audit, or demonstrate the vulnerability without external dependencies.


Embedded Artifact: automation.log

Using Chromium executable: /snap/bin/chromium
browser console: [DOM] Input elements should have autocomplete attributes (suggested: "current-password"): (More info: https://goo.gl/9p2vKq) %o
sessionStorage before submit: { name: null, password: null }
browser console: sessionStorage.setItem name admin
browser console: sessionStorage.setItem password PruvaTest1!
HTTP request: GET http://127.0.0.1:1080/webfig/
HTTP request: GET http://127.0.0.1:1080/webfig/master-d3bb55452204.css
HTTP request: GET http://127.0.0.1:1080/webfig/curve255-541e54a862be.js
HTTP request: GET http://127.0.0.1:1080/webfig/master-min-99e951c770b2.js
browser console: 2025.10.29 13:41:03.331: initWebfig
HTTP request: GET http://127.0.0.1:1080/webfig
HTTP request: GET http://127.0.0.1:1080/webfig/progress.gif
HTTP request: GET http://127.0.0.1:1080/webfig/list
HTTP request: GET http://127.0.0.1:1080/webfig/roteros-6fb93413ec27.jg
HTTP request: GET http://127.0.0.1:1080/webfig/wave2-4be31cf85401.jg
HTTP request: GET http://127.0.0.1:1080/webfig/ups-e29683c8d492.jg
HTTP request: GET http://127.0.0.1:1080/webfig/secure-453c6a560083.jg
HTTP request: GET http://127.0.0.1:1080/webfig/ppp-0531971e726b.jg
HTTP request: GET http://127.0.0.1:1080/webfig/ipv6-9efe97e331aa.jg
HTTP request: GET http://127.0.0.1:1080/webfig/hotspot-f1e2e1d4af99.jg
HTTP request: GET http://127.0.0.1:1080/webfig/dhcp-accdf83c6abd.jg
HTTP request: GET http://127.0.0.1:1080/webfig/advtool-89d29caa39a2.jg
HTTP request: GET http://127.0.0.1:1080/webfig/icons.png
HTTP request: GET http://127.0.0.1:1080/webfig/right-arrow.svg
HTTP request: GET http://127.0.0.1:1080/webfig/down.svg
HTTP request: GET http://127.0.0.1:1080/webfig/up.svg
browser console: [DOM] Password field is not contained in a form: (More info: https://goo.gl/9p2vKq) %o
browser console: [DOM] Password field is not contained in a form: (More info: https://goo.gl/9p2vKq) %o
sessionStorage after navigation: { name: null, password: null, keys: [] }

Embedded Artifact: captures/webfig_login.pcap (selected frames)

POST /jsproxy HTTP/1.1
Host: 127.0.0.1:1080
Connection: keep-alive
Content-Length: 68
sec-ch-ua-platform: "Linux"
Accept-Language: 
sec-ch-ua: "Chromium";v="141", "Not?A_Brand";v="8"
Content-Type: msg
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/141.0.0.0 Safari/537.36
Accept: */*
Origin: http://127.0.0.1:1080
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:1080/webfig/
Accept-Encoding: gzip, deflate, br, zstd
payload hex: 0000000100000001c1d94364cd7db2c349cc459f9984df73902745f74cda38494effaddd69c14247bf96251993fb7a9dd2dbfad038a026e466ec4d6d65a948cf58b43f9c
payload ascii: ..........Cd.}..I.E....s.'E.L.8IN...i.BG..%...z.....8.&.f.Mme.H.X.?.
--------------------------------------------------------------------------------
POST /jsproxy HTTP/1.1
Host: 127.0.0.1:1080
Connection: keep-alive
Content-Length: 57
sec-ch-ua-platform: "Linux"
Accept-Language: 
sec-ch-ua: "Chromium";v="141", "Not?A_Brand";v="8"
Content-Type: msg
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/141.0.0.0 Safari/537.36
Accept: */*
Origin: http://127.0.0.1:1080
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:1080/webfig/
Accept-Encoding: gzip, deflate, br, zstd
payload hex: 000000010000003d83ff755986a5d8e43bc37e3e399aac875cda42e549eea542349374f892a714badb3dc3dea6729a3e7ba8ecc55cc777d0ce
payload ascii: .......=..uY....;.~>9...\.B.I..B4.t......=...r.>{...\.w..
--------------------------------------------------------------------------------
POST /jsproxy HTTP/1.1
Host: 127.0.0.1:1080
Connection: keep-alive
Content-Length: 64
sec-ch-ua-platform: "Linux"
Accept-Language: 
sec-ch-ua: "Chromium";v="141", "Not?A_Brand";v="8"
Content-Type: msg
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/141.0.0.0 Safari/537.36
Accept: */*
Origin: http://127.0.0.1:1080
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:1080/webfig/
Accept-Encoding: gzip, deflate, br, zstd
payload hex: 00000001000000902b5805234a74122500f34eb6029b6e44ca43aefef0447123975374d4875df5df2abc06926c976d8834b8791996403ee1add8f3891c6431d3
payload ascii: ........+X.#Jt.%..N...nD.C...Dq#.St..]..*...l.m.4.y..@>......d1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment