PluginProbe ʕ •ᴥ•ʔ
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / trunk
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz vtrunk
2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8 0.0.9 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.1.2 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.12.3 1.13.0 1.13.1 1.13.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.8.0 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.0 2.5.2 2.6.0
sureforms / inc / submit-token.php
sureforms / inc Last commit date
abilities 3 weeks ago admin 3 months ago ai-form-builder 1 month ago blocks 2 months ago compatibility 3 weeks ago database 3 weeks ago email 3 weeks ago fields 3 weeks ago global-settings 1 month ago lib 1 month ago migrator 2 months ago page-builders 3 weeks ago payments 3 weeks ago single-form-settings 2 months ago traits 2 months ago activator.php 1 year ago admin-ajax.php 2 months ago background-process.php 9 months ago create-new-form.php 3 months ago duplicate-form.php 3 months ago entries.php 3 weeks ago events-scheduler.php 2 years ago export.php 3 months ago field-validation.php 3 weeks ago form-restriction.php 2 months ago form-styling.php 1 month ago form-submit.php 4 days ago forms-data.php 5 months ago frontend-assets.php 4 days ago generate-form-markup.php 1 week ago gutenberg-hooks.php 1 week ago helper.php 4 days ago learn.php 4 months ago onboarding.php 2 months ago post-types.php 1 week ago rest-api.php 1 week ago smart-tags.php 4 days ago submit-token.php 4 months ago translatable.php 1 month ago updater-callbacks.php 3 weeks ago updater.php 3 weeks ago
submit-token.php
137 lines
1 <?php
2 /**
3 * Form Submission Token.
4 *
5 * Generates and verifies time-windowed HMAC tokens used to authenticate
6 * public form submissions. Unlike WordPress nonces (12–24 h expiry), these
7 * tokens are keyed on half-day windows and remain valid across multiple
8 * windows, making them safe to embed in cached pages without any client-side
9 * refresh logic.
10 *
11 * @package SureForms
12 * @since 2.6.0
13 */
14
15 namespace SRFM\Inc;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 /**
22 * Submit_Token
23 *
24 * Security properties:
25 * - HMAC-SHA256 keyed on the site's WordPress auth salt (unique per install).
26 * - Tokens are form-specific: a token issued for form 5 is invalid for form 6.
27 * - Each token encodes a half-day time window (43 200 s). Verification accepts
28 * the current window plus the previous SRFM_SUBMIT_TOKEN_ACCEPTED_WINDOWS − 1
29 * windows, giving up to 48 hours of validity. This covers even aggressive CDN
30 * or full-page-cache TTLs without any refresh round-trip from the browser.
31 * - Constant-time comparison (`hash_equals`) prevents timing-based oracle attacks.
32 *
33 * @since 2.6.0
34 */
35 class Submit_Token {
36 /**
37 * Length of a single time window in seconds (12 hours).
38 *
39 * Rotating every 12 hours keeps the attack window short while staying
40 * far below typical cache TTLs.
41 *
42 * @since 2.6.0
43 */
44 public const WINDOW_SECONDS = 43200;
45
46 /**
47 * Number of consecutive windows that are considered valid.
48 *
49 * 4 windows × 12 hours = 48 hours maximum token lifetime.
50 * Raise via the `srfm_submit_token_accepted_windows` filter if your
51 * deployment uses unusually long cache TTLs.
52 *
53 * @since 2.6.0
54 */
55 public const DEFAULT_ACCEPTED_WINDOWS = 4;
56
57 /**
58 * Generate a submission token for a given form.
59 *
60 * The token encodes the form ID and the current half-day window, signed
61 * with the site's auth salt. It is safe to embed in cached HTML because
62 * `verify()` accepts several consecutive past windows.
63 *
64 * @since 2.6.0
65 * @param int $form_id The form post ID.
66 * @return string 64-character lowercase hex HMAC-SHA256 token.
67 */
68 public static function generate( int $form_id ): string {
69 return self::sign( $form_id, self::current_window() );
70 }
71
72 /**
73 * Verify a token submitted with a form.
74 *
75 * Checks the token against every accepted window, from newest to oldest,
76 * using constant-time comparison throughout.
77 *
78 * @since 2.6.0
79 * @param string $token Token value received from the client.
80 * @param int $form_id Form post ID extracted from the request body.
81 * @return bool True if the token is valid for the given form, false otherwise.
82 */
83 public static function verify( string $token, int $form_id ): bool {
84 if ( '' === $token || $form_id <= 0 ) {
85 return false;
86 }
87
88 // Clamp to [1, 14]: must accept at least one window; cap at 14 (7 days)
89 // to prevent a misconfigured filter from making tokens effectively permanent.
90 $accepted = max( 1, min( 14, (int) apply_filters( 'srfm_submit_token_accepted_windows', self::DEFAULT_ACCEPTED_WINDOWS ) ) );
91
92 // Walk backwards through accepted windows; current window first.
93 for ( $offset = 0; $offset < $accepted; $offset++ ) {
94 if ( hash_equals( self::sign( $form_id, self::current_window() - $offset ), $token ) ) {
95 return true;
96 }
97 }
98
99 return false;
100 }
101
102 /**
103 * Return the index of the current half-day window.
104 *
105 * Dividing Unix time by WINDOW_SECONDS yields an integer that increments
106 * once every 12 hours, regardless of the server's local timezone.
107 *
108 * @since 2.6.0
109 * @return int
110 */
111 private static function current_window(): int {
112 return (int) floor( time() / self::WINDOW_SECONDS );
113 }
114
115 /**
116 * Build an HMAC-SHA256 signature for a specific form / window pair.
117 *
118 * The payload deliberately combines a namespace prefix, the form ID, and
119 * the window index so that tokens cannot be repurposed across forms or
120 * replayed across time windows.
121 *
122 * @since 2.6.0
123 * @param int $form_id Post ID of the form.
124 * @param int $window Half-day window index.
125 * @return string 64-character lowercase hex digest.
126 */
127 private static function sign( int $form_id, int $window ): string {
128 // Derive a plugin-specific sub-key from the site's auth salt so this
129 // system has an independent key surface from WordPress session cookies.
130 // Rotating wp-config.php secrets invalidates all outstanding tokens, which
131 // is intentional — a cache purge should follow any secret key rotation.
132 $signing_key = hash_hmac( 'sha256', 'srfm-submit-token-v1', wp_salt( 'auth' ) );
133 $payload = implode( '|', [ 'srfm_submit', $form_id, $window ] );
134 return hash_hmac( 'sha256', $payload, $signing_key );
135 }
136 }
137