Wordpress CSRF token gets generated more than once

253 Views Asked by At

I am running a website on wordpress, and i want to implement CSRF protection, i mostly use php on the website with the help of shortcodes and a child theme, i have tried implementing the token generation in shortcodes and using action hooks to run before the most of the code runs so that the token can be stored in SESSION and used on another page, and it always runs more than once (two times). I found another question where people described how this might be because other "processes" are running the page as well, and they run the code on the page, which changes the value from what is displayed. When i fill the token into a form, submit, it's always a token mismatch because of this, i could store the token before and use that to check for a match, but that is not a real solution to the problem and if the page runs more than twice, it's a mismatch again.

How can i make sure that my code only runs exactly once per visit?

My code in the function.php file

function generate_csrf_token() {
    if(is_page('myprofile')){
        session_start();
        $token = md5(uniqid(mt_rand(), true));
        $_SESSION['token'] = $token;
    }
}
add_action('template_redirect', 'generate_csrf_token', 1);

Tried to use different action hooks, store the second last token as well as the last, used a counter to check how many times the code actually runs.

1

There are 1 best solutions below

0
LoicTheAztec On

Try to use did_action() in an IF statement to avoid that action being fired multiple times:

add_action('template_redirect', 'generate_csrf_token', 1);
function generate_csrf_token() {
    // Avoiding hook repetition
    if ( did_action( 'template_redirect' ) >= 2 )
        return;

    if ( is_page('myprofile') && session_status() === PHP_SESSION_NONE ){
        session_start();

        // Set the token in session, if it doesn't exist
        if ( ! isset($_SESSION['token']) ) {
            $_SESSION['token'] = md5(uniqid(mt_rand(), true));
        }
    }
}

It should work.


If you want to keep the session alive everywhere (not only in "myprofile" page), use:

add_action('template_redirect', 'generate_csrf_token', 1);
function generate_csrf_token() {
    // Avoiding hook repetition
    if ( did_action( 'template_redirect' ) >= 2 )
        return;

    if ( session_status() === PHP_SESSION_NONE ){
        session_start();

        // Set the token in session, if it doesn't exist
        if ( is_page('myprofile') && ! isset($_SESSION['token']) ) {
            $_SESSION['token'] = md5(uniqid(mt_rand(), true));
        }
    }
}