.htaccess header edit set-cookie not work with laravel - laravel

My project need to run a security test requested by the client.
I use OWASP ZAP to run the test.
and it'll given back cookie No HttpOnly Flag warning, and it happended because of the "X-XSRF TOKEN" cookie didn't set httponly flag.
I did some research, and I know about this cookie didn't set httponly for javascript librarys like axios to use it.
but I use laravel 5.5, and in initial project, there is a great mechanism to let javascript library get this token without operate cookie directly, by meta flag:
......
// layout.blade.php
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
......
and in index.js:
/**
* Next we will register the CSRF Token as a common header with Axios so that
* all outgoing HTTP requests automatically have it attached. This is just
* a simple convenience so we don't have to attach every token manually.
*/
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
so it should be work fine even if XSRF-TOKEN 's httponly flag set to true.
I trace src code, and realize that I can modify just one line to make it work.
and somenoe already open a pr on github to solve this question:
https://github.com/ametad/framework/commit/2241b020ae3001ce5dabc1b7c5ea1514ff7f2e33
but I don't feel free to modify source code of framework,
so I try to modify cookie header via apache server (by public/.httaccess)
# public/.httaccess
<ifModule mod_headers.c>
Header always edit Set-Cookie: (.*) "$1, httponly"
Header set X-Content-Type-Options nosniff
Header always append X-Frame-Options SAMEORIGIN
Header set X-XSS-Protection "1; mode=block"
</ifModule>
but Header always edit Set-Cookie: (.*) "$1, httponly" didn't work for me.
below this line is all work, just set-cookie not work.
any idea how to fix this problem?

Firstly it generally isn't necessary to have the XSRF-TOKEN be http only. This cookie is encrypted (all Laravel cookies get encrypted) therefore even if a client does gain access to it, it does not actually contain any useful information.
You can override the default VerifyCsrfToken middleware with your own:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier {
protected function addCookieToResponse($request, $response)
{
$config = config('session');
$response->headers->setCookie(
new Cookie(
'XSRF-TOKEN', $request->session()->token(), $this->availableAt(60 * $config['lifetime']),
$config['path'], $config['domain'], $config['secure'], $config['http_only'], false, $config['same_site'] ?? null
)
);
return $response;
}
}
You can then replace the framework VerifyCsrfToken in Http/Kernel.php with your own overrriden one.
The downside to this is when you upgrade your Laravel version you will need to ensure your overriden class will still be compatible with the base class.

Related

Laravel - Vue Axios CORS policy

The request
axios.get("https://maps.googleapis.com/maps/api/geocode/json?latlng="+this.currentLocation.lat+","+this.currentLocation.lng+"&key="+this.apiKey).then((response) => {
if (this.town === response.data.results[0].address_components[2].long_name){
return
}
else{
this.town = response.data.results[0].address_components[2].long_name
this.getSuggested();
this.getAllEvents();
}
}).catch(er => {
console.log(er)
})
When i'm trying to get the town of a location i get this error
Access to XMLHttpRequest at 'API Route' from origin 'http://localhost:8000' has been blocked by CORS policy: Request header field x-requested-with is not allowed by Access-Control-Allow-Headers in preflight response.
When i remove
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
from bootstrap.js, the request works just fine.
What exactly is the problem here?
Any server you’re sending a request to will, usually by default, reject it if it contains headers which are not CORS-safelisted.
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
This code tells axios to attach a custom header to every request. Ergo, your request is being sent with a non safelisted header, which the server won’t permit, so you receive the error.
To permit the request, the server must be configured to allow the custom header.

CSRF Token Name on Django Documentation is not Matching the Actual Name of the Variable in AJAX Header

