├── .coveralls.yml ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── composer.json ├── docs ├── files │ └── libs │ │ └── csrf │ │ └── csrfprotector-php.html ├── index.html ├── index │ ├── Files.html │ ├── Functions.html │ ├── General.html │ └── Variables.html ├── javascript │ ├── main.js │ ├── prettify.js │ └── searchdata.js ├── search │ ├── FilesC.html │ ├── FunctionsA.html │ ├── FunctionsC.html │ ├── FunctionsF.html │ ├── FunctionsG.html │ ├── FunctionsI.html │ ├── FunctionsL.html │ ├── FunctionsO.html │ ├── FunctionsR.html │ ├── FunctionsU.html │ ├── GeneralA.html │ ├── GeneralC.html │ ├── GeneralF.html │ ├── GeneralG.html │ ├── GeneralI.html │ ├── GeneralL.html │ ├── GeneralO.html │ ├── GeneralR.html │ ├── GeneralU.html │ ├── GeneralV.html │ ├── NoResults.html │ ├── VariablesC.html │ ├── VariablesI.html │ └── VariablesR.html └── styles │ └── main.css ├── js ├── README.md ├── csrfprotector.js └── index.php ├── libs ├── README.md ├── config.sample.php ├── csrf │ ├── LoggerInterface.php │ ├── README.md │ ├── csrfpAction.php │ ├── csrfpCookieConfig.php │ ├── csrfpDefaultLogger.php │ ├── csrfprotector.php │ └── index.php └── index.php ├── licence.md ├── phpunit.xml.dist ├── readme.md └── test ├── config.test.php ├── config.testInit_incompleteConfigurationException.php ├── config.testInit_withoutInjectedCSRFGuardScript.php ├── csrfprotector_test.php ├── csrfprotector_test_customlogger.php ├── fakeLogger.php └── testHelpers.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | src_dir: ./libs/ 3 | coverage_clover: build/logs/clover.xml 4 | json_path: build/logs/coveralls-upload.json -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug, repro needed 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots (Optional)** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | ** Error logs (Optional) ** 23 | If applicable, Share error logs 24 | 25 | **Additional context** 26 | - PHP Version (example: php 5.6) 27 | - Framework, if applicable 28 | - Browser 29 | 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | phpunit.phar 2 | coveralls.phar 3 | 4 | coverage/ 5 | vendor/ 6 | build/ 7 | log/*.log 8 | 9 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - "5.6" 4 | - "5.5" 5 | - "5.4" 6 | - "7.0" 7 | - "7.1" 8 | - hhvm 9 | - nightly 10 | - "5.3" 11 | 12 | 13 | matrix: 14 | allow_failures: 15 | - php: nightly 16 | - php: hhvm 17 | - php: "5.3" 18 | 19 | os: 20 | - linux 21 | 22 | dist: trusty 23 | 24 | install: 25 | # Install composer packages, will also trigger dump-autoload 26 | - composer install --no-interaction 27 | # Install coveralls.phar 28 | - wget -c -nc --retry-connrefused --tries=0 https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar 29 | - chmod +x coveralls.phar 30 | - php coveralls.phar --version 31 | 32 | before_script: 33 | - mkdir -p build/logs 34 | - ls -al 35 | 36 | script: 37 | - mkdir -p build/logs 38 | - if [ $(phpenv version-name) = 'hhvm' ]; then echo 'xdebug.enable=1' >> /etc/hhvm/php.ini; fi 39 | - phpunit --stderr --coverage-clover build/logs/clover.xml 40 | 41 | after_script: 42 | - php vendor/bin/coveralls -v 43 | 44 | after_success: 45 | - travis_retry php coveralls.phar -v 46 | 47 | cache: 48 | directories: 49 | - vendor 50 | - $HOME/.cache/composer 51 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at minhazav@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to OWASP CSRF Protector PHP 2 | CSRF Protector is a community project, and we are always delighted to welcome new contributors! 3 | 4 | There are lots of ways you can contribute: 5 | 6 | ## Got a Question or Problem? 7 | If you have a question or problem relating to using this project then the first thing to do is to check: 8 | - **Project Wiki**: We have a comprehensive User Guide for CSRF Protector in our [Github Wiki](https://github.com/mebjas/CSRF-Protector-PHP/wiki). 9 | - **OWASP Wiki**: We have more information about the project and sibling projects at [https://www2.owasp.org/www-project-csrfprotector/](https://www2.owasp.org/www-project-csrfprotector/). 10 | - **Articles online**: We have some articles online on how to use or new features introduced like: 11 | - [CSRF Protector - concept design & future](https://blog.minhazav.dev/CSRF-Protector-concept-design-and-future/) 12 | - [Session based login compatibility issues with CSRF - how to mitigate](https://blog.minhazav.dev/logging-out-and-then-logging-in-throws-403-error-with-csrf-protector/) 13 | - [Minor improvements to CSRF Protector PHP](https://blog.minhazav.dev/introducing-minor-improvements-to-csrf-protector-php/) 14 | - **Google groups**: If they don't help then please ask on the [User Group](https://groups.google.com/a/owasp.org/forum/#!forum/csrfprotector-project) 15 | 16 | ## Found an Issue? 17 | If you have found a bug then raise an issue on the CSRF Protector repo: [https://github.com/mebjas/CSRF-Protector-PHP/issues](https://github.com/mebjas/CSRF-Protector-PHP/issues) 18 | 19 | Its worth checking to see if its already been reported, and including as much information as you can to help us diagnose your problem. 20 | 21 | ## Found a Vulnerability? 22 | If you think you have found a vulnerability in CSRF Protector then please report it directly on email at [minhazav@gmail.com](mailto:https://github.com/mebjas/CSRF-Protector-PHP/issues) or [minhaz@owasp.org](mailto: minhaz@owasp.org) 23 | 24 | We are always very grateful to researchers who report vulnerabilities responsibly and will be very happy to give credit for the valuable assistance they provide. 25 | 26 | ## Have a Feature Request? 27 | If you have a suggestion for new functionality then you can raise an issue on the CSRF Protector PHP repo: [https://github.com/mebjas/CSRF-Protector-PHP/issues](https://github.com/mebjas/CSRF-Protector-PHP/issues) 28 | 29 | Its worth checking to see if its already been requested, and including as much information as you can so that we can fully understand your requirements. 30 | 31 | ## Become a CSRF Protector Evangelist 32 | Please feel free to write more about this project on how it works or how to use it. Please feel free to send a [pull request](https://github.com/mebjas/CSRF-Protector-PHP/pulls) by adding a reference to your article in the `README.md` or `CONTRIBUTING.md`. 33 | 34 | ## Help Improve the Documentation 35 | The source for the CSRF Protector OWASP wiki [User Guide is hosted at OWASP/www-project-csrfprotector](https://github.com/OWASP/www-project-csrfprotector) and is hosted at [https://www2.owasp.org/www-project-csrfprotector/](https://www2.owasp.org/www-project-csrfprotector/). Please feel free to send a [pull request](https://github.com/OWASP/www-project-csrfprotector/pulls) to add updates to the documentation. 36 | 37 | ## Coding 38 | 39 | There's always lots of coding to be done! If you feel something can be improved feel free to send a pull request. 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "owasp/csrf-protector-php", 3 | "type": "library", 4 | "description": "CSRF protector php, a standalone php library for csrf mitigation in web applications. Easy to integrate in any php web app.", 5 | "keywords": ["security","csrf", "owasp"], 6 | "homepage": "https://github.com/mebjas/CSRF-Protector-PHP", 7 | "license": "Apache-2.0", 8 | "require-dev": { 9 | "satooshi/php-coveralls": "~1.0" 10 | }, 11 | "autoload": { 12 | "classmap": ["libs/csrf/"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/files/libs/csrf/csrfprotector-php.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | /Users/minhazav/github/CSRF-Protector-PHP/libs/csrf/csrfprotector.php 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

csrfprotector.php

Summary
csrfprotector.php
Variables
$cookieExpiryTimeexpiry time for cookie @var int
$isSameOriginflag for cross origin/same origin request @var bool
$isValidHTMLflag to check if output file is a valid HTML or not @var bool
$requestTypeVaraible to store weather request type is post or get @var string
$configconfig file for CSRFProtector @var int Array, length = 6 Property: #1: failedAuthAction (int) => action to be taken in case autherisation fails Property: #2: logDirectory (string) => directory in which log will be saved Property: #3: customErrorMessage (string) => custom error message to be sent in case of failed authentication Property: #4: jsFile (string) => location of the CSRFProtector js file Property: #5: tokenLength (int) => default length of hash Property: #6: disabledJavascriptMessage (string) => error message if client’s js is disabled
Functions
initfunction to initialise the csrfProtector work flow
useCachedVersionfunction to check weather to use cached version of js file or not
createNewJsCacheFunction to create new cache version of js
authorisePostfunction to authorise incoming post requests
failedValidationActionfunction to be called in case of failed validation performs logging and take appropriate action
refreshTokenFunction to set auth cookie
generateAuthTokenfunction to generate random hash of length as given in parameter max length = 128
ob_handlerRewrites <form> on the fly to add CSRF tokens to them.
logCSRFattackFunctio to log CSRF Attack
getCurrentUrlFunction to return current url of executing page
isURLallowedFunction to check if a url mataches for any urls Listed in config file
15 | 16 |

Variables

17 | 18 |

$cookieExpiryTime

public static $cookieExpiryTime

expiry time for cookie @var int

19 | 20 |

$isSameOrigin

private static $isSameOrigin

flag for cross origin/same origin request @var bool

21 | 22 |

$isValidHTML

private static $isValidHTML

flag to check if output file is a valid HTML or not @var bool

23 | 24 |

$requestType

protected static $requestType

Varaible to store weather request type is post or get @var string

25 | 26 |

$config

public static $config

config file for CSRFProtector @var int Array, length = 6 Property: #1: failedAuthAction (int) => action to be taken in case autherisation fails Property: #2: logDirectory (string) => directory in which log will be saved Property: #3: customErrorMessage (string) => custom error message to be sent in case of failed authentication Property: #4: jsFile (string) => location of the CSRFProtector js file Property: #5: tokenLength (int) => default length of hash Property: #6: disabledJavascriptMessage (string) => error message if client’s js is disabled

27 | 28 |

Functions

29 | 30 |

init

public static function init($length =  null,
$action =  null)

function to initialise the csrfProtector work flow

Parameters

$lengthlength of CSRF_AUTH_TOKEN to be generated
$actionint array, for different actions to be taken in case of failed validation

Returns

void

Throws

configFileNotFoundExceptionwhen configuration file is not found
31 | 32 |

useCachedVersion

public static function useCachedVersion()

function to check weather to use cached version of js file or not

Parameters

void

Returns

bool -- true if cacheversion can be used -- false otherwise

33 | 34 |

createNewJsCache

public static function createNewJsCache()

Function to create new cache version of js

Parameters

void

Returns

void

Throws

baseJSFileNotFoundExceptioif baseJsFile is not found
35 | 36 |

authorisePost

public static function authorisePost()

function to authorise incoming post requests

Parameters

void

Returns

void

Throws

logDirectoryNotFoundExceptionif log directory is not found
37 | 38 |

failedValidationAction

private static function failedValidationAction()

function to be called in case of failed validation performs logging and take appropriate action

Parameters

void

Returns

void

39 | 40 |

refreshToken

public static function refreshToken()

Function to set auth cookie

Parameters

void

Returns

void

41 | 42 |

generateAuthToken

public static function generateAuthToken()

function to generate random hash of length as given in parameter max length = 128

Parameters

length to hash required, int

Returns

string, token

43 | 44 |

ob_handler

public static function ob_handler($buffer,
$flags)

Rewrites <form> on the fly to add CSRF tokens to them.  This can also inject our JavaScript library.

Parameters

$bufferoutput buffer to which all output are stored
$flagINT

Return

string, complete output buffer

45 | 46 |

logCSRFattack

private static function logCSRFattack()

Functio to log CSRF Attack

Parameters

void

Retruns

void

Throws

logFileWriteErrorif unable to log an attack
47 | 48 |

getCurrentUrl

private static function getCurrentUrl()

Function to return current url of executing page

Parameters

void

Returns

stringcurrent url
49 | 50 |

isURLallowed

public static function isURLallowed()

Function to check if a url mataches for any urls Listed in config file

Parameters

void

Returns

booleantrue is url need no validation, false if validation needed
51 | 52 |
53 | 54 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 65 |
public static $cookieExpiryTime
expiry time for cookie @var int
private static $isSameOrigin
flag for cross origin/same origin request @var bool
private static $isValidHTML
flag to check if output file is a valid HTML or not @var bool
protected static $requestType
Varaible to store weather request type is post or get @var string
public static $config
config file for CSRFProtector @var int Array, length = 6 Property: #1: failedAuthAction (int) => action to be taken in case autherisation fails Property: #2: logDirectory (string) => directory in which log will be saved Property: #3: customErrorMessage (string) => custom error message to be sent in case of failed authentication Property: #4: jsFile (string) => location of the CSRFProtector js file Property: #5: tokenLength (int) => default length of hash Property: #6: disabledJavascriptMessage (string) => error message if client’s js is disabled
public static function init($length =  null,
$action =  null)
function to initialise the csrfProtector work flow
public static function useCachedVersion()
function to check weather to use cached version of js file or not
public static function createNewJsCache()
Function to create new cache version of js
public static function authorisePost()
function to authorise incoming post requests
private static function failedValidationAction()
function to be called in case of failed validation performs logging and take appropriate action
public static function refreshToken()
Function to set auth cookie
public static function generateAuthToken()
function to generate random hash of length as given in parameter max length = 128
public static function ob_handler($buffer,
$flags)
Rewrites form on the fly to add CSRF tokens to them.
private static function logCSRFattack()
Functio to log CSRF Attack
private static function getCurrentUrl()
Function to return current url of executing page
public static function isURLallowed()
Function to check if a url mataches for any urls Listed in config file
66 | 67 | 68 | 69 | 70 |
Close
71 | 72 | 73 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/index/Files.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | File Index 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
File Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
C
 csrfprotector.php
15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 |
Close
30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/index/Functions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Function Index 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Function Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
A
 authorisePost
C
 createNewJsCache
F
 failedValidationAction
G
 generateAuthToken
 getCurrentUrl
I
 init
 isURLallowed
L
 logCSRFattack
O
 ob_handler
R
 refreshToken
U
 useCachedVersion
15 | 16 |
public static function authorisePost()
function to authorise incoming post requests
17 | 18 | 19 | 20 |
public static function createNewJsCache()
Function to create new cache version of js
21 | 22 | 23 | 24 |
private static function failedValidationAction()
function to be called in case of failed validation performs logging and take appropriate action
25 | 26 | 27 | 28 |
public static function generateAuthToken()
function to generate random hash of length as given in parameter max length = 128
private static function getCurrentUrl()
Function to return current url of executing page
29 | 30 | 31 | 32 |
public static function init($length =  null,
$action =  null)
function to initialise the csrfProtector work flow
public static function isURLallowed()
Function to check if a url mataches for any urls Listed in config file
33 | 34 | 35 | 36 |
private static function logCSRFattack()
Functio to log CSRF Attack
37 | 38 | 39 | 40 |
public static function ob_handler($buffer,
$flags)
Rewrites form on the fly to add CSRF tokens to them.
41 | 42 | 43 | 44 |
public static function refreshToken()
Function to set auth cookie
45 | 46 | 47 | 48 |
public static function useCachedVersion()
function to check weather to use cached version of js file or not
49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | 61 |
Close
62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/index/General.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Index 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
A
 authorisePost
C
$config
$cookieExpiryTime
 createNewJsCache
 csrfprotector.php
F
 failedValidationAction
 Functions
G
 generateAuthToken
 getCurrentUrl
I
 init
$isSameOrigin
 isURLallowed
$isValidHTML
L
 logCSRFattack
O
 ob_handler
R
 refreshToken
$requestType
U
 useCachedVersion
V
 Variables
15 | 16 |
public static function authorisePost()
function to authorise incoming post requests
17 | 18 | 19 | 20 |
public static $config
config file for CSRFProtector @var int Array, length = 6 Property: #1: failedAuthAction (int) => action to be taken in case autherisation fails Property: #2: logDirectory (string) => directory in which log will be saved Property: #3: customErrorMessage (string) => custom error message to be sent in case of failed authentication Property: #4: jsFile (string) => location of the CSRFProtector js file Property: #5: tokenLength (int) => default length of hash Property: #6: disabledJavascriptMessage (string) => error message if client’s js is disabled
public static $cookieExpiryTime
expiry time for cookie @var int
public static function createNewJsCache()
Function to create new cache version of js
21 | 22 | 23 | 24 |
private static function failedValidationAction()
function to be called in case of failed validation performs logging and take appropriate action
25 | 26 | 27 | 28 |
public static function generateAuthToken()
function to generate random hash of length as given in parameter max length = 128
private static function getCurrentUrl()
Function to return current url of executing page
29 | 30 | 31 | 32 |
public static function init($length =  null,
$action =  null)
function to initialise the csrfProtector work flow
private static $isSameOrigin
flag for cross origin/same origin request @var bool
public static function isURLallowed()
Function to check if a url mataches for any urls Listed in config file
private static $isValidHTML
flag to check if output file is a valid HTML or not @var bool
33 | 34 | 35 | 36 |
private static function logCSRFattack()
Functio to log CSRF Attack
37 | 38 | 39 | 40 |
public static function ob_handler($buffer,
$flags)
Rewrites form on the fly to add CSRF tokens to them.
41 | 42 | 43 | 44 |
public static function refreshToken()
Function to set auth cookie
protected static $requestType
Varaible to store weather request type is post or get @var string
45 | 46 | 47 | 48 |
public static function useCachedVersion()
function to check weather to use cached version of js file or not
49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 63 | 64 | 65 |
Close
66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/index/Variables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Variable Index 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Variable Index
$#! · 0-9 · A · B · C · D · E · F · G · H · I · J · K · L · M · N · O · P · Q · R · S · T · U · V · W · X · Y · Z
C
$config
$cookieExpiryTime
I
$isSameOrigin
$isValidHTML
R
$requestType
15 | 16 |
public static $config
config file for CSRFProtector @var int Array, length = 6 Property: #1: failedAuthAction (int) => action to be taken in case autherisation fails Property: #2: logDirectory (string) => directory in which log will be saved Property: #3: customErrorMessage (string) => custom error message to be sent in case of failed authentication Property: #4: jsFile (string) => location of the CSRFProtector js file Property: #5: tokenLength (int) => default length of hash Property: #6: disabledJavascriptMessage (string) => error message if client’s js is disabled
public static $cookieExpiryTime
expiry time for cookie @var int
17 | 18 | 19 | 20 |
private static $isSameOrigin
flag for cross origin/same origin request @var bool
private static $isValidHTML
flag to check if output file is a valid HTML or not @var bool
21 | 22 | 23 | 24 |
protected static $requestType
Varaible to store weather request type is post or get @var string
25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 |
Close
38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/javascript/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mebjas/CSRF-Protector-PHP/df629c87e4b7b5408d32f1692ea8c93fb5ac8e4e/docs/javascript/main.js -------------------------------------------------------------------------------- /docs/javascript/prettify.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mebjas/CSRF-Protector-PHP/df629c87e4b7b5408d32f1692ea8c93fb5ac8e4e/docs/javascript/prettify.js -------------------------------------------------------------------------------- /docs/javascript/searchdata.js: -------------------------------------------------------------------------------- 1 | var indexSectionsWithContent = { 2 | "General": { 3 | "Symbols": false, 4 | "Numbers": false, 5 | "A": true, 6 | "B": false, 7 | "C": true, 8 | "D": false, 9 | "E": false, 10 | "F": true, 11 | "G": true, 12 | "H": false, 13 | "I": true, 14 | "J": false, 15 | "K": false, 16 | "L": true, 17 | "M": false, 18 | "N": false, 19 | "O": true, 20 | "P": false, 21 | "Q": false, 22 | "R": true, 23 | "S": false, 24 | "T": false, 25 | "U": true, 26 | "V": true, 27 | "W": false, 28 | "X": false, 29 | "Y": false, 30 | "Z": false 31 | }, 32 | "Variables": { 33 | "Symbols": false, 34 | "Numbers": false, 35 | "A": false, 36 | "B": false, 37 | "C": true, 38 | "D": false, 39 | "E": false, 40 | "F": false, 41 | "G": false, 42 | "H": false, 43 | "I": true, 44 | "J": false, 45 | "K": false, 46 | "L": false, 47 | "M": false, 48 | "N": false, 49 | "O": false, 50 | "P": false, 51 | "Q": false, 52 | "R": true, 53 | "S": false, 54 | "T": false, 55 | "U": false, 56 | "V": false, 57 | "W": false, 58 | "X": false, 59 | "Y": false, 60 | "Z": false 61 | }, 62 | "Functions": { 63 | "Symbols": false, 64 | "Numbers": false, 65 | "A": true, 66 | "B": false, 67 | "C": true, 68 | "D": false, 69 | "E": false, 70 | "F": true, 71 | "G": true, 72 | "H": false, 73 | "I": true, 74 | "J": false, 75 | "K": false, 76 | "L": true, 77 | "M": false, 78 | "N": false, 79 | "O": true, 80 | "P": false, 81 | "Q": false, 82 | "R": true, 83 | "S": false, 84 | "T": false, 85 | "U": true, 86 | "V": false, 87 | "W": false, 88 | "X": false, 89 | "Y": false, 90 | "Z": false 91 | }, 92 | "Files": { 93 | "Symbols": false, 94 | "Numbers": false, 95 | "A": false, 96 | "B": false, 97 | "C": true, 98 | "D": false, 99 | "E": false, 100 | "F": false, 101 | "G": false, 102 | "H": false, 103 | "I": false, 104 | "J": false, 105 | "K": false, 106 | "L": false, 107 | "M": false, 108 | "N": false, 109 | "O": false, 110 | "P": false, 111 | "Q": false, 112 | "R": false, 113 | "S": false, 114 | "T": false, 115 | "U": false, 116 | "V": false, 117 | "W": false, 118 | "X": false, 119 | "Y": false, 120 | "Z": false 121 | } 122 | } -------------------------------------------------------------------------------- /docs/search/FilesC.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/FunctionsA.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/FunctionsC.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/FunctionsF.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/FunctionsG.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/FunctionsI.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/FunctionsL.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/FunctionsO.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/FunctionsR.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/FunctionsU.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralA.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralC.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralF.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralG.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralI.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralL.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralO.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralR.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralU.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/GeneralV.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/NoResults.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
No Matches
-------------------------------------------------------------------------------- /docs/search/VariablesC.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/VariablesI.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/search/VariablesR.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Loading...
Searching...
No Matches
-------------------------------------------------------------------------------- /docs/styles/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mebjas/CSRF-Protector-PHP/df629c87e4b7b5408d32f1692ea8c93fb5ac8e4e/docs/styles/main.css -------------------------------------------------------------------------------- /js/README.md: -------------------------------------------------------------------------------- 1 | Compatiblity with different browsers 2 | =================================== 3 | **OS: `windows`**
4 | 5 | 6 | Cases | IE (Win) | Opera | Chrome | Mozilla | Safari 7 | ------------------ | ------- | ----- | ------ | ------- | ------ 8 | XHR wrapping | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) 9 | HTML dom-0 wrapping | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) 10 | HTML dom-2 wrapping | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) 11 | URL rewriting | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) | ![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) |![yes](https://cdn3.iconfinder.com/data/icons/fatcow/32/accept.png) 12 | 13 |
Note: Missing tick means, this has not yet been implemented or tested
14 | 15 | 16 | -------------------------------------------------------------------------------- /js/csrfprotector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ================================================================= 3 | * Javascript code for OWASP CSRF Protector 4 | * Task it does: Fetch csrftoken from cookie, and attach it to every 5 | * POST request 6 | * Allowed GET url 7 | * -- XHR 8 | * -- Static Forms 9 | * -- URLS (GET only) 10 | * -- dynamic forms 11 | * ================================================================= 12 | */ 13 | 14 | var CSRFP_FIELD_TOKEN_NAME = 'csrfp_hidden_data_token'; 15 | var CSRFP_FIELD_URLS = 'csrfp_hidden_data_urls'; 16 | 17 | var CSRFP = { 18 | CSRFP_TOKEN: 'CSRFP-Token', 19 | /** 20 | * Array of patterns of url, for which csrftoken need to be added 21 | * In case of GET request also, provided from server 22 | * 23 | * @var {Array} 24 | */ 25 | checkForUrls: [], 26 | /** 27 | * Returns true if the get request doesn't need csrf token. 28 | * 29 | * @param {String} url to check. 30 | * @return {Boolean} true if csrftoken is not needed. 31 | */ 32 | _isValidGetRequest: function (url) { 33 | for (var i = 0; i < CSRFP.checkForUrls.length; i++) { 34 | var match = CSRFP.checkForUrls[i].exec(url); 35 | if (match !== null && match.length > 0) { 36 | return false; 37 | } 38 | } 39 | return true; 40 | }, 41 | /** 42 | * Returns auth key from cookie. 43 | * 44 | * @return {String} auth key from cookie. 45 | */ 46 | _getAuthKey: function () { 47 | var regex = new RegExp(`(?:^|;\s*)${CSRFP.CSRFP_TOKEN}=([^;]+)(;|$)`); 48 | var regexResult = regex.exec(document.cookie); 49 | if (regexResult === null) { 50 | return null; 51 | } 52 | 53 | return regexResult[1]; 54 | }, 55 | /** 56 | * Returns domain name of a url. 57 | * 58 | * @param {String} url - url to check. 59 | * @return {String} domain of the input url. 60 | */ 61 | _getDomain: function (url) { 62 | // TODO(mebjas): add support for other protocols that web supports. 63 | if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { 64 | return document.domain; 65 | } 66 | return /http(s)?:\/\/([^\/]+)/.exec(url)[2]; 67 | }, 68 | /** 69 | * Creates hidden input element with CSRF_TOKEN in it. 70 | * 71 | * @return {HTMLInputElement} hidden input element. 72 | */ 73 | _createHiddenInputElement: function () { 74 | var inputElement = document.createElement('input'); 75 | inputElement.setAttribute('name', CSRFP.CSRFP_TOKEN); 76 | inputElement.setAttribute('class', CSRFP.CSRFP_TOKEN); 77 | inputElement.type = 'hidden'; 78 | inputElement.value = CSRFP._getAuthKey(); 79 | return inputElement; 80 | }, 81 | /** 82 | * Returns absolute url from the input relative components. 83 | * 84 | * @param {String} basePart - base part of the url. 85 | * @param {String} relativePart - relative part of the url. 86 | * @return {String} absolute url. 87 | */ 88 | _createAbsolutePath: function (basePart, relativePart) { 89 | var stack = basePart.split("/"); 90 | var parts = relativePart.split("/"); 91 | stack.pop(); 92 | 93 | for (var i = 0; i < parts.length; i++) { 94 | if (parts[i] === ".") { 95 | continue; 96 | } 97 | if (parts[i] === "..") { 98 | stack.pop(); 99 | } else { 100 | stack.push(parts[i]); 101 | } 102 | } 103 | return stack.join("/"); 104 | }, 105 | /** 106 | * Creates a function wrapper around {@param runnableFunction}, removes 107 | * CSRF Token before calling the function and then put it back. 108 | * 109 | * @param {Function} runnableFunction - function to run. 110 | * @param {Object} htmlFormObject - reference form object. 111 | * @return modified wrapped function. 112 | */ 113 | _createCsrfpWrappedFunction: function (runnableFunction, htmlFormObject) { 114 | return function (event) { 115 | // Remove CSRf token if exists 116 | if (typeof htmlFormObject[CSRFP.CSRFP_TOKEN] !== 'undefined') { 117 | var target = htmlFormObject[CSRFP.CSRFP_TOKEN]; 118 | target.parentNode.removeChild(target); 119 | } 120 | 121 | // Trigger the functions 122 | var result = runnableFunction.apply(this, [event]); 123 | 124 | // Now append the CSRFP-Token back 125 | htmlFormObject.appendChild(CSRFP._createHiddenInputElement()); 126 | return result; 127 | }; 128 | }, 129 | /** 130 | * Initialises the CSRFProtector js script. 131 | */ 132 | _init: function () { 133 | this.CSRFP_TOKEN = document.getElementById( 134 | CSRFP_FIELD_TOKEN_NAME).value; 135 | 136 | try { 137 | var csrfFieldElem = document.getElementById(CSRFP_FIELD_URLS); 138 | this.checkForUrls = JSON.parse(csrfFieldElem.value); 139 | } catch (exception) { 140 | console.error(exception); 141 | console.error('[ERROR] [CSRF Protector] unable to parse blacklisted' 142 | + ` url fields. Exception = ${exception}`); 143 | } 144 | 145 | // Convert the rules received from php library to regex objects. 146 | for (var i = 0; i < CSRFP.checkForUrls.length; i++) { 147 | this.checkForUrls[i] 148 | = this.checkForUrls[i].replace(/\*/g, '(.*)') 149 | .replace(/\//g, "\\/"); 150 | this.checkForUrls[i] = new RegExp(CSRFP.checkForUrls[i]); 151 | } 152 | } 153 | } 154 | 155 | //========================================================== 156 | // Adding tokens, wrappers on window onload 157 | //========================================================== 158 | 159 | function csrfprotector_init() { 160 | 161 | // Call the init function 162 | CSRFP._init(); 163 | 164 | // Basic FORM submit event handler to intercept the form request and attach 165 | // a CSRFP TOKEN if it's not already available. 166 | var basicSubmitInterceptor = function (event) { 167 | if (!event.target[CSRFP.CSRFP_TOKEN]) { 168 | event.target.appendChild(CSRFP._createHiddenInputElement()); 169 | } else { 170 | //modify token to latest value 171 | event.target[CSRFP.CSRFP_TOKEN].value = CSRFP._getAuthKey(); 172 | } 173 | }; 174 | 175 | //================================================================== 176 | // Adding csrftoken to request resulting from
submissions 177 | // Add for each POST, while for mentioned GET request 178 | // TODO - check for method 179 | //================================================================== 180 | // run time binding 181 | document.querySelector('body').addEventListener('submit', function (event) { 182 | if (event.target.tagName.toLowerCase() === 'form') { 183 | basicSubmitInterceptor(event); 184 | } 185 | }); 186 | 187 | //================================================================== 188 | // Adding csrftoken to request resulting from direct form.submit() call 189 | // Add for each POST, while for mentioned GET request 190 | // TODO - check for form method 191 | //================================================================== 192 | HTMLFormElement.prototype.submit_real = HTMLFormElement.prototype.submit; 193 | HTMLFormElement.prototype.submit = function () { 194 | // check if the FORM already contains the token element 195 | if (!this.getElementsByClassName(CSRFP.CSRFP_TOKEN).length) { 196 | this.appendChild(CSRFP._createHiddenInputElement()); 197 | } 198 | this.submit_real(); 199 | }; 200 | 201 | /** 202 | * Add wrapper for HTMLFormElements addEventListener so that any further 203 | * addEventListens won't have trouble with CSRF token 204 | * todo - check for method 205 | */ 206 | HTMLFormElement.prototype.addEventListener_real 207 | = HTMLFormElement.prototype.addEventListener; 208 | HTMLFormElement.prototype.addEventListener = function ( 209 | eventType, func, bubble) { 210 | if (eventType === 'submit') { 211 | var wrappedFunc = CSRFP._createCsrfpWrappedFunction(func, this); 212 | this.addEventListener_real(eventType, wrappedFunc, bubble); 213 | } else { 214 | this.addEventListener_real(eventType, func, bubble); 215 | } 216 | }; 217 | 218 | /** 219 | * Add wrapper for IE's attachEvent 220 | * todo - check for method 221 | * todo - typeof is now obsolete for IE 11, use some other method. 222 | */ 223 | if (HTMLFormElement.prototype.attachEvent) { 224 | HTMLFormElement.prototype.attachEvent_real 225 | = HTMLFormElement.prototype.attachEvent; 226 | HTMLFormElement.prototype.attachEvent = function (eventType, func) { 227 | if (eventType === 'onsubmit') { 228 | var wrappedFunc = CSRFP._createCsrfpWrappedFunction(func, this); 229 | this.attachEvent_real(eventType, wrappedFunc); 230 | } else { 231 | this.attachEvent_real(eventType, func); 232 | } 233 | } 234 | } 235 | 236 | //================================================================== 237 | // Wrapper for XMLHttpRequest & ActiveXObject (for IE 6 & below) 238 | // Set X-No-CSRF to true before sending if request method is 239 | //================================================================== 240 | 241 | /** 242 | * Wrapper to XHR open method 243 | * Add a property method to XMLHttpRequest class 244 | * @param: all parameters to XHR open method 245 | * @return: object returned by default, XHR open method 246 | */ 247 | function new_open(method, url, async, username, password) { 248 | this.method = method; 249 | var isAbsolute = url.indexOf("./") === -1; 250 | if (!isAbsolute) { 251 | var base = location.protocol + '//' + location.host 252 | + location.pathname; 253 | url = CSRFP._createAbsolutePath(base, url); 254 | } 255 | 256 | if (method.toLowerCase() === 'get' && !CSRFP._isValidGetRequest(url)) { 257 | var token = CSRFP._getAuthKey(); 258 | if (url.indexOf('?') === -1) { 259 | url += `?${CSRFP.CSRFP_TOKEN}=${token}` 260 | } else { 261 | url += `&${CSRFP.CSRFP_TOKEN}=${token}`; 262 | } 263 | } 264 | 265 | return this.old_open(method, url, async, username, password); 266 | } 267 | 268 | /** 269 | * Wrapper to XHR send method 270 | * Add query parameter to XHR object 271 | * 272 | * @param: all parameters to XHR send method 273 | * 274 | * @return: object returned by default, XHR send method 275 | */ 276 | function new_send(data) { 277 | if (this.method.toLowerCase() === 'post') { 278 | // attach the token in request header 279 | this.setRequestHeader(CSRFP.CSRFP_TOKEN, CSRFP._getAuthKey()); 280 | } 281 | return this.old_send(data); 282 | } 283 | 284 | if (window.XMLHttpRequest) { 285 | // Wrapping 286 | XMLHttpRequest.prototype.old_send = XMLHttpRequest.prototype.send; 287 | XMLHttpRequest.prototype.old_open = XMLHttpRequest.prototype.open; 288 | XMLHttpRequest.prototype.open = new_open; 289 | XMLHttpRequest.prototype.send = new_send; 290 | } 291 | if (typeof ActiveXObject !== 'undefined') { 292 | ActiveXObject.prototype.old_send = ActiveXObject.prototype.send; 293 | ActiveXObject.prototype.old_open = ActiveXObject.prototype.open; 294 | ActiveXObject.prototype.open = new_open; 295 | ActiveXObject.prototype.send = new_send; 296 | } 297 | //================================================================== 298 | // Rewrite existing urls ( Attach CSRF token ) 299 | // Rules: 300 | // Rewrite those urls which matches the regex sent by Server 301 | // Ignore cross origin urls & internal links (one with hashtags) 302 | // Append the token to those url already containing GET query parameter(s) 303 | // Add the token to those which does not contain GET query parameter(s) 304 | //================================================================== 305 | 306 | for (var i = 0; i < document.links.length; i++) { 307 | document.links[i].addEventListener("mousedown", function (event) { 308 | var href = event.target.href; 309 | if (typeof href !== "string") { 310 | return; 311 | } 312 | var urlParts = href.split('#'); 313 | var url = urlParts[0]; 314 | var hash = urlParts[1]; 315 | 316 | if (CSRFP._getDomain(url).indexOf(document.domain) === -1 317 | || CSRFP._isValidGetRequest(url)) { 318 | //cross origin or not to be protected by rules -- ignore 319 | return; 320 | } 321 | 322 | var token = CSRFP._getAuthKey(); 323 | if (url.indexOf('?') !== -1) { 324 | if (url.indexOf(CSRFP.CSRFP_TOKEN) === -1) { 325 | url += `&${CSRFP.CSRFP_TOKEN}=${token}`; 326 | } else { 327 | var replacementString = `${CSRFP.CSRFP_TOKEN}=${token}$1`; 328 | url = url.replace( 329 | new RegExp(CSRFP.CSRFP_TOKEN + "=.*?(&|$)", 'g'), 330 | replacementString); 331 | } 332 | } else { 333 | url += `?${CSRFP.CSRFP_TOKEN}=${token}`; 334 | } 335 | 336 | event.target.href = url; 337 | if (hash) { 338 | event.target.href += `#${hash}`; 339 | } 340 | }); 341 | } 342 | } 343 | 344 | window.addEventListener("DOMContentLoaded", function () { 345 | csrfprotector_init(); 346 | 347 | // Dispatch an event so clients know the library has initialized 348 | var postCsrfProtectorInit = new Event('postCsrfProtectorInit'); 349 | window.dispatchEvent(postCsrfProtectorInit); 350 | }, false); 351 | -------------------------------------------------------------------------------- /js/index.php: -------------------------------------------------------------------------------- 1 | **Default:** `0` for both `GET` & `POST`): 6 | * `0` Send **403, Forbidden** Header 7 | * `1` **Strip the POST/GET query** and forward the request! unset($_POST) 8 | * `2` **Redirect to custom error page** mentioned in `errorRedirectionPage` 9 | * `3` **Show custom error message** to user, mentioned in `customErrorMessage` 10 | * `4` Send **500, Internal Server Error** header 11 | 12 | - `errorRedirectionPage`: **Absolute url** of the file to which user should be redirected.
**Default: null** 13 | - `customErrorMessage`: **Error Message** to be shown to user. Only this text will be shown!
**Default: null** 14 | - `jsUrl`: **Absolute url** of the js file or `FALSE` if the js file will be added to the page manually. (See [Setting up](https://github.com/mebjas/CSRF-Protector-PHP/wiki/Setting-up-CSRF-Protector-PHP-in-your-web-application) for more information) 15 | - `tokenLength`: length of csrfp token, Default `10` 16 | - `cookieConfig`: Array of parameter values for set cookie method. supports three properties: `path`, `domain`, `secure` and `expire`. They have same meaning as respective parameters of `setcookie` method: [learn more - php.net] 17 | - `disabledJavascriptMessage`: messaged to be shown if js is disabled (string) 18 | - `verifyGetFor`: regex rules for those urls for which csrfp validation should be enabled for `GET` requests also. (View [verifyGetFor rules](https://github.com/mebjas/CSRF-Protector-PHP/wiki/verifyGetFor-rules) for more information) 19 | -------------------------------------------------------------------------------- /libs/config.sample.php: -------------------------------------------------------------------------------- 1 | "", 11 | "failedAuthAction" => array( 12 | "GET" => 0, 13 | "POST" => 0), 14 | "errorRedirectionPage" => "", 15 | "customErrorMessage" => "", 16 | "jsUrl" => "", 17 | "tokenLength" => 10, 18 | "cookieConfig" => array( 19 | "path" => '', 20 | "domain" => '', 21 | "secure" => false, 22 | "expire" => '', 23 | ), 24 | "disabledJavascriptMessage" => "This site attempts to protect users against 25 | Cross-Site Request Forgeries attacks. In order to do so, you must have JavaScript enabled in your web browser otherwise this site will fail to work correctly for you. 26 | See details of your web browser for how to enable JavaScript.", 27 | "verifyGetFor" => array() 28 | ); 29 | -------------------------------------------------------------------------------- /libs/csrf/LoggerInterface.php: -------------------------------------------------------------------------------- 1 | 5 | **Configuration-File:** `../config.php`
6 | **Configuration-Format:** `PHP ARRAY`
7 | -------------------------------------------------------------------------------- /libs/csrf/csrfpAction.php: -------------------------------------------------------------------------------- 1 | path = $cfg['path']; 54 | } 55 | 56 | if (isset($cfg['domain'])) { 57 | $this->domain = $cfg['domain']; 58 | } 59 | 60 | if (isset($cfg['secure'])) { 61 | $this->secure = (bool) $cfg['secure']; 62 | } 63 | 64 | if (isset($cfg['expire']) && $cfg['expire']) { 65 | $this->expire = (int)$cfg['expire']; 66 | } 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /libs/csrf/csrfpDefaultLogger.php: -------------------------------------------------------------------------------- 1 | 51 | */ 52 | private static $cookieConfig = null; 53 | 54 | /** 55 | * Variable: $logger 56 | * Logger class object 57 | * @var LoggerInterface 58 | */ 59 | private static $logger = null; 60 | 61 | /** 62 | * Variable: $tokenHeaderKey 63 | * Key value in header array, which contain the token 64 | * @var string 65 | */ 66 | private static $tokenHeaderKey = null; 67 | 68 | /* 69 | * Variable: $requestType 70 | * Variable to store whether request type is post or get 71 | * @var string 72 | */ 73 | protected static $requestType = "GET"; 74 | 75 | /* 76 | * Variable: $config 77 | * config file for CSRFProtector 78 | * @var int Array, length = 6 79 | * Property: #1: failedAuthAction (int) => action to be taken in case 80 | * autherisation fails. 81 | * Property: #3: customErrorMessage (string) => custom error message to 82 | * be sent in case of failed authentication. 83 | * Property: #4: jsFile (string) => location of the CSRFProtector js 84 | * file. 85 | * Property: #5: tokenLength (int) => default length of hash. 86 | * Property: #6: disabledJavascriptMessage (string) => error message if 87 | * client's js is disabled. 88 | * 89 | * TODO(mebjas): this field should be private 90 | */ 91 | public static $config = array(); 92 | 93 | /* 94 | * Variable: $requiredConfigurations 95 | * Contains list of those parameters that are required to be there 96 | * in config file for csrfp to work 97 | * 98 | * TODO(mebjas): this field should be private 99 | */ 100 | public static $requiredConfigurations = array( 101 | 'failedAuthAction', 'jsUrl', 'tokenLength'); 102 | 103 | /* 104 | * Function: function to initialise the csrfProtector work flow 105 | * 106 | * Parameters: 107 | * $length - (int) length of CSRF_AUTH_TOKEN to be generated. 108 | * $action - (int array), for different actions to be taken in case of 109 | * failed validation. 110 | * $logger - (LoggerInterface) custom logger class object. 111 | * 112 | * Returns: 113 | * void 114 | * 115 | * Throws: 116 | * configFileNotFoundException - when configuration file is not found 117 | * incompleteConfigurationException - when all required fields in config 118 | * file are not available 119 | */ 120 | public static function init($length = null, $action = null, $logger = null) 121 | { 122 | // Check if init has already been called. 123 | if (count(self::$config) > 0) { 124 | throw new alreadyInitializedException("OWASP CSRFProtector: library was already initialized."); 125 | } 126 | 127 | // If mod_csrfp already enabled, no extra verification needed. 128 | if (getenv('mod_csrfp_enabled')) { 129 | return; 130 | } 131 | 132 | // Start session in case its not, and unit test is not going on 133 | if (session_id() == '' && !defined('__CSRFP_UNIT_TEST__')) { 134 | session_start(); 135 | } 136 | 137 | // Load configuration file and properties & Check locally for a 138 | // config.php then check for a config/csrf_config.php file in the 139 | // root folder for composer installations 140 | $standard_config_location = __DIR__ ."/../config.php"; 141 | $composer_config_location = __DIR__ ."/../../../../../config/csrf_config.php"; 142 | 143 | if (file_exists($standard_config_location)) { 144 | self::$config = include($standard_config_location); 145 | } elseif (file_exists($composer_config_location)) { 146 | self::$config = include($composer_config_location); 147 | } else { 148 | throw new configFileNotFoundException( 149 | "OWASP CSRFProtector: configuration file not found for CSRFProtector!"); 150 | } 151 | 152 | // Overriding length property if passed in parameters 153 | if ($length != null) { 154 | self::$config['tokenLength'] = intval($length); 155 | } 156 | 157 | // Action that is needed to be taken in case of failed authorisation 158 | if ($action != null) { 159 | self::$config['failedAuthAction'] = $action; 160 | } 161 | 162 | if (self::$config['CSRFP_TOKEN'] == '') { 163 | self::$config['CSRFP_TOKEN'] = CSRFP_TOKEN; 164 | } 165 | 166 | self::$tokenHeaderKey = 'HTTP_' .strtoupper(self::$config['CSRFP_TOKEN']); 167 | self::$tokenHeaderKey = str_replace('-', '_', self::$tokenHeaderKey); 168 | 169 | // Load parameters for setcookie method 170 | if (!isset(self::$config['cookieConfig'])) { 171 | self::$config['cookieConfig'] = array(); 172 | } 173 | 174 | self::$cookieConfig = new csrfpCookieConfig(self::$config['cookieConfig']); 175 | 176 | // Validate the config if everything is filled out 177 | $missingConfiguration = []; 178 | foreach (self::$requiredConfigurations as $value) { 179 | if (!isset(self::$config[$value]) || self::$config[$value] === '') { 180 | $missingConfiguration[] = $value; 181 | } 182 | } 183 | 184 | if ($missingConfiguration) { 185 | throw new incompleteConfigurationException( 186 | 'OWASP CSRFProtector: Incomplete configuration file: missing ' . 187 | implode(', ', $missingConfiguration) . ' value(s)'); 188 | } 189 | 190 | // Initialize the logger class 191 | if ($logger !== null) { 192 | self::$logger = $logger; 193 | } else { 194 | self::$logger = new csrfpDefaultLogger(); 195 | } 196 | 197 | // Authorise the incoming request 198 | self::authorizePost(); 199 | 200 | // Initialize output buffering handler 201 | if (!defined('__TESTING_CSRFP__')) { 202 | ob_start('csrfProtector::ob_handler'); 203 | } 204 | 205 | if (!isset($_COOKIE[self::$config['CSRFP_TOKEN']]) 206 | || !isset($_SESSION[self::$config['CSRFP_TOKEN']]) 207 | || !is_array($_SESSION[self::$config['CSRFP_TOKEN']]) 208 | || !in_array($_COOKIE[self::$config['CSRFP_TOKEN']], 209 | $_SESSION[self::$config['CSRFP_TOKEN']])) { 210 | self::refreshToken(); 211 | } 212 | } 213 | 214 | /* 215 | * Function: authorizePost 216 | * function to authorise incoming post requests 217 | * 218 | * Parameters: 219 | * void 220 | * 221 | * Returns: 222 | * void 223 | * 224 | * TODO(mebjas): this method should be private. 225 | */ 226 | public static function authorizePost() 227 | { 228 | // TODO(mebjas): this method is valid for same origin request only, 229 | // enable it for cross origin also sometime for cross origin the 230 | // functionality is different. 231 | if ($_SERVER['REQUEST_METHOD'] === 'POST') { 232 | // Set request type to POST 233 | self::$requestType = "POST"; 234 | 235 | // Look for token in payload else from header 236 | $token = self::getTokenFromRequest(); 237 | 238 | // Currently for same origin only 239 | if (!($token && isset($_SESSION[self::$config['CSRFP_TOKEN']]) 240 | && (self::isValidToken($token)))) { 241 | 242 | // Action in case of failed validation 243 | self::failedValidationAction(); 244 | } else { 245 | self::refreshToken(); //refresh token for successful validation 246 | } 247 | } else if (!static::isURLallowed()) { 248 | // Currently for same origin only 249 | if (!(isset($_GET[self::$config['CSRFP_TOKEN']]) 250 | && isset($_SESSION[self::$config['CSRFP_TOKEN']]) 251 | && (self::isValidToken($_GET[self::$config['CSRFP_TOKEN']])))) { 252 | // Action in case of failed validation 253 | self::failedValidationAction(); 254 | } else { 255 | self::refreshToken(); // Refresh token for successful validation 256 | } 257 | } 258 | } 259 | 260 | /* 261 | * Function: getTokenFromRequest 262 | * function to get token in case of POST request 263 | * 264 | * Parameters: 265 | * void 266 | * 267 | * Returns: 268 | * any (string / bool) - token retrieved from header or form payload 269 | */ 270 | private static function getTokenFromRequest() 271 | { 272 | // Look for in $_POST, then header 273 | if (isset($_POST[self::$config['CSRFP_TOKEN']])) { 274 | return $_POST[self::$config['CSRFP_TOKEN']]; 275 | } 276 | 277 | if (function_exists('getallheaders')) { 278 | $requestHeaders = getallheaders(); 279 | if (isset($requestHeaders[self::$config['CSRFP_TOKEN']])) { 280 | return $requestHeaders[self::$config['CSRFP_TOKEN']]; 281 | } 282 | } 283 | 284 | if (self::$tokenHeaderKey === null) { 285 | return false; 286 | } 287 | 288 | if (isset($_SERVER[self::$tokenHeaderKey])) { 289 | return $_SERVER[self::$tokenHeaderKey]; 290 | } 291 | 292 | return false; 293 | } 294 | 295 | /* 296 | * Function: isValidToken 297 | * function to check the validity of token in session array 298 | * Function also clears all tokens older than latest one 299 | * 300 | * Parameters: 301 | * $token - the token sent with GET or POST payload 302 | * 303 | * Returns: 304 | * bool - true if its valid else false 305 | */ 306 | private static function isValidToken($token) 307 | { 308 | if (!isset($_SESSION[self::$config['CSRFP_TOKEN']])) { 309 | return false; 310 | } 311 | 312 | if (!is_array($_SESSION[self::$config['CSRFP_TOKEN']])) { 313 | return false; 314 | } 315 | 316 | foreach ($_SESSION[self::$config['CSRFP_TOKEN']] as $key => $value) { 317 | if ($value == $token) { 318 | // Clear all older tokens assuming they have been consumed 319 | foreach ($_SESSION[self::$config['CSRFP_TOKEN']] as $_key => $_value) { 320 | if ($_value == $token) break; 321 | array_shift($_SESSION[self::$config['CSRFP_TOKEN']]); 322 | } 323 | 324 | return true; 325 | } 326 | } 327 | 328 | return false; 329 | } 330 | 331 | /* 332 | * Function: failedValidationAction 333 | * function to be called in case of failed validation 334 | * performs logging and take appropriate action 335 | * 336 | * Parameters: 337 | * void 338 | * 339 | * Returns: 340 | * void 341 | */ 342 | private static function failedValidationAction() 343 | { 344 | //call the logging function 345 | static::logCSRFattack(); 346 | 347 | // TODO(mebjas): ask mentors if $failedAuthAction is better as an int or string 348 | // default case is case 0 349 | switch (self::$config['failedAuthAction'][self::$requestType]) { 350 | case csrfpAction::ForbiddenResponseAction: 351 | // Send 403 header 352 | header('HTTP/1.0 403 Forbidden'); 353 | exit("

403 Access Forbidden by CSRFProtector!

"); 354 | break; 355 | case csrfpAction::ClearParametersAction: 356 | // Unset the query parameters and forward 357 | if (self::$requestType === 'GET') { 358 | $_GET = array(); 359 | } else { 360 | $_POST = array(); 361 | } 362 | break; 363 | case csrfpAction::RedirectAction: 364 | // Redirect to custom error page 365 | $location = self::$config['errorRedirectionPage']; 366 | header("location: $location"); 367 | exit(self::$config['customErrorMessage']); 368 | break; 369 | case csrfpAction::CustomErrorMessageAction: 370 | // Send custom error message 371 | exit(self::$config['customErrorMessage']); 372 | break; 373 | case csrfpAction::InternalServerErrorResponseAction: 374 | // Send 500 header -- internal server error 375 | header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); 376 | exit("

500 Internal Server Error!

"); 377 | break; 378 | default: 379 | // Unset the query parameters and forward 380 | if (self::$requestType === 'GET') { 381 | $_GET = array(); 382 | } else { 383 | $_POST = array(); 384 | } 385 | break; 386 | } 387 | } 388 | 389 | /* 390 | * Function: refreshToken 391 | * Function to set auth cookie 392 | * 393 | * Parameters: 394 | * void 395 | * 396 | * Returns: 397 | * void 398 | */ 399 | public static function refreshToken() 400 | { 401 | $token = self::generateAuthToken(); 402 | 403 | if (!isset($_SESSION[self::$config['CSRFP_TOKEN']]) 404 | || !is_array($_SESSION[self::$config['CSRFP_TOKEN']])) 405 | $_SESSION[self::$config['CSRFP_TOKEN']] = array(); 406 | 407 | // Set token to session for server side validation 408 | array_push($_SESSION[self::$config['CSRFP_TOKEN']], $token); 409 | 410 | // Set token to cookie for client side processing 411 | if (self::$cookieConfig === null) { 412 | if (!isset(self::$config['cookieConfig'])) 413 | self::$config['cookieConfig'] = array(); 414 | self::$cookieConfig = new csrfpCookieConfig(self::$config['cookieConfig']); 415 | } 416 | 417 | setcookie( 418 | self::$config['CSRFP_TOKEN'], 419 | $token, 420 | time() + self::$cookieConfig->expire, 421 | self::$cookieConfig->path, 422 | self::$cookieConfig->domain, 423 | (bool) self::$cookieConfig->secure); 424 | } 425 | 426 | /* 427 | * Function: generateAuthToken 428 | * function to generate random hash of length as given in parameter 429 | * max length = 128 430 | * 431 | * Parameters: 432 | * length to hash required, int 433 | * 434 | * Returns: 435 | * string, token 436 | */ 437 | public static function generateAuthToken() 438 | { 439 | // TODO(mebjas): Make this a member method / configurable 440 | $randLength = 64; 441 | 442 | // If config tokenLength value is 0 or some non int 443 | if (intval(self::$config['tokenLength']) == 0) { 444 | self::$config['tokenLength'] = 32; //set as default 445 | } 446 | 447 | // TODO(mebjas): if $length > 128 throw exception 448 | 449 | if (function_exists("random_bytes")) { 450 | $token = bin2hex(random_bytes($randLength)); 451 | } elseif (function_exists("openssl_random_pseudo_bytes")) { 452 | $token = bin2hex(openssl_random_pseudo_bytes($randLength)); 453 | } else { 454 | $token = ''; 455 | for ($i = 0; $i < 128; ++$i) { 456 | $r = mt_rand (0, 35); 457 | if ($r < 26) { 458 | $c = chr(ord('a') + $r); 459 | } else { 460 | $c = chr(ord('0') + $r - 26); 461 | } 462 | $token .= $c; 463 | } 464 | } 465 | return substr($token, 0, self::$config['tokenLength']); 466 | } 467 | 468 | /* 469 | * Function: ob_handler 470 | * Rewrites on the fly to add CSRF tokens to them. This can also 471 | * inject our JavaScript library. 472 | * 473 | * Parameters: 474 | * $buffer - output buffer to which all output are stored 475 | * $flag - INT 476 | * 477 | * Return: 478 | * string, complete output buffer 479 | */ 480 | public static function ob_handler($buffer, $flags) 481 | { 482 | // Even though the user told us to rewrite, we should do a quick heuristic 483 | // to check if the page is *actually* HTML. We don't begin rewriting until 484 | // we hit the first message to outgoing HTML output, 500 | // informing the user to enable js for CSRFProtector to work 501 | // best section to add, after tag 502 | $buffer = preg_replace("/]*>/", "$0 ", $buffer); 504 | 505 | $hiddenInput = '' .PHP_EOL; 507 | 508 | $hiddenInput .= ''; 510 | 511 | // Implant hidden fields with check url information for reading in javascript 512 | $buffer = str_ireplace('', $hiddenInput . '', $buffer); 513 | 514 | if (self::$config['jsUrl']) { 515 | // Implant the CSRFGuard js file to outgoing script 516 | $script = ''; 517 | $buffer = str_ireplace('', $script . PHP_EOL . '', $buffer, $count); 518 | 519 | // Add the script to the end if the body tag was not closed 520 | if (!$count) { 521 | $buffer .= $script; 522 | } 523 | } 524 | 525 | return $buffer; 526 | } 527 | 528 | /* 529 | * Function: logCSRFattack 530 | * Function to log CSRF Attack 531 | * 532 | * Parameters: 533 | * void 534 | * 535 | * Returns: 536 | * void 537 | * 538 | * Throws: 539 | * logFileWriteError - if unable to log an attack 540 | */ 541 | protected static function logCSRFattack() 542 | { 543 | //miniature version of the log 544 | $context = array(); 545 | $context['HOST'] = $_SERVER['HTTP_HOST']; 546 | $context['REQUEST_URI'] = $_SERVER['REQUEST_URI']; 547 | $context['requestType'] = self::$requestType; 548 | $context['cookie'] = $_COOKIE; 549 | self::$logger->log( 550 | "OWASP CSRF PROTECTOR VALIDATION FAILURE", $context); 551 | } 552 | 553 | /* 554 | * Function: getCurrentUrl 555 | * Function to return current url of executing page 556 | * 557 | * Parameters: 558 | * void 559 | * 560 | * Returns: 561 | * string - current url 562 | */ 563 | private static function getCurrentUrl() 564 | { 565 | $request_scheme = 'https'; 566 | if (isset($_SERVER['REQUEST_SCHEME'])) { 567 | $request_scheme = $_SERVER['REQUEST_SCHEME']; 568 | } else { 569 | if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') { 570 | $request_scheme = 'https'; 571 | } else { 572 | $request_scheme = 'http'; 573 | } 574 | } 575 | 576 | return $request_scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']; 577 | } 578 | 579 | /* 580 | * Function: isURLallowed 581 | * Function to check if a url matches for any urls 582 | * Listed in config file 583 | * 584 | * Parameters: 585 | * void 586 | * 587 | * Returns: 588 | * boolean - true is url need no validation, false if validation needed 589 | */ 590 | public static function isURLallowed() { 591 | foreach (self::$config['verifyGetFor'] as $key => $value) { 592 | $value = str_replace(array('/','*'), array('\/','(.*)'), $value); 593 | preg_match('/' .$value .'/', self::getCurrentUrl(), $output); 594 | if (count($output) > 0) { 595 | return false; 596 | } 597 | } 598 | 599 | return true; 600 | } 601 | }; 602 | } 603 | -------------------------------------------------------------------------------- /libs/csrf/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ./test/csrfprotector_test.php 9 | ./test/csrfprotector_test_customlogger.php 10 | 11 | 12 | 13 | libs/csrf/csrfprotector.php 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | CSRF Protector 2 | ========================== 3 | [![Todo Status](http://todofy.org/b/mebjas/CSRF-Protector-PHP)](http://todofy.org/r/mebjas/CSRF-Protector-PHP) 4 | [![Build Status](https://travis-ci.org/mebjas/CSRF-Protector-PHP.svg?branch=master)](https://travis-ci.org/mebjas/CSRF-Protector-PHP) 5 | [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%205.4-8892BF.svg)](https://php.net/) 6 |
CSRF protector php, a standalone php library for csrf mitigation in web applications. Easy to integrate in any php web app. 7 | 8 | # Add to your project using packagist 9 | Add a `composer.json` file to your project directory 10 | ```json 11 | { 12 | "require": { 13 | "owasp/csrf-protector-php": "dev-master" 14 | } 15 | } 16 | ``` 17 | Then open terminal (or command prompt), move to project directory and run 18 | ```shell 19 | composer install 20 | 21 | ## Or alternatively 22 | 23 | php composer.phar install 24 | ``` 25 | This will add CSRFP (library will be downloaded at `./vendor/owasp/csrf-protector-php`) to your project directory. View [packagist.org](https://packagist.org/) for more help with composer! 26 | 27 | # Configuration 28 | For composer installations: Copy the config.sample.php file into your root folder at config/csrf_config.php 29 | For non-composer installations: Copy the `libs/csrf/config.sample.php` file into `libs/csrf/config.php` 30 | Edit config accordingly. See Detailed Information link below. 31 | 32 | [Link to wiki - Editing Configurations & Mandatory requirements before using this library](https://github.com/mebjas/CSRF-Protector-PHP/wiki/Configurations) 33 | 34 | # How to use 35 | ```php 36 | then fork > and then send a pull request to `master branch`. 67 | 68 | ## FAQ: 69 | 1. What happens if token expires? - https://github.com/mebjas/CSRF-Protector-PHP/wiki/what-if-token-expires 70 | 2. Secure flag in a cookie? - https://github.com/mebjas/CSRF-Protector-PHP/issues/54 71 | 3. \[Deprecated\] ~NoJS support? - https://github.com/mebjas/CSRF-Protector-PHP/tree/nojs-support~ 72 | -------------------------------------------------------------------------------- /test/config.test.php: -------------------------------------------------------------------------------- 1 | "CSRFP-Token", 11 | "failedAuthAction" => array( 12 | "GET" => 0, 13 | "POST" => 0), 14 | "errorRedirectionPage" => "", 15 | "customErrorMessage" => "", 16 | "jsUrl" => "http://localhost/csrfp/js/csrfprotector.js", 17 | "tokenLength" => 10, 18 | "cookieConfig" => array( 19 | "path" => '', 20 | "domain" => '', 21 | "secure" => false, 22 | "expire" => '', 23 | ), 24 | "disabledJavascriptMessage" => "sample error message", 25 | "verifyGetFor" => array() 26 | ); 27 | -------------------------------------------------------------------------------- /test/config.testInit_incompleteConfigurationException.php: -------------------------------------------------------------------------------- 1 | "CSRFP-Token", 11 | "errorRedirectionPage" => "", 12 | "customErrorMessage" => "", 13 | "cookieConfig" => array( 14 | "path" => '', 15 | "domain" => '', 16 | "secure" => false, 17 | "expire" => '', 18 | ), 19 | "disabledJavascriptMessage" => "sample error message", 20 | "verifyGetFor" => array() 21 | ); 22 | -------------------------------------------------------------------------------- /test/config.testInit_withoutInjectedCSRFGuardScript.php: -------------------------------------------------------------------------------- 1 | "CSRFP-Token", 11 | "failedAuthAction" => array( 12 | "GET" => 0, 13 | "POST" => 0), 14 | "errorRedirectionPage" => "", 15 | "customErrorMessage" => "", 16 | "jsUrl" => false, 17 | "tokenLength" => 10, 18 | "cookieConfig" => array( 19 | "path" => '', 20 | "domain" => '', 21 | "secure" => false, 22 | "expire" => '', 23 | ), 24 | "disabledJavascriptMessage" => "sample error message", 25 | "verifyGetFor" => array() 26 | ); 27 | -------------------------------------------------------------------------------- /test/csrfprotector_test.php: -------------------------------------------------------------------------------- 1 | = 7 11 | && !class_exists('\PHPUnit_Framework_TestCase', true)) { 12 | class_alias('\PHPUnit\Framework\TestCase', '\PHPUnit_Framework_TestCase'); 13 | } 14 | 15 | /** 16 | * main test class 17 | */ 18 | class csrfp_test extends PHPUnit_Framework_TestCase { 19 | /** 20 | * @var array to hold current configurations 21 | */ 22 | protected $config = array(); 23 | 24 | /** 25 | * Function to be run before every test*() functions. 26 | */ 27 | public function setUp() { 28 | csrfprotector::$config['CSRFP_TOKEN'] = 'CSRFP-Token'; 29 | csrfprotector::$config['cookieConfig'] = array('secure' => false); 30 | 31 | $_SERVER['REQUEST_URI'] = 'temp'; // For logging 32 | $_SERVER['REQUEST_SCHEME'] = 'http'; // For authorizePost 33 | $_SERVER['HTTP_HOST'] = 'test'; // For isUrlAllowed 34 | $_SERVER['PHP_SELF'] = '/index.php'; // For authorizePost 35 | $_POST[csrfprotector::$config['CSRFP_TOKEN']] 36 | = $_GET[csrfprotector::$config['CSRFP_TOKEN']] = '123'; 37 | 38 | //token mismatch - leading to failed validation 39 | $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('abc'); 40 | $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; 41 | $_SERVER['HTTPS'] = null; 42 | 43 | $this->config = include(__DIR__ .'/config.test.php'); 44 | 45 | // Create an instance of config file -- for testing 46 | $data = file_get_contents(__DIR__ .'/config.test.php'); 47 | file_put_contents(__DIR__ .'/../libs/config.php', $data); 48 | 49 | if (!defined('__CSRFP_UNIT_TEST__')) define('__CSRFP_UNIT_TEST__', true); 50 | } 51 | 52 | /** 53 | * tearDown() 54 | */ 55 | public function tearDown() { 56 | unlink(__DIR__ .'/../libs/config.php'); 57 | } 58 | 59 | /** 60 | * Function to check refreshToken() functionality 61 | */ 62 | public function testRefreshToken() { 63 | $val = $_COOKIE[csrfprotector::$config['CSRFP_TOKEN']] = '123abcd'; 64 | $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('123abcd'); 65 | csrfProtector::$config['tokenLength'] = 20; 66 | csrfProtector::refreshToken(); 67 | 68 | $this->assertTrue( 69 | strcmp($val, $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][1]) != 0); 70 | 71 | $this->assertTrue(csrfp_wrapper::checkHeader('Set-Cookie')); 72 | $this->assertTrue(csrfp_wrapper::checkHeader('CSRFP-Token')); 73 | $this->assertTrue( 74 | csrfp_wrapper::checkHeader( 75 | $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][1])); 76 | } 77 | 78 | /** 79 | * Function to check cookieConfig class 80 | */ 81 | public function testCookieConfigClass() { 82 | $cfg = array( 83 | "path" => "abcd", 84 | "secure" => true, 85 | "domain" => "abcd", 86 | "expire" => 600, 87 | ); 88 | 89 | // simple test 90 | $cookieConfig = new csrfpCookieConfig($cfg); 91 | $this->assertEquals("abcd", $cookieConfig->path); 92 | $this->assertEquals("abcd", $cookieConfig->domain); 93 | $this->assertEquals(true, $cookieConfig->secure); 94 | $this->assertEquals(600, $cookieConfig->expire); 95 | 96 | // default value test 97 | $cookieConfig = new csrfpCookieConfig(array()); 98 | $this->assertEquals('', $cookieConfig->path); 99 | $this->assertEquals('', $cookieConfig->domain); 100 | $this->assertEquals(false, $cookieConfig->secure); 101 | $this->assertEquals(1800, $cookieConfig->expire); 102 | 103 | // secure as string 104 | $cookieConfig = new csrfpCookieConfig(array('secure' => 'true')); 105 | $this->assertEquals(true, $cookieConfig->secure); 106 | $cookieConfig = new csrfpCookieConfig(array('secure' => 'false')); 107 | $this->assertEquals(true, $cookieConfig->secure); 108 | 109 | // expire as string 110 | $cookieConfig = new csrfpCookieConfig(array('expire' => '600')); 111 | $this->assertEquals(600, $cookieConfig->expire); 112 | $cookieConfig = new csrfpCookieConfig(array('expire' => '')); 113 | $this->assertEquals(1800, $cookieConfig->expire); 114 | } 115 | 116 | /** 117 | * test secure flag is set in the token cookie when requested 118 | */ 119 | public function testSecureCookie() { 120 | $_SERVER['REQUEST_METHOD'] = 'POST'; 121 | $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('123abcd'); 122 | csrfProtector::$config['tokenLength'] = 20; 123 | 124 | // this one would generally fails, as init was already called and now private static 125 | // property is set with secure as false; 126 | $csrfp = new csrfProtector; 127 | $reflection = new \ReflectionClass(get_class($csrfp)); 128 | $property = $reflection->getProperty('cookieConfig'); 129 | $property->setAccessible(true); 130 | 131 | // change value to false 132 | $property->setValue($csrfp, new csrfpCookieConfig(array('secure' => false))); 133 | csrfprotector::refreshToken(); 134 | $this->assertNotRegExp('/; secure/', csrfp_wrapper::getHeaderValue('Set-Cookie')); 135 | 136 | // change value to true 137 | $property->setValue($csrfp, new csrfpCookieConfig(array('secure' => true))); 138 | csrfprotector::refreshToken(); 139 | $this->assertRegExp('/; secure/', csrfp_wrapper::getHeaderValue('Set-Cookie')); 140 | } 141 | 142 | /** 143 | * test secure flag is set in the token cookie when requested 144 | */ 145 | public function testCookieExpireTime() { 146 | $_SERVER['REQUEST_METHOD'] = 'POST'; 147 | $_SESSION[csrfprotector::$config['CSRFP_TOKEN']] = array('123abcd'); 148 | csrfProtector::$config['tokenLength'] = 20; 149 | 150 | // this one would generally fails, as init was already called and now private static 151 | // property is already set; 152 | $csrfp = new csrfProtector; 153 | $reflection = new \ReflectionClass(get_class($csrfp)); 154 | $property = $reflection->getProperty('cookieConfig'); 155 | $property->setAccessible(true); 156 | 157 | // change value to 600 158 | $property->setValue($csrfp, new csrfpCookieConfig(array('expire' => 600))); 159 | csrfprotector::refreshToken(); 160 | // Check the expire date to the nearest minute in case the seconds does not match during test execution 161 | $this->assertRegExp('/; expires=' . date('D, d-M-Y H:i', time() + 600) . ':\d\d GMT;?/', csrfp_wrapper::getHeaderValue('Set-Cookie')); 162 | if(version_compare(phpversion(), '5.5', '>=')) { 163 | $this->assertRegExp('/; Max-Age=600/', csrfp_wrapper::getHeaderValue('Set-Cookie')); 164 | } 165 | } 166 | 167 | /** 168 | * test authorise post -> action = 403, forbidden 169 | */ 170 | public function testAuthorisePost_failedAction_1() { 171 | $_SERVER['REQUEST_METHOD'] = 'POST'; 172 | csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); 173 | csrfprotector::$config['failedAuthAction']['POST'] = 0; 174 | csrfprotector::$config['failedAuthAction']['GET'] = 0; 175 | 176 | //csrfprotector::authorizePost(); 177 | $this->markTestSkipped('Cannot add tests as code exit here'); 178 | 179 | $_SERVER['REQUEST_METHOD'] = 'GET'; 180 | csrfp_wrapper::changeRequestType('GET'); 181 | //csrfprotector::authorizePost(); 182 | 183 | $this->markTestSkipped('Cannot add tests as code exit here'); 184 | } 185 | 186 | /** 187 | * test authorise post -> strip $_GET, $_POST 188 | */ 189 | public function testAuthorisePost_failedAction_2() { 190 | $_SERVER['REQUEST_METHOD'] = 'POST'; 191 | $csrfp = new csrfProtector; 192 | $fakeLogger = $this->setFakeLogger($csrfp); 193 | 194 | csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); 195 | csrfprotector::$config['failedAuthAction']['POST'] = 1; 196 | csrfprotector::$config['failedAuthAction']['GET'] = 1; 197 | 198 | $_POST = array('param1' => 1, 'param2' => 2); 199 | csrfprotector::authorizePost(); 200 | $this->assertEmpty($_POST); 201 | 202 | $_SERVER['REQUEST_METHOD'] = 'GET'; 203 | csrfp_wrapper::changeRequestType('GET'); 204 | $_GET = array('param1' => 1, 'param2' => 2); 205 | 206 | csrfprotector::authorizePost(); 207 | $this->assertEmpty($_GET); 208 | } 209 | 210 | /** 211 | * test authorise post -> redirect 212 | */ 213 | public function testAuthorisePost_failedAction_3() { 214 | $_SERVER['REQUEST_METHOD'] = 'POST'; 215 | 216 | csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); 217 | csrfprotector::$config['errorRedirectionPage'] = 'http://test'; 218 | csrfprotector::$config['failedAuthAction']['POST'] = 2; 219 | csrfprotector::$config['failedAuthAction']['GET'] = 2; 220 | 221 | //csrfprotector::authorizePost(); 222 | $this->markTestSkipped('Cannot add tests as code exit here'); 223 | 224 | $_SERVER['REQUEST_METHOD'] = 'GET'; 225 | csrfp_wrapper::changeRequestType('GET'); 226 | //csrfprotector::authorizePost(); 227 | $this->markTestSkipped('Cannot add tests as code exit here'); 228 | } 229 | 230 | /** 231 | * test authorise post -> error message & exit 232 | */ 233 | public function testAuthorisePost_failedAction_4() { 234 | $_SERVER['REQUEST_METHOD'] = 'POST'; 235 | 236 | csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); 237 | csrfprotector::$config['customErrorMessage'] = 'custom error message'; 238 | csrfprotector::$config['failedAuthAction']['POST'] = 3; 239 | csrfprotector::$config['failedAuthAction']['POST'] = 3; 240 | 241 | //csrfprotector::authorizePost(); 242 | $this->markTestSkipped('Cannot add tests as code exit here'); 243 | 244 | $_SERVER['REQUEST_METHOD'] = 'GET'; 245 | csrfp_wrapper::changeRequestType('GET'); 246 | //csrfprotector::authorizePost(); 247 | $this->markTestSkipped('Cannot add tests as code exit here'); 248 | } 249 | 250 | /** 251 | * test authorise post -> 500 internal server error 252 | */ 253 | public function testAuthorisePost_failedAction_5() { 254 | $_SERVER['REQUEST_METHOD'] = 'POST'; 255 | 256 | csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); 257 | csrfprotector::$config['failedAuthAction']['POST'] = 4; 258 | csrfprotector::$config['failedAuthAction']['GET'] = 4; 259 | 260 | //csrfprotector::authorizePost(); 261 | //$this->markTestSkipped('Cannot add tests as code exit here'); 262 | 263 | $_SERVER['REQUEST_METHOD'] = 'GET'; 264 | csrfp_wrapper::changeRequestType('GET'); 265 | //csrfprotector::authorizePost(); 266 | //csrfp_wrapper::checkHeader('500'); 267 | //$this->markTestSkipped('Cannot add tests as code exit here'); 268 | } 269 | 270 | /** 271 | * test authorise post -> default action: strip $_GET, $_POST 272 | */ 273 | public function testAuthorisePost_failedAction_6() { 274 | $_SERVER['REQUEST_METHOD'] = 'POST'; 275 | $csrfp = new csrfProtector; 276 | $fakeLogger = $this->setFakeLogger($csrfp); 277 | 278 | csrfprotector::$config['verifyGetFor'] = array('http://test/index*'); 279 | csrfprotector::$config['failedAuthAction']['POST'] = 10; 280 | csrfprotector::$config['failedAuthAction']['GET'] = 10; 281 | 282 | $_POST = array('param1' => 1, 'param2' => 2); 283 | csrfprotector::authorizePost(); 284 | $this->assertEmpty($_POST); 285 | 286 | $_SERVER['REQUEST_METHOD'] = 'GET'; 287 | csrfp_wrapper::changeRequestType('GET'); 288 | $_GET = array('param1' => 1, 'param2' => 2); 289 | 290 | csrfprotector::authorizePost(); 291 | $this->assertEmpty($_GET); 292 | } 293 | 294 | /** 295 | * test authorise success with token in $_POST 296 | */ 297 | public function testAuthorisePost_success() { 298 | $_SERVER['REQUEST_METHOD'] = 'POST'; 299 | $_POST[csrfprotector::$config['CSRFP_TOKEN']] 300 | = $_GET[csrfprotector::$config['CSRFP_TOKEN']] 301 | = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0]; 302 | $temp = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']]; 303 | 304 | csrfprotector::authorizePost(); //will create new session and cookies 305 | $this->assertFalse($temp == $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0]); 306 | $this->assertTrue(csrfp_wrapper::checkHeader('Set-Cookie')); 307 | $this->assertTrue(csrfp_wrapper::checkHeader('CSRFP-Token')); 308 | // $this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0])); // Combine these 3 later 309 | 310 | // For get method 311 | $_SERVER['REQUEST_METHOD'] = 'GET'; 312 | csrfp_wrapper::changeRequestType('GET'); 313 | $_POST[csrfprotector::$config['CSRFP_TOKEN']] 314 | = $_GET[csrfprotector::$config['CSRFP_TOKEN']] 315 | = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0]; 316 | $temp = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']]; 317 | 318 | csrfprotector::authorizePost(); //will create new session and cookies 319 | $this->assertFalse($temp == $_SESSION[csrfprotector::$config['CSRFP_TOKEN']]); 320 | $this->assertTrue(csrfp_wrapper::checkHeader('Set-Cookie')); 321 | $this->assertTrue(csrfp_wrapper::checkHeader('CSRFP-Token')); 322 | // $this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0])); // Combine these 3 later 323 | } 324 | 325 | /** 326 | * test authorise success with token in header 327 | */ 328 | public function testAuthorisePost_success_2() { 329 | unset($_POST[csrfprotector::$config['CSRFP_TOKEN']]); 330 | $_SERVER['REQUEST_METHOD'] = 'POST'; 331 | $serverKey = 'HTTP_' .strtoupper(csrfprotector::$config['CSRFP_TOKEN']); 332 | $serverKey = str_replace('-', '_', $serverKey); 333 | 334 | $csrfp = new csrfProtector; 335 | $reflection = new \ReflectionClass(get_class($csrfp)); 336 | $property = $reflection->getProperty('tokenHeaderKey'); 337 | $property->setAccessible(true); 338 | // change value to false 339 | $property->setValue($csrfp, $serverKey); 340 | 341 | $_SERVER[$serverKey] = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0]; 342 | $temp = $_SESSION[csrfprotector::$config['CSRFP_TOKEN']]; 343 | 344 | csrfprotector::authorizePost(); //will create new session and cookies 345 | $this->assertFalse($temp == $_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0]); 346 | $this->assertTrue(csrfp_wrapper::checkHeader('Set-Cookie')); 347 | $this->assertTrue(csrfp_wrapper::checkHeader('CSRFP-Token')); 348 | // $this->assertTrue(csrfp_wrapper::checkHeader($_SESSION[csrfprotector::$config['CSRFP_TOKEN']][0])); // Combine these 3 later 349 | 350 | } 351 | 352 | /** 353 | * test for generateAuthToken() 354 | */ 355 | public function testGenerateAuthToken() { 356 | csrfprotector::$config['tokenLength'] = 20; 357 | $token1 = csrfprotector::generateAuthToken(); 358 | $token2 = csrfprotector::generateAuthToken(); 359 | 360 | $this->assertFalse($token1 == $token2); 361 | $this->assertEquals(20, strlen($token1)); 362 | $this->assertRegExp('/^[a-z0-9]{20}$/', $token1); 363 | 364 | csrfprotector::$config['tokenLength'] = 128; 365 | $token = csrfprotector::generateAuthToken(); 366 | $this->assertEquals(128, strlen($token)); 367 | $this->assertRegExp('/^[a-z0-9]{128}$/', $token); 368 | } 369 | 370 | /** 371 | * test ob_handler_function 372 | */ 373 | public function testob_handler() { 374 | csrfprotector::$config['verifyGetFor'] = array(); 375 | csrfprotector::$config['disabledJavascriptMessage'] = 'test message'; 376 | csrfprotector::$config['jsUrl'] = 'http://localhost/test/csrf/js/csrfprotector.js'; 377 | 378 | $testHTML = ''; 379 | $testHTML .= '1'; 380 | $testHTML .= ''; 381 | $testHTML .= '-- some static content --'; 382 | $testHTML .= '-- some static content --'; 383 | $testHTML .= ''; 384 | $testHTML .= ''; 385 | 386 | $modifiedHTML = csrfprotector::ob_handler($testHTML, 0); 387 | $inpLength = strlen($testHTML); 388 | $outLength = strlen($modifiedHTML); 389 | 390 | //Check if file has been modified 391 | $this->assertNotEquals($inpLength, $outLength); 392 | $this->assertContains('