CVE-2021-24892: Authorization Bypass Through User-Controlled Key (CWE-639)
value: Insecure Direct Object Reference in edit function of Advanced Forms (Free & Pro) before 1.6.9 allows authenticated remote attacker to change arbitrary user's email address and request for reset password, which could lead to take over of WordPress's administrator account. To exploit this vulnerability, an attacker must register to obtain a valid WordPress's user and use such user to authenticate with WordPress in order to exploit the vulnerable edit function.
CWE description: The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.
CVSS v3 base score: 8.8
Published: 2021-11-23T20:15Z
Repository: https://github.com/advancedforms/advanced-forms
Language: PHP
Files changed: core/forms/forms-rendering.php, core/forms/forms-submissions.php
Commit date: 2020-06-27 01:10:50 +0200
Commit message: Added form nonce with arguments

This fixes a security issue where arguments could be edited client-side.

Diff: diff --git a/core/forms/forms-rendering.php b/core/forms/forms-rendering.php
index 2c8fc5f..e7eb16b 100644
--- a/core/forms/forms-rendering.php
+++ b/core/forms/forms-rendering.php
@@ -296,17 +296,24 @@ class AF_Core_Forms_Rendering {
     ));
 
     // Hidden fields to identify form
     echo '<div class="acf-hidden">';
 
-      $nonce = wp_create_nonce( 'acf_nonce' );
-      echo sprintf( '<input type="hidden" name="_acfnonce" value="%s">', $nonce );
-      echo sprintf( '<input type="hidden" name="nonce" value="%s">', $nonce );
+      $acf_nonce = wp_create_nonce( 'acf_nonce' );
+      echo sprintf( '<input type="hidden" name="_acfnonce" value="%s">', $acf_nonce );
+      echo sprintf( '<input type="hidden" name="nonce" value="%s">', $acf_nonce );
     
       echo sprintf( '<input type="hidden" name="af_form" value="%s">', $form['key'] );
-      echo sprintf( '<input type="hidden" name="af_form_args" value="%s">', base64_encode( json_encode( $args ) ) );
-      echo sprintf( '<input type="hidden" name="_acf_form" value="%s">', base64_encode( json_encode( $args ) ) );
+
+      $encoded_args = base64_encode( json_encode( $args ) );
+      echo sprintf( '<input type="hidden" name="af_form_args" value="%s">', $encoded_args );
+      echo sprintf( '<input type="hidden" name="_acf_form" value="%s">', $encoded_args );
+
+      // Add nonce to ensure arguments can't be altered.
+      $hashed_args = hash( 'sha256', $encoded_args );
+      $nonce = wp_create_nonce( sprintf( 'af_submission_%s_%s', $form['key'], $hashed_args ) );
+      echo sprintf( '<input type="hidden" name="af_form_nonce" value="%s">', $nonce );
 
       // Add honeypot field that is not visible to users.
       // Bots should hopefully fill this in allowing them to be detected.
       if ( $args['honeypot'] ) {
         echo '<input type="text" name="email_for_non_humans" tabindex="-1" autocomplete="off" />';
diff --git a/core/forms/forms-submissions.php b/core/forms/forms-submissions.php
index fb63a94..ce3cde6 100644
--- a/core/forms/forms-submissions.php
+++ b/core/forms/forms-submissions.php
@@ -171,11 +171,21 @@ class AF_Core_Forms_Submissions {
     if ( ! $form ) {
       return false;
     }
 
     // Retrieve the args used to display the form
-    $args = json_decode( base64_decode( $_POST['af_form_args'] ), true );
+    $encoded_args = $_POST['af_form_args'];
+    $args = json_decode( base64_decode( $encoded_args ), true );
+
+    // Verify nonce
+    $nonce = $_POST['af_form_nonce'];
+    $hashed_args = hash( 'sha256', $encoded_args );
+    $nonce_value = sprintf( 'af_submission_%s_%s', $form['key'], $hashed_args );
+    if ( ! wp_verify_nonce( $nonce, $nonce_value ) ) {
+      wp_die( 'Invalid form nonce' );
+      exit;
+    }
 
     // Retrieve all form fields and their values
     $fields = array();
     
     if ( isset( $_POST['acf'] ) ) {

Vulnerable code:       $nonce = wp_create_nonce( 'acf_nonce' );
      echo sprintf( '<input type="hidden" name="_acfnonce" value="%s">', $nonce );
      echo sprintf( '<input type="hidden" name="nonce" value="%s">', $nonce );
      echo sprintf( '<input type="hidden" name="af_form_args" value="%s">', base64_encode( json_encode( $args ) ) );
      echo sprintf( '<input type="hidden" name="_acf_form" value="%s">', base64_encode( json_encode( $args ) ) );
    $args = json_decode( base64_decode( $_POST['af_form_args'] ), true );
Fixed code:       $acf_nonce = wp_create_nonce( 'acf_nonce' );
      echo sprintf( '<input type="hidden" name="_acfnonce" value="%s">', $acf_nonce );
      echo sprintf( '<input type="hidden" name="nonce" value="%s">', $acf_nonce );

      $encoded_args = base64_encode( json_encode( $args ) );
      echo sprintf( '<input type="hidden" name="af_form_args" value="%s">', $encoded_args );
      echo sprintf( '<input type="hidden" name="_acf_form" value="%s">', $encoded_args );

      // Add nonce to ensure arguments can't be altered.
      $hashed_args = hash( 'sha256', $encoded_args );
      $nonce = wp_create_nonce( sprintf( 'af_submission_%s_%s', $form['key'], $hashed_args ) );
      echo sprintf( '<input type="hidden" name="af_form_nonce" value="%s">', $nonce );
    $encoded_args = $_POST['af_form_args'];
    $args = json_decode( base64_decode( $encoded_args ), true );

    // Verify nonce
    $nonce = $_POST['af_form_nonce'];
    $hashed_args = hash( 'sha256', $encoded_args );
    $nonce_value = sprintf( 'af_submission_%s_%s', $form['key'], $hashed_args );
    if ( ! wp_verify_nonce( $nonce, $nonce_value ) ) {
      wp_die( 'Invalid form nonce' );
      exit;
    }