Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CORS Headers are altered in the browser resulting in content becoming blocked

Tags:

ajax

php

cors

nginx

Update 2 (A complete set of logs)

From Client's Perspective

Request headers:

POST /dev/micro_server.php HTTP/1.1 Host: production-server.com
Connection: keep-alive
Content-Length: 86
Pragma: no-cache
Cache-Control: no-cache
Accept: text/html, /; q=0.01
Origin: https://debug.dev
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 OPR/58.0.3135.90
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: https://debug.dev/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: debugger_session=iq4tbdk374mtvodsd3edcf2jq5

Response headers:

HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Date: Tue, 12 Mar 2019 12:01:27 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.5.9-1ubuntu4.17
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Origin: https://production-server.com
Access-Control-Allow-Credentials: true
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Encoding: gzip

Console Error:

Access to XMLHttpRequest at 'https://production-server.com/dev/micro_server.php' from origin 'https://debug.dev' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'https://production-server.com' that is not equal to the supplied origin.

Console Warning:

Cross-Origin Read Blocking (CORB) blocked cross-origin response https://daikai.no/dev/micro_server.php with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details.

From Server's Perspective

This is what the server says it has received and sent (check the code that does the logging in update 1):

Array
(
    [req] => Array
        (
            ...
            [HTTP_ORIGIN] => https://debug.dev
            ...
        )

    [rsp] => Array
        (
            [0] => X-Powered-By: PHP/5.5.9-1ubuntu4.17
            [1] => Access-Control-Allow-Origin: https://debug.dev
            [2] => Access-Control-Allow-Methods: GET, POST, OPTIONS
            [3] => Access-Control-Allow-Credentials: true
        )

)

Update

I have added some logging on the server and the script now begins with these lines:

# allow access from other domains
    header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
    header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
    header("Access-Control-Allow-Credentials: true");

$all = [
    'req' => $_SERVER,
    'rsp' => headers_list()
];

$s = print_r($all, true);
$p = '/var/www/path/to/file_' . uniqid() . '.txt';
file_put_contents($p, $s);

With this I can confirm that the request arrives on the server with the correct Origin, AND the server sends back the correct CORS headers. Yet, the Access-Control-Allow-Origin in the developer console is wrong and the request is blocked.

Here is a stripped down log obtained with the code above:

Array
(
    [req] => Array
        (
            ...
            [HTTP_ORIGIN] => https://debug.dev
            ...
        )

    [rsp] => Array
        (
            [0] => X-Powered-By: PHP/5.5.9-1ubuntu4.17
            [1] => Access-Control-Allow-Origin: https://debug.dev
            [2] => Access-Control-Allow-Methods: GET, POST, OPTIONS
            [3] => Access-Control-Allow-Credentials: true
        )

)

Question

How and why does the Access-Control-Allow-Origin is changed to https://production.com when the actual header received is Access-Control-Allow-Origin: https://debug.dev?


(Original Post)

Background

I have a web-based debug tool that I have installed on my local development machine. With an entry in my /etc/hosts I have assigned to it the domain debug.dev. I have also added a local CA authority and have successfully created a SSL certificate for the domain name so now I can open https://debug.dev/ in my browser and the debug tool opens normally.

This tool is supposed to work with staging and production servers. So it needs to send AJAX requests to other domains. I have full control over those servers and I am sending back CORS headers from those servers like so:

header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
header("Access-Control-Allow-Credentials: true");

The Issue

Now I am facing a baffling situation in which when I send an AJAX request to the production server I get back Wrong CORS headers with the SERVER's domain like this:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://production-server.com

But if I right click and use Open in new tab the CORS headers are what they ought to be; i.e.

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://debug.dev

The only differences between the requests as far as I can see is that the first one is sent as an AJAX POST request and thus sends a HTTP_X_REQUESTED_WITH header whereas the second request is sent as an ordinary GET request. How could this result in a different CORS header be returned by the server?

like image 656
Majid Fouladpour Avatar asked Mar 07 '19 08:03

Majid Fouladpour


2 Answers

The issue could be similar as my answer here:

The server is not configured to respond to OPTIONS requests with the correct "Access-Control-Allow-" headers.

Opening the url in a new Tab is a GET request and is working because it is not making a preflight request, as it meets the criteria to be a simple request as defined by the CORS documentation

On the other hand, the ajax request is a POST request and meets the criteria to be a Preflighted request, meaning a preflight OPTIONS request should be made first.

In short, you have correctly setup the CORS response headers, but the server is not configured to add these headers for OPTIONS method requests.

The solution is to handle OPTIONS requests on the server code with a 2xx response and add the **Access-Control-Allow- as you do for GET and POST requests. Keep in mind that OPTIONS requests do not include any parameters, so this should be done before any validation or request parsing.

Moreover, according to Access-Control-Allow-Origin documentation:

If the server specifies a single origin rather than the "*" wildcard, then the server should also include Origin in the Vary response header — to indicate to clients that server responses will differ based on the value of the Origin request header.

So set the Vary response header also:

eg at the top of you script try:

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
    header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
    header("Access-Control-Allow-Credentials: true");
    header("Vary: Origin");
    exit;
}

References

Preflighted requests

response for preflight 403 forbidden

like image 92
Jannes Botis Avatar answered Sep 29 '22 11:09

Jannes Botis


The problem is that you use header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']); (BTW: not very recommended) and you get the wrong header. I bet on one of the reasons:

  • The web server overwrites the headers.
  • The browser uses cache, although it should not. (BFC).
  • You did not upload the updated code to the requesting server.

Let's start from the end.

  • Do not believe herself, verify. (Not once did I look, we do not work, and then came the reflection that I did not upload changes :) ).
  • Turn off the cache in the browser debugger. (you must have open debugger) If this is a problem and the function is to be available in the pool, add timestamp to the request.
  • Check the configuration of nginx / apache / server panel

Are you aware that the construction used by you is synonymous with Access-Control-Allow-Origin: *? You should check HTTP_ORIGIN if it belongs to the allowed pool.

like image 25
bato3 Avatar answered Sep 29 '22 10:09

bato3