Safari on mobile devices resets CSRF token in GAASE session

I am running Flask on the Google App Engine using the Flask GAE Template .

I implemented the CSRF protection described in Flask-WTF Docs and I tested it.

I had an interesting problem when the session variable csrf_tokengets reset by my iPhone browser (Safari). I will describe what led me to this conclusion, but firstly, here is my code:

Relevant code in views.py:

def test_page(code):
    form = forms.TestCSRFForm()
    return render_template('csrf_test.html', form=form)

def test_ajax():
    form = forms.TestCSRFForm()
    return "{}".format(form.validate_on_submit())

Relevant code in urls.py:

app.add_url_rule('/csrftest/<code>', 'test_page', view_func=views.test_page, methods=['GET'])

app.add_url_rule('/csrftest/', 'test_ajax', view_func=views.test_ajax, methods=['POST'])

My template (csrf_test.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

    <title>Test</title>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>


    <script type="text/javascript">
        function go() {

            var csrftoken = $('{{ form.csrf_token }}').attr('value');


            alert(csrftoken);

            $.ajaxSetup({
                beforeSend: function(xhr, settings) {
                    if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type)) {
                        xhr.setRequestHeader("X-CSRFToken", csrftoken)
                    }
                }
            });
            $.ajax({
                type: "POST",
                url: "/csrftest/",
                data: { csrf_token: csrftoken }
            }).done(function( msg ) {
                alert(msg);
            });
        }
    </script>

</head>

<body>
    <button onclick="go();">go</button>
</body>

</html>

Some entries I made in Flask-WTF csrf.py:

def generate_csrf(secret_key=None, time_limit=None):
    """Generate csrf token code.

    :param secret_key: A secret key for mixing in the token,
                       default is Flask.secret_key.
    :param time_limit: Token valid in the time limit,
                       default is 3600s.
    """
    if not secret_key:
        secret_key = current_app.config.get(
            'WTF_CSRF_SECRET_KEY', current_app.secret_key
        )

    if not secret_key:
        raise Exception('Must provide secret_key to use csrf.')

    if time_limit is None:
        time_limit = current_app.config.get('WTF_CSRF_TIME_LIMIT', 3600)

    if 'csrf_token' not in session:
        session['csrf_token'] = hashlib.sha1(os.urandom(64)).hexdigest()

    if time_limit:
        expires = time.time() + time_limit
        csrf_build = '%s%s' % (session['csrf_token'], expires)
    else:
        expires = ''
        csrf_build = session['csrf_token']

    hmac_csrf = hmac.new(
        to_bytes(secret_key),
        to_bytes(csrf_build),
        digestmod=hashlib.sha1
    ).hexdigest()

    logging.info("session['csrf_token'] = %s" % session['csrf_token']) #logging here

    return '%s##%s' % (expires, hmac_csrf)



def validate_csrf(data, secret_key=None, time_limit=None):
    """Check if the given data is a valid csrf token.

    :param data: The csrf token value to be checked.
    :param secret_key: A secret key for mixing in the token,
                       default is Flask.secret_key.
    :param time_limit: Check if the csrf token is expired.
                       default is True.
    """
    if not data or '##' not in data:
        return False

    expires, hmac_csrf = data.split('##', 1)

    if time_limit is None:
        time_limit = current_app.config.get('WTF_CSRF_TIME_LIMIT', 3600)

    if time_limit:
        try:
            expires = float(expires)
        except:
            return False

        now = time.time()
        if now > expires:
            return False

    if not secret_key:
        secret_key = current_app.config.get(
            'WTF_CSRF_SECRET_KEY', current_app.secret_key
        )

    if 'csrf_token' not in session:
        return False

    logging.info("session['csrf_token'] = %s" % session['csrf_token']) #logging here

    csrf_build = '%s%s' % (session['csrf_token'], expires)
    hmac_compare = hmac.new(
        to_bytes(secret_key),
        to_bytes(csrf_build),
        digestmod=hashlib.sha1
    ).hexdigest()

    logging.info("comparing hmacs: {0} {1}".format(hmac_compare, hmac_csrf)) #logging here
    return safe_str_cmp(hmac_compare, hmac_csrf)

Let's say I save the following links in the Notes application on my phone:

http://myapp.appspot.com/csrftest/123
http://myapp.appspot.com/csrftest/456

If I am in the first link from my phone and press "Go", everything works fine: enter image description here

csrf_token , , , hmacs , .

, "", Safari ( ) , (csrftest/456), Safari ; /csrftest/ 456, ( , , ), /csrftest/ 123

:

enter image description here

enter image description here

enter image description here

, , , Safari (/csrftest/123), csrf_token - hmac /csrftest/ 456 csrf_token, hmac csrf_token , hmacs .

Safari, , ?

. , , Happy Thanksgiving:).

+4

All Articles