PHP CSRF sitewide solution - Is it good enough - csrf

I have a PHP based document management system that I wrote a while back to store my families paperwork. Although the full code is not publicly available it would still be a good idea to protect it against CSRF attacks.
Would this be a suitable solution for implementing sitewide CSRF protection:
All forms are protected against being directly accessed, so they all have to be accessed via index.php, this is done with the following code at the top of each form PHP file:
if(basename(__FILE__) == basename($_SERVER['PHP_SELF'])){
header("Location: ../../");
}
In the index.php file, which every form must go through to be accessed I have the following code to create a token session if it doesn't exist and then check for any post data containing a token (this is done before any possible calls to a form):
// Generate session token for CSRF
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
if (isset($_POST['token'])) {
if (!hash_equals($_SESSION['token'], $_POST['token'])) {
echo 'There is a problem with your session token.';
exit;
}
}
Form validation contains the following code to check that a token session has been set to ensure the session checking on index.php has been ran:
if (isset($_POST['token']) && $_POST['token'] == $_SESSION['token']) {
// Process form data
}
Finally, each form would include a hidden element like so:
<input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>" />
As far as I can see this should be suitable, but I am not an expert on CSRF PHP attacks.
My questions are:
1) Is the first block of code a suitable method of blocking access to the file, other than being included in another PHP file on the same server?
2) Is this method of implementing a token suitable to stop CSRF attacks?

Related

Google storage upload files in javascript

