| @@ -48,8 +48,12 @@ | ||
| 48 | 48 | /** |
| 49 | 49 | * Load and get the template we need. |
| 50 | 50 | */ |
| 51 | 51 | public function getTemplate() { |
| 52 | + // A nonce proves intent, not authorization — gate on capability too. | |
| 53 | + if ( ! current_user_can( 'manage_options' ) ) { | |
| 54 | + wp_die( -1, 403 ); | |
| 55 | + } | |
| 52 | 56 | $this->get_logger()->debug( 'getTemplate called', [ |
| 53 | 57 | 'plugin' => 'double-opt-in', |
| 54 | 58 | 'class' => __CLASS__, |
| 55 | 59 | 'method' => __METHOD__, |
| @@ -56,12 +60,19 @@ | ||
| 56 | 60 | 'post' => $_POST, |
| 57 | 61 | ] ); |
| 58 | 62 | |
| 59 | 63 | $content = ''; |
| 60 | - if ( isset( $_POST['template'] ) && wp_verify_nonce( $_POST['nonce'], 'f12_doi_templateloader' ) ) { | |
| 61 | - $template_path = plugin_dir_path( dirname( __FILE__ ) ) . 'mails/' . esc_attr( sanitize_text_field( $_POST['template'] ) ) . '.html'; | |
| 64 | + if ( isset( $_POST['template'] ) && wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'f12_doi_templateloader' ) ) { | |
| 65 | + // Use sanitize_file_name() to prevent path traversal attacks | |
| 66 | + $template_name = sanitize_file_name( wp_unslash( $_POST['template'] ) ); | |
| 67 | + $mails_dir = plugin_dir_path( dirname( __FILE__ ) ) . 'mails/'; | |
| 68 | + $template_path = $mails_dir . $template_name . '.html'; | |
| 62 | 69 | |
| 63 | - if ( file_exists( $template_path ) ) { | |
| 70 | + // Verify the resolved path is within the allowed directory (prevent path traversal) | |
| 71 | + $real_template_path = realpath( $template_path ); | |
| 72 | + $real_mails_dir = realpath( $mails_dir ); | |
| 73 | + | |
| 74 | + if ( $real_template_path && $real_mails_dir && strpos( $real_template_path, $real_mails_dir ) === 0 && file_exists( $real_template_path ) ) { | |
| 64 | 75 | $this->get_logger()->debug( 'Template file found, loading content', [ |
| 65 | 76 | 'plugin' => 'double-opt-in', |
| 66 | 77 | 'class' => __CLASS__, |
| 67 | 78 | 'method' => __METHOD__, |
| @@ -66,9 +77,9 @@ | ||
| 66 | 77 | 'class' => __CLASS__, |
| 67 | 78 | 'method' => __METHOD__, |
| 68 | 79 | 'template_path' => $template_path, |
| 69 | 80 | ] ); |
| 70 | - $content = file_get_contents( $template_path ); | |
| 81 | + $content = file_get_contents( $real_template_path ); | |
| 71 | 82 | } else { |
| 72 | 83 | $this->get_logger()->warning( 'Template file not found', [ |
| 73 | 84 | 'plugin' => 'double-opt-in', |
| 74 | 85 | 'class' => __CLASS__, |
| @@ -93,8 +104,13 @@ | ||
| 93 | 104 | /** |
| 94 | 105 | * Return the Popup for the HASH DOI |
| 95 | 106 | */ |
| 96 | 107 | public function getDetails() { |
| 108 | + // Exposes opt-in PII (email, IPs, all form fields). A nonce proves | |
| 109 | + // intent, not authorization — require the capability explicitly. | |
| 110 | + if ( ! current_user_can( 'manage_options' ) ) { | |
| 111 | + wp_die( -1, 403 ); | |
| 112 | + } | |
| 97 | 113 | $this->get_logger()->debug( 'getDetails called', [ |
| 98 | 114 | 'plugin' => 'double-opt-in', |
| 99 | 115 | 'class' => __CLASS__, |
| 100 | 116 | 'method' => __METHOD__, |
| @@ -100,9 +116,9 @@ | ||
| 100 | 116 | 'method' => __METHOD__, |
| 101 | 117 | 'post' => $_POST, |
| 102 | 118 | ] ); |
| 103 | 119 | |
| 104 | - if ( isset( $_POST['hash'] ) && wp_verify_nonce( $_POST['nonce'], 'f12_doi_details' ) ) { | |
| 120 | + if ( isset( $_POST['hash'] ) && wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'f12_doi_details' ) ) { | |
| 105 | 121 | global $wpdb; |
| 106 | 122 | $tableName = $wpdb->prefix . 'f12_cf7_doubleoptin'; |
| 107 | 123 | $hash = sanitize_text_field( $_POST['hash'] ); |
| 108 | 124 | |
| @@ -147,8 +163,12 @@ | ||
| 147 | 163 | ], |
| 148 | 164 | ] ); |
| 149 | 165 | |
| 150 | 166 | $formfields = maybe_unserialize( $OptIn->get_content() ); |
| 167 | + // Handle nested content structure (e.g., Avada stores {data: {...}, field_labels: {...}, ...}) | |
| 168 | + if ( is_array( $formfields ) && isset( $formfields['data'] ) && is_array( $formfields['data'] ) && ! isset( $formfields['fields'] ) ) { | |
| 169 | + $formfields = $formfields['data']; | |
| 170 | + } | |
| 151 | 171 | ob_start(); |
| 152 | 172 | ?> |
| 153 | 173 | <h2><?php echo esc_html( $OptIn->get_hash() ); ?></h2> |
| 154 | 174 | <?php if ( current_user_can( 'manage_options' ) ): ?> |
| @@ -167,9 +187,9 @@ | ||
| 167 | 187 | <td><?php _e( 'Key', 'double-opt-in' ); ?></td> |
| 168 | 188 | <td><?php _e( 'Value', 'double-opt-in' ); ?></td> |
| 169 | 189 | </tr> |
| 170 | 190 | <tr> |
| 171 | - <td><?php _e( 'ID', 'f12-cf7-doupleoptin' ); ?></td> | |
| 191 | + <td><?php _e( 'ID', 'double-opt-in' ); ?></td> | |
| 172 | 192 | <td><?php echo esc_html( $OptIn->get_id() ); ?></td> |
| 173 | 193 | </tr> |
| 174 | 194 | <tr> |
| 175 | 195 | <td><?php _e( 'CF7 Form ID', 'double-opt-in' ); ?></td> |
| @@ -246,10 +266,11 @@ | ||
| 246 | 266 | 'hook' => $hook, |
| 247 | 267 | ] ); |
| 248 | 268 | |
| 249 | 269 | if ( $hook == 'tools_page_f12doubleoptin' ) { |
| 250 | - wp_enqueue_style( 'f12-cf7-doubleoptin-admin', plugins_url( 'assets/admin-style.css', __FILE__ ) ); | |
| 251 | - wp_enqueue_script( 'f12-cf7-doubleoptin-admin', plugins_url( 'assets/f12-cf7-popup.js', __FILE__ ), [ 'jquery' ] ); | |
| 270 | + $ver = defined( 'FORGE12_OPTIN_VERSION' ) ? FORGE12_OPTIN_VERSION : false; | |
| 271 | + wp_enqueue_style( 'f12-cf7-doubleoptin-admin', plugins_url( 'assets/admin-style.css', __FILE__ ), array(), $ver ); | |
| 272 | + wp_enqueue_script( 'f12-cf7-doubleoptin-admin', plugins_url( 'assets/f12-cf7-popup.js', __FILE__ ), [ 'jquery' ], $ver, true ); | |
| 252 | 273 | |
| 253 | 274 | $this->get_logger()->info( 'Admin styles and scripts enqueued for DOI tools page', [ |
| 254 | 275 | 'plugin' => 'double-opt-in', |
| 255 | 276 | 'class' => __CLASS__, |