CSRF (Cross Site Request Forgery) has been major vulnerability for PHP applications. CSRF protection is not difficult to implement especially with good web application frameworks. However, CSRF protection requires many lines of code if users have to implement by themselves. This proposal extends session module to provide optional automatic/manual CSRF protection for PHP web applications.
Although basic authenticity (CSRF protection) should be part of web infrastructure, it was not addressed in session managers because
PHP has output buffer and URL rewriter. These are used for Trans SID (Transparent Session ID) for session management without cookie currently. These can be extended to add CSRF protection.
This proposal does not intended to replace framework's CSRF protections, but it provides alternative to PHP applications with minimum code/setting changes.
This RFC archives:
Session module is extended to manage CSRF validation. When CSRF protection is enabled, CSRF attack is prevented by session_start(). Execution is terminated by default, but developers may suppress error and detect CSRF attack by themselves via session_csrf_status(). Validation can be done by developers also via session_csrf_validate(). CSRF protection token is generated by random secret key stored in session data and specified TTL value. Stolen CSRF protection token only allows attacks for specific period upto TTL.
Textbook web forms like
<form action="http://example.com/edit.php" method="POST"> <textarea name="comment"></textarea> <input type="submit" name="submit" /> </form>
can be protected by one line. 1)
<?php session_start(['csrf_rewrite'=>SESSION_CSRF_POST, 'csrf_validate'=SESSION_CSRF_POST]); ?>
How many CSRF vulnerabilities exist in PHP applications? Use of this CSRF protection is simple and easy. This RFC would help huge number of PHP applications.
<?php session_start('csrf_rewrite'=>SESSION_CSRF_POST, 'csrf_validate'=>SESSION_CSRF_POST); ?>
All post pages that use PHP session are protected. If application modify data only by POST, above session_start() is enough for POST CSRF protection. Developers can catch E_RECOVERABLE_ERROR 2) by set_error_handler() callback and handle them.
NOTE: Browsers cannot send POST request directly. i.e. It must display “form” before submit. Forms will have CSRF token by URL rewriter always. Therefore, POST requests have CSRF token always.
Manually embedding tokens and validation codes is mistakable, but it is supported.
To protect CSRF from manually, user can
then, user can add token manually
<?php // Rewrite and Validation is disabled by default, but explicitly disabled here. session_start(['csrf_rewrite'=>SESSION_CSRF_NONE, 'csrf_validate'=>SESSION_CSRF_NONE]); ?> // GET <a href="http://example.com/delete.php?id=1234&<?php echo session_csrf_token(SESSION_CSRF_GET);?>">Delete ID:1234</a> // POST - put this inside form tag <?php echo session_csrf_token(SESSION_CSRF_POST);?>
then, validate manually
<?php // Rewrite and Validation is disabled by default, but explicitly disabled here. session_start(['csrf_rewrite'=>SESSION_CSRF_NONE, 'csrf_validate'=>SESSION_CSRF_NONE]); // This is POST. if (session_csrf_validate($_POST) !== SESSION_CSRF_VALID) { die('Invalid request'); }
Followings are example for manual protection.
Entry page.
<?php // Disable automatic rewrite and validation explicitly - they are disabled by default session_start(['csrf_rewrite'=>SESSION_CSRF_NONE, 'csrf_validate'=SESSION_CSRF_NONE]); ?> <html> <head></head> <body> <a href="http://example.com/delete.php?id=1234&<?php echo session_csrf_token(SESSION_CSRF_GET);?>">Delete ID:1234</a> <a href="http://example.com/show.php?id=1234">Show ID:1234</a> <form action="http://example.com/edit.php" method="POST"> <textarea name="comment"></textarea> <?php echo session_csrf_token(SESSION_CSRF_POST);?> <input type="submit" /> </form> </body> </html>
Output to browser will be something like
<html> <head></head> <body> <a href="http://example.com/delete.php?id=1234&SESSCSRF=1462920523-5fd057a6ff9dc7a124fa5c814765a498e5aa024a">Remove ID:1234</a> <a href="http://example.com/show.php?id=1234">Show ID:1234</a> <form action="http://example.com/edit.php" method="POST"> <textarea name="comment"></textarea> <input type="hidden" name="SESSCSRF" value="1462920523-5fd057a6ff9dc7a124fa5c814765a498e5aa024a" /> <input type="submit" /> </form> </body> </html>
delete.php
<?php // Disable automatic rewrite ('csrf_rewrite'=>SESSION_CSRF_NONE) and validation explicitly ('csrf_validate'=>SESSION_CSRF_NONE) - they are disabled by default session_start(['csrf_rewrite'=>SESSION_CSRF_NONE, 'csrf_validate'=>SESSION_CSRF_NONE]); if (session_csrf_validate($_GET) !== SESSION_CSRF_VALID) { die('CSRF Attack or expired CSRF token'); } // Delete data ?>
show.php
<?php // Disable automatic rewrite and validation explicitly - they are disabled by default session_start(['csrf_rewrite'=>SESSION_CSRF_NONE, 'csrf_validate'=SESSION_CSRF_NONE]); // No CSRF validation token for this // Show data ?>
edit.php
<?php // Disable automatic rewrite and validation explicitly - they are disabled by default session_start(['csrf_rewrite'=>SESSION_CSRF_NONE, 'csrf_validate'=SESSION_CSRF_NONE]); if (session_csrf_validate($_POST) !== SESSION_CSRF_VALID) { die('CSRF Attack or expired CSRF token'); } // Modify data ?>
session_start(['csrf_rewrite'=>SESSION_CSRF_GET]);
This rewrites
<a href="http://example.com/delete.php?id=1234">Remove ID:1234</a>
to
<a href="http://example.com/delete.php?id=1234&SESSCSRF=1462920523-5fd057a6ff9dc7a124fa5c814765a498e5aa024a">Remove ID:1234</a>
NOTE: Something like “SESSCSRF=1462920523-5fd057a6ff9dc7a124fa5c814765a498e5aa024a” is added automatically.
// Add CSRF protection token for POST ('csrf_rewrite'=>SESSION_CSRF_POST) session_start(['csrf_rewrite'=>SESSION_CSRF_POST]);
This rewrites
<form action="http://example.com/edit.php" method="POST"> <textarea name="comment"></textarea> <input type="submit" /> </form>
to
<form action="http://example.com/edit.php" method="POST"> <textarea name="comment"></textarea> <input type="hidden" name="SESSCSRF" value="1462920523-5fd057a6ff9dc7a124fa5c814765a498e5aa024a" /> <input type="submit" /> </form>
NOTE: Something like “<input type=“hidden” name=“SESSCSRF” value=“1462920523-5fd057a6ff9dc7a124fa5c814765a498e5aa024a” />” is added to form automatically.
Validate GET request.
// 'csrf_validate'=>SESSION_CSRF_GET enables $_GET CSRF token validation session_start(['csrf_validate'=>SESSION_CSRF_GET]);
Validate POST request.
// 'csrf_validate'=>SESSION_CSRF_POST enables $_POST CSRF token validation session_start(['csrf_validate'=>SESSION_CSRF_POST]);
Validate GET and POST request.
// Enable both $_GET/$_POST CSRF token validation session_start(['csrf_validate'=>SESSION_CSRF_GET|SESSION_CSRF_POST]);
It may seem implementation is complex due to number of configuration parameters/functions/constants, but it is straightforward and simple.
JS apps may need to obtain CSRF token manually.
get_csrf_token.php
<?php header("Content-Type: application/json; charset=utf-8"); session_start(['csrf_protection'=>SESSION_CSRF_GET]); // CSRF token is secure because it is generated by using secret random key stored in session data. echo json_encode(['SESSCSRF'=>session_csrf_token()]); ?>
Simplicity and Security for user code.
If application requires POST CSRF protection only, 2 INI settings is good enough to protect whole application. No code modification is required. There are many existing applications that could be protected by this.
This implementation is more secure than most CSRF protection implementations because it has TTL. CSRF token is only valid specified time and token value changes according to TTL. This implementation is much secure than session lifetime CSRF token (semi static CSRF token).
It adds codes to PHP session module, but implementation (patch for this RFC) is straightforward and simple.
Session task, by its definition, is to distinguish and manage state of requests. Session must distinguish requests, i.e. must keep authenticity. CSRF is obvious authenticity issue. Some of us see session manager as just a storage, but it's not sufficient definition.
One can argue that “CSRF protection is not a mandatory requirement for session management”. I agree with this argument. It's not considered as a mandatory today at least. However, “CSRF protection does not belong to session management” cannot be correct because authenticity is basic feature of session.
The main reason why CSRF protection is not included in other languages' session managers is technical limitation. Output buffering and buffered content rewriter is required for implementation. PHP has them both, but other languages do not. PHP is made for web and not utilizing ability would be a waste of features.
If there is no pages, you may use PHP script (get_csrf_token.php) mentioned above. Be Careful for TTL, but don't get new token always. It's waste of resources.
Yes, it increases security risk of CSRF token exposure. It's not recommended for public web sites. However, have you ever write simple web tools for your development or operation environment? Did you implement full CSRF protection for every tool? I don't. GET protection is handy for such tools and provides instant full CSRF protection. 3)
URL may be pasted to chat/mail/etc, but it is expired in 1800 seconds by default.
It's secure. Random CSRF token generation key is stored in session data which is private to users. CSRF token is generated by using the secret key. Therefore, attacker cannot get CSRF token unless they have stolen session already.
No. It works for large/complex applications, but one can use their own implementation.
For instance, I have CSRF lib that detects both CSRF attack and multiple submits. Session module will never support multiple submit detection.
All defaults are the same
Include these so readers know where you are heading and can discuss the proposed voting options.
State whether this project requires a 2/3 or 50%+1 majority (see voting)
TBD
After the project is implemented, this section should contain
Links to external references, discussions or RFCs
Keep this updated with features that were discussed on the mail lists.