I'm looking for the absolute minimal requirements to upload a file to a storage bucket. And the minimal required code.
The minimal oAuth/API-key information I have is from console.cloud.google.com/apis/credentials/oauthclient/
clientId: Client ID from Credentials page.
apiKey: Client secret from Credentials page?
scopes: 'https://www.googleapis.com/auth/devstorage.read_write' (Why is it called devstorage? As if it's intended only for devs? Or is it short for device?
I'm not sure if the apiKey is right, but using this from an example doesn't throw an error:
// This is triggered after the `client.js` loads
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}
So, that's step one, but correct me if I'm wrong.
Now to handle Auth Result:
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
makeApiCall();
}
}
Here I'm kind of stuck, there's an example I'm working off: https://developers.google.com/api-client-library/javascript/start/start-js
There it stops working when it gets to gapi.client.plus.people, where people is not defined. This is because I have an anonymous user, and sort of a public API-key. Later I will implement per user ACL, but for now I just need it to work/upload.
Now, I've got something similar working on Amazon Cloud, with a simple jQuery-based widget that can upload files, where I only needed to input their API-key and the bucket name basically (which was in PHP unfortunately).
I would be happy with just a simple <form>, but the examples I encounter have a lot more information/fields in them than the above 3 minimal data (clientId, apiKey, scopes) and the additional url to the bucket (like strange encrypted acl strings)
I understand that bucket-name.storage.googleapis.com is where the files end up, and that works when I manually upload images.
I'm now looking for the absolute minimal piece of code, preferably javascript, using the google client so using something like (which I found in another example):
gapi.client.request({
'path': '/upload/storage/' + API_VERSION + '/b/' + BUCKET + '/o',
'method': 'POST',
'params': {'uploadType': 'media'}
'headers': {
'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
}
Do I need API_VERSION? How do I find out which path to use? I know my bucket name, but where did the upload/storage/ come from? and /b/? (although https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload shows that I need it)
The example used a header with a boundary but I cannot find anything about that, and I probably also don't need that. Problem is I can't find any minimally required headers.
I came across 'x-goog-acl': 'public-read', does this need to be in the headers?
So to sum up, the questions I'd like answered:
What headers would I minimally need to upload to a bucket with allUsers access set to Owner ( I know, not the best idea, this will change in the future)
Any example code of how to get a file from an html form, and either use ajax to send the request, or use the gapi, which I think is meant to do that.
I hope my problem is clear, I basically don't know where to start/find the right code examples. Although this might have to do with the fact the google javascript client api is in beta..
What headers would I minimally need to upload to a bucket with allUsers access set to Owner ( I know, not the best idea, this will change in the future).
You are correct! This is not a good idea. If anonymous users own your bucket, that means they can delete any object in it, overwrite an existing objects, and otherwise cause expensive mischief. Nonetheless, the answer to your question:
<form action="https://storage.googleapis.com/YOUR_BUCKET_NAME"
method="post" enctype="multipart/form-data">
<input name="key" type="text" value="objectName.txt" /><br/>
<input name="file" type="file" /><br/>
<input type="submit" value="Upload!" />
</form>
This is the absolute minimum required to upload an object. Note that it doesn't redirect you anywhere or otherwise indicate success. For that, you'd want to use the success_action_redirect parameter, and you asked for the absolute minimum.

The "state" param from the URL and session do not match

In facebook documantion
require('include/facebook/autoload.php'); //SDK directory
$fb = new Facebook\Facebook([
'app_id' => '***********',
'app_secret' => '***********************'
]);
$helper = $fb->getRedirectLoginHelper();
$permissions = ['email', 'public_profile']; // optional
$loginUrl = $helper->getLoginUrl('http://www.meusite.com.br/login-callback.php', $permissions);
When direct it to the url $loginUrl, the return is:
Facebook SDK returned an error: Cross-site request forgery validation failed. The "state" param from the URL and session do not match
I had the same error.
The problem occurred because I did getLoginUrl(...) before getAccessToken()
So rid of getLoginUrl(...) in redirected URL and code should works.
I had the same issue and for me that error was occurring because I did not put session_start(); in my login.php page code before calling getLoginUrl(..) and also at the top of login-callback.php page.
Just put session_start(); in your "login" page and "login-callback" page and it will work surely just like it is working for me now.
There could be 2 reason for this error:
you didn't call session_start(); before getLoginUrl call
You executed getLoginUrl again in login-callback.php, so state value regenerated and mismatched with the redirected value
Possible Fixes : I used the following configuration settings .
Enable WebAuthLogin under the advanced tab . Provide the url in the WebAuthLogin settins as same as that you provide in $loginUrl ;
For example if you use $loginUrl as https://example.com/ use that same in the WebAuthlogin Url
$loginUrl = $helper->getLoginUrl('https://example.com/', $permissions);
This problem occures also in case that you generate 2 or more login links on the same page (e.g. one for login and other for registration - even both point to the same url, they have just different labels).
Facebook SDK creates/updates $_SESSION[FBRLH_state] for each new generated loginURL. So if there are 2 generated URLs (using $helper->getLoginUrl()) then the $_SESSION[FBRLH_state] is 2-times rewritten and valid only for the last generated URL. Previous login URL becomes invalid. It means that it is not possible to generate 2 valid loginURLs. In case that 2 same URLs are generated then return the first one and avoid call of Facebook SDK for generation of second one.
I had the same problem.
The reason for this error is because --->
When "$helper->getLoginUrl" calls, it create a session variable "FB_State", and this is something to FB uses to match the token. Every-time getLoginUrl calls, it create new state. Then after user authorized and redirect back, if you codes cannot detect this event and re-run "$helper->getLoginUrl", then this error will occur.
The solution ->
refine your coding, stop run "$helper->getLoginUrl" again if authorized.
if you already rerun, then set the session variable for the token to NULL if you have, then User can re-authorize again.
when user tries re-authorize, they can remove the authorized APP once or you need to generate new link with "$helper->getReRequestUrl"
Yet, token has be called by "getAccessToken()" before the "$helper->getLoginUrl" or "$helper->getReRequestUrl" runs.
Good Luck!!!!!
Finally, looking into FB code, I discovered that the problem "Cross-site request forgery validation failed. Required param “state” missing" and similars are caused by PHP variable $_SESSION['FBRLH_state'] that for some "strange" reason when FB call the login-callback file.
To solve it I store this variable "FBRLH_state" AFTER the call of function $helper->getLoginUrl(...). Is very important to do only after the call of this function due to is inside this function when the variable $_SESSION['FBRLH_state'] is populated.
Below an example of my code in the login.php:
$uri=$helper->getLoginUrl($uri, $permissions);
foreach ($_SESSION as $k=>$v) {
if(strpos($k, "FBRLH_")!==FALSE) {
if(!setcookie($k, $v)) {
//what??
} else {
$_COOKIE[$k]=$v;
}
}
}
var_dump($_COOKIE);
And in the login-callback.php before calling all FB code:
foreach ($_COOKIE as $k=>$v) {
if(strpos($k, "FBRLH_")!==FALSE) {
$_SESSION[$k]=$v;
}
}
Last, but not least, remember also to include code for PHP session so..
if(!session_id()) {
session_start();
}
...
...
...
...
<?php session_write_close() ?>
I hope this response can help you to save 8-10 hours of work :)
Bye, Alex.
This issue was a bit confusing for me, because I had to change a line at the facebook src file:
src/Facebook/Helpers/FacebookRedirectLoginHelper.php
at the function: "validateCsrf" like this:
if ($result !== 0) {
throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
}
And change it into:
if ($result === 0) {
throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
}
I don't know if this makes a violation to the facebook SDK security, so I truly opened to any exlanation or recommendation for this answer.
You may also make the following changes at the facebook app manager:
add your site and callback-url into your facebook app account at:
setting->advanced:Valid OAuth redirect URIs
Don't forget to add another url with slash (/) at the end of each url and check all 4 checkboxes at Client OAuth Settings.
I had the same error. Are you using 1 file or 2? I was trying to get by using 1 file but my error was resolved when I split into login.php & fb-callback.php as the documentation recommended. My sessions were being re-written so the state was never saved properly.
Good luck!
Happens when the session in missing a needed variable.
might be caused by several things.
In my case I left the "www" out of the callback URL
You could actually be parsing the data from another domain... for example:
website.com is different from www .website.com
If you're parsing data from http ://website.com/login.php to http://www.website.com/fb-callback.php this would be a cross-domain problem and the error you are receiving would be because of that....
http ://website.com and http ://www.website.com are the same but the script identifies them as different..... hope that gives insight to the problem.

issue capturing the hashed URI parameters in Coldfusion [duplicate]

I have such url - http://www.coolsite.com/daily-plan/#id=1
What the easiest way to parse that string and read a hash value (the value after #id=)?
Thank you
On client side (i.e. from JavaScript) you can check window.location.hash to get hash. On server side, general answer is 'it is impossible' since hash is not sent in request to server.
Upd: I maybe misunderstood the question. My answer is about how to get hash part of url either in browser or in server side code during request processing, not about string processing.
Upd2: Answer to comment here because it doesn't fit in comment.
How does it work when user clicks on your navigational links?
I assume hash is changed and corresponding content is downloaded via AJAX request from web service or REST.
For example if your user has URL www.example.com in his browser and this page shows a list of product categories. User clicks one category and URL changes to www.example.com/#id=5 and products from that category(with ID=5) are downloaded via AJAX and shown on the page. No postback, only partial page refresh.
Is this close to your scenario?
Now you want user to paste/enter www.example.com/#id=5 directly in the browser address bar and go directly to list of products in that category.
But /#id=5 is not sent to server with request by the browser, so there is no way to get that value on server side, and you can do nothing about it since it is the browser decided not to send this data and you don't have it on server side.
In our project we use solution when server returns only common page code/html, i.e. header, footer, without main/center part of the page. Then there is a JavaScript code which executes right after this common HTML loaded. It takes window.location.hash and sends it to web service via AJAX and web service returns content (HTML) for the main part of the page.
new URI("http://.../abc#xyz").getFragment();
See the Javadocs for URI
Here is how to capture anchor links. Works across all web frameworks.
I'll use an example scenario to illustrate: let's say we need to capture a deep URL http://server.com/#/xyz requested by an unauthenticated user so that they can be redirected to that deep URL post-login.
The unauthenticated user requests http://server.com/#/xyz (everything from the '#' onwards is not sent to the server).
All the server knows is that the user wants http://server.com/ and that they are unauthenticated. Server redirects the user to a login form.
Here's the clever bit: the client is still waiting on their original request so if the server includes a hidden element in the login form with some JS that references window.location.href, it can capture the full URL of the original request complete with the anchor portion:
<form action="/login" method="post">
<div>
<label>Username:</label>
<input type="text" name="username"/><br/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password"/>
</div>
<!-- XXXXXXXXX CLEVER BIT XXXXXXXXXX-->
<script>
document.write('<input type="hidden" name="from" value="'+document.location.href+'"/>');
</script>
<!-- XXXXXXXXXX-->
<div>
<input class="submit-button" type="submit" value="Submit"/>
</div>
</form>
The user authenticates themself and the original URL is sent with the POST. The server can then relay the user to the original deep URL.
String url = " http://www.coolsite.com/daily-plan/#id=1";
int sharpPos = url.indexOf('#');
String q = null;
if (sharpPos >= 0) {
q = url.substring(sharpPos);
}
Surely you can use various methods of string manipulation including regular expressions.
But actually your example is strange. Typically parameters of URL are passed after question mark. In this case you can just use standard class URL:
String q = new URL(" http://www.coolsite.com/daily-plan?id=1").getQuery();
what you are using to do this ?
If you are using jsp or servlet following will be useful to you
if (request.getParameter("#id") == null) {
out.println("Please enter your name.");
} else {
out.println("Hello <b>"+request.getParameter(i)+"</b>!");
}
If you are using javascript for it following function will be useful to you
function getURLParameters()
{
var sURL = window.document.URL.toString();
if (sURL.indexOf("?") > 0)
{
var arrParams = sURL.split("?");
var arrURLParams = arrParams[1].split("&");
var arrParamNames = new Array(arrURLParams.length);
var arrParamValues = new Array(arrURLParams.length);
var i = 0;
for (i=0;i<arrURLParams.length;i++)
{
var sParam = arrURLParams[i].split("=");
arrParamNames[i] = sParam[0];
if (sParam[1] != "")
arrParamValues[i] = unescape(sParam[1]);
else
arrParamValues[i] = "No Value";
}
for (i=0;i<arrURLParams.length;i++)
{
alert(arrParamNames[i]+" = "+ arrParamValues[i]);
}
}
else
{
alert("No parameters.");
}
}
REPLACE the '#' with '?' when parsing the url. Check the code below
String url = "http://www.coolsite.com/daily-plan/#id=1";
String urlNew = url.replace("#", "?");
String id = Uri.parse(urlNew).getQueryParameter("id");
If you URL will the same as you write and doesn't contains anythins else then whis code on Java will help you
String val = "http://www.coolsite.com/daily-plan/#id=1";
System.out.println(val.split("#id")[1]);
Don't forget check to null value.
P.S. If you use servlet you can get this parameter from request.getAttribute("id").
With best regards,
Psycho
if your url get from OAuth callback,then you can't!
because the full url won't send to service because of hash(#)

CSRFGuard - request token does not match session token

I am trying to incorporate the CSRFGuard library in order to rectify some CSRF vulnerabilties in an application. However after configuring as specified here I am now getting the below messages in the log, when I navigate the application:
WARNING: potential cross-site request forgery (CSRF) attack thwarted (user:<anonymous>, ip:169.xx.x.xxx, uri:/myapp/MyAction, error:request token does not match session token)
Through including the:
<script src="/sui/JavaScriptServlet"></script>
On my main.jsp page the links have all been built incorporating the CSRFGuard token, e.g.
......./myapp/MyAction?CSRFTOKEN=BNY8-3H84-6SRR-RJXM-KMCH-KLLD-1W45-M18N
So I am unable to understand what I'm doing wrong that could cause the links to pass a token other than the expected value.
Please let me know if any additional information would make it easier to understand.
In case anyone stumbles across a similar issue:
Turned out that accessing the app using IE wasn't passing a token to an AJAX call, this would in turn result in the tokens being refreshed but the links in the already rendered page remained, causing the mismatch when clicked.
Found out the issue by building CSRFGuard myself from source and adding extra logging.
The primefaces commandlink and commandbutton seem to cause the csrfguard javascript to malfunction, if you have use these two component with ajax set to true (which is the default), it can prevent the token being injected after the ajax call
One of the possible fixes is to change the following 2 lines in Owasp.CsrfGuard.js file.
Change
function injectTokenForm(form, tokenName, tokenValue, pageTokens) {
var action = form.attribute("action");
To
function injectTokenForm(form, tokenName, tokenValue, pageTokens) {
var action = form.attributes["action"].value;
AND
Change
function injectTokenAttribute(element, attr, tokenName, tokenValue, pageTokens) {
location = element.getAttribute(attr);
To
function injectTokenAttribute(element, attr, tokenName, tokenValue, pageTokens) {
var location = null;
if (attr == "action") {
location = element.attributes[attr].value;
} else {
location = element.getAttribute(attr);
}

How to use Zend_Form_Element_Hash?

Then I'm trying to use Zend_Form_Element_Hash it regenerates a hash every request.
In my code:
// form
$this->addElement('hash', 'hihacker', array('salt' => 'thesal'));
Then I dumping $_SESSION I see a new value each page reload.
Then I send a form it reports an error "The token '28a5e0e2a50a3d4afaa654468fd29420' does not match the given token 'a64407cc11376dac1916d2101de90d29'", each time - new pair of tokens
$form = new Form();
$form->addElement('hash', 'hihacker',
array('salt' => 'YOUR TOO MUCH SALTY TEXT !!##'));
if ($this->_request->isPost() && $form->isValid($this->_request->getPost())) {
// Valid ! you are safe do what ever you want .
} else if (count($form->getErrors('request_token')) > 0) {
///get him to the error controller
$this->_forward('csrf-forbidden', 'error');
return;
}
its working very well for me but double check your session setting
"
Internally, the element stores a unique identifier using Zend_Session_Namespace, and checks for it at submission (checking that the TTL has not expired). The 'Identical' validator is then used to ensure the submitted hash matches the stored hash.
The 'formHidden' view helper is used to render the element in the form.
"
form ZF docs
Zend_Form_Element_Hash is supposed to regenerate every request. What you're describing is your tokens going out of synch. This generally happens with multiple forms or with redirects/forwards.
If you're using ajax somewhere on the page you can put this in the controller action (near the end)
$form->hash->initCsrfToken();
$this->view->hash = $form->hash->getValue();
Then when you do the ajax call, just pull the token and replace the token on the form using a selector and .replaceWith(). This is how you deal with multiple forms as well
Otherwise you're probably either redirecting something or loading something twice and you should change the hop in the Zend library. The hop is how many times a token can be requested before it expires
Check that there is not a hidden redirect or forward somewhere in your script... the hash has a hop count of 1 so any redirect will make it expire.
FWIW i think there was a subtle bug in the hash a few versions of ZF ago. I got stuck on exactly the same problem, and hacked the code to make the hop count = 2. When I upgraded ZF this problem went away.