I was struggling to send and recieve CSRF token, and I found, in the end, that Django was not able to get the token value because its name was different from the recommended one in its documentation. Why?
(I am doing AJAX on a HTTPS address and requests are cross-site.)
Django documentation recommends that I add token to AJAX header in following way:
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
Here, the name is X-CSRFToken, which somehow becomes HTTP_X_CSRFTOKEN.
On the other hand, Django is looking up the cookie under CSRF_COOKIE.
Line 278 in csrf.py of CsrfViewMiddleware:
csrf_token = request.META.get('CSRF_COOKIE')
if csrf_token is None:
# No CSRF cookie. For POST requests, we insist on a CSRF cookie,
# and in this way we can avoid all CSRF attacks, including login
# CSRF.
return self._reject(request, REASON_NO_CSRF_COOKIE)
I cannot change the variable name because I get this error:
Request header field CSRF_COOKIE is not allowed by Access-Control-Allow-Headers in preflight response.
So, I ended up changing the variable name in the source code from CSRF_COOKIE to HTTP_X_CSRFTOKEN. Are there any way to make this work?
(I do not do #csrf_exempt, so please do not recommend.)
The problem is not from Django, if you read closely here: https://docs.djangoproject.com/en/2.0/ref/csrf/#how-it-works you will understand how it works and what kind of logic they follow.
The problem is that you are not allowing the headers:
Request header field CSRF_COOKIE is not allowed by Access-Control-Allow-Headers in preflight response.
If you search for this ACAH you will find that you must edit your server config file to allow this kind of posts.
The other case is that you may not be sending properly the header and that's why it's looking for the cookie. In that case you can try adding this to your header:
xhr.setRequestHeader('X-CSRFToken': $('meta[name="token"]').attr('content') });

Set AllowedOrigins based on condition using Laravel-Cors

I was previously using a custom cors middleware in order to handle Allow Origin based on my environment. But after upgrading to 5.5, I had an issue with preflight OPTIONS cors so I switched to laravel-cors library. But now I don't know how I can handle different cases just by a config file. I'm wondering if anyone has experienced a similar issue. This is my previous custom cors middleware:
public function handle($request, Closure $next)
{
$origin = \Config::get('app.url', "https://mysite.ca");
// Set origin for localhost developing, else just use the staging server
if( isset( $_SERVER['HTTP_ORIGIN'] ) && $_SERVER['HTTP_ORIGIN'] === 'http://app.localhost:3333' ) {
$origin = 'http://app.localhost:3333';
}
$response = $next($request);
$response->headers->set('Access-Control-Allow-Origin', $origin);
$response->headers->set('Access-Control-Expose-Headers','Authorization');
$response->headers->set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE');
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization, X-Requested-With, Application');
$response->headers->set('Access-Control-Allow-Credentials','true');
return $response;
}
laravel-cors already by default automatically does the equivalent of what the custom code in the question is doing — that is, laravel-cors sets the Access-Control-Allow-Origin value conditionally based what the value of the Origin header is and if you’ve allowed it in your config.
However, as far as what the code in the question does, it’s not clear why you want to ever send an Access-Control-Allow-Origin response header with its value set to the app.url value.
What I mean is, app.url’s value is the URL for the same server you’ve installed laravel-cors at, right? That is, it’s the application to which you want to allow cross-origin requests from particular origins.
If that’s the case, then you don’t need to explicitly allow requests from app.url, because those aren’t cross-origin requests so they’re allowed already without you needing to do anything.
Another point is that app.url isn’t an origin — instead it’s a URL, potentially with a path. But origins don’t have paths. So you don’t actually want to be setting the value of $origin to app.url unless you’re certain that your app.url has no path (not even a trailing slash).
All that said, if you really want to get the exact behavior of the custom code in the question, you can set your $origin variable as a global variable and then set the allowedOrigins array like this:
return [
/*
|--------------------------------------------------------------------------
| Laravel CORS
|--------------------------------------------------------------------------
|
| allowedOrigins, allowedHeaders and allowedMethods can be set to array('*')
| to accept any value.
|
*/
'supportsCredentials' => true,
'allowedOrigins' => [$origin, 'http://app.localhost:3333'],
'allowedHeaders' => ['Content-Type', 'Accept', 'Authorization', 'X-Requested-With', 'Application'],
'allowedMethods' => ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE'],
'exposedHeaders' => ['Authorization'],
'maxAge' => 0,
]
Those are the complete config settings for doing the equivalent of the custom code in the question.
Given the allowedOrigins value above, the conditional logic laravel-cors follows is this:
if the Origin request-header value matches the value of $origin, then send back the response header Access-Control-Allow-Origin with its value set to the $origin value
else if the Origin request-header value is http://app.localhost:3333, then send back the response header Access-Control-Allow-Origin: http://app.localhost:3333
else don’t send back any Access-Control-Allow-Origin response header
That’s what you need if you want to allow cross-origin requests from either the value of $origin or http://app.localhost:3333 but not from any other origins.
It’s true that does something a bit different from what the custom code in the question does — in that the code in the question causes an Access-Control-Allow-Origin response header with the $origin value to be sent back even to origins that are not allowed.
But you don’t want to be doing that anyway. In the case of a request coming from an origin that’s not allowed, there’s no point in sending back any Access-Control-Allow-Origin header at all — because the absence of the Access-Control-Allow-Origin response header tells the browser “don’t allow frontend JavaScript code running at this origin to access responses from our server”.
Beyond that there’s no point in publicly leaking information about what any of the the allowed origins are — which is what you’d be doing if you sent a default Access-Control-Allow-Origin response header set to $origin, as the custom code in the question does.

POST Request to other server

I want to send a POST request to an other server with Ajax when a button is pressed.
But I'm getting the error message:
XMLHttpRequest cannot load https://www.example.com/hello. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://my.site.com' is therefore not allowed access.
This is my button:
<button id="my-button">Click me pls</button>
And this is the JS code:
document.getElementById("my-button").addEventListener("click", function (evt) {
var request = new XMLHttpRequest();
request.open('POST', 'https://www.example.com/hello', true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.setRequestHeader('X-CSRF-TOKEN', "<...>");
request.onreadystatechange = function () {
if (request.readyState == 4 && request.status == 200) {
console.log(request.responseText);
}
};
request.send("message=Thisismymessage&" +
"_token=<...>");
evt.preventDefault();
return false;
});
/hello should process and store the message in the database.
On the server side I'm using Laravel 5.4.
This is my route:
Route::post('/hello', 'Auth\RegisterController#hello')
->middleware('cors');
The cors Middleware looks like this:
namespace App\Http\Middleware;
use Closure;
class Cors
{
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', 'https//my.site.com')
->header('Access-Control-Allow-Methods', 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS')
->header('Access-Control-Allow-Headers', 'origin, content-type, accept, authorization');
}
}
The hello method in the RegisterController just takes the data (in this case the message "Thisismymessage") and puts it in the database.
protected function hello(Request $request)
{
// Working with the data...
}
Do any of you have an idea how to fix it?
And my additional question ist: is there a way to "generate" the CSRF token from Laravel from an other application which doesn't use Laravel as framework or do I have to copy & paste it manually?
Thank you in advance.
I could recommend a little hack to solve the second question, i.e:
is there a way to "generate" the CSRF token from Laravel from an other application which doesn't use Laravel as framework or do I have to copy & paste it manually?
Create an end point in the backend of your app to generate a view of the form. You should already include the csrf_token field when making this form, so that at your front end you have a complete form which you will submit again.
This look like a snake long way.
The more recommended way is that: it appears you are building an api, this makes it easy because its stateless. Use an api key example with JWT-AUTH, so that you don't have to deal any thing with csrf token.
For the first question:
You should just sort out the cors issue from your backend a good one is Cors middleware for Laravel 5
Others: Not recommended but for test purpose you can add these in the constructor of your controller to see how your app fair:
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Accept, Authorization, X-Requested-With, Application');
Remember: This is useful only if you are doing a test.

Laravel CSRF token for AJAX CORS

How can I supply a csrf token for cross domain(subdomain) request in Laravel.
Both of the domain domain.tld and sub.domain.tld is run under the same Laravel Framework,
therefore I could use csrf_token() in the sub.domain.tld, I directly attach it to the request but it keep giving me tokenMismatchException, and I tried to turn off the csrf filter and dump the Input::('_token') and the Session::token(), it always DOES NOT MATCH
So, how can I supply a token for for another domain?
Have you added the correct headers to allow ajax requests cross domain/subdomain?
If not, in app/filters.php in your App::before filter, add:
App::before(function($request)
{
$host = explode( '.', $_SERVER['HTTP_HOST'] );
$subdomain = array_shift( $host );
header('Access-Control-Allow-Origin: http://' . $subdomain . '.yourdomain.com');
});

Resources