Jump to content

CORS and Sandboxing User Javascript, and Cookies


Go to solution Solved by kicken,

Recommended Posts

What I would like to do is to is to have a User Forum that allows Users to upload their own Javascript files that will run on other Users computers.

I am aware of how dangerous that is.  It can be done IF done properly.  The User scripts are intended to be run ONLY inside a Sandboxed Iframe with very restrictive CORS policies in place.  I have already done this.  CORS and Sandboxing allows preventing all XMLHttpRequests / Fetch requests to external sites so there is much less chance of a Users computer being compromised or trying to download malicious packages.  I take that back.  There is ONE place they should be allowed to retrieve data from, which is a simple AJAX request to my server, which will be one page that serves up their scripts.

I have run into an issue however.  I need to be able to pass an HTTPONLY cookie through the AJAX request so that the User is able to use the Sandbox for its intended purposes.  On my PHP side where the AJAX request is sent, I am not getting any cookie data sent from the Browser.  Im not sure if I should turn off HTTPONLY on it for the CORS request tho.

I am not sure where to even begin troubleshooting this.  Could one of you very experienced people take a peek at my code and offer any advice as to why I am not able to read cookies on my XML Requests?

---

Page where I am having trouble working within CORS Policies and still being able to read Cookies...

https://www.webucate.me/cors_csp/

Data URL that I am trying to use AJAX to get data from (test json object, time + cookie with a value of 123456):

https://www.webucate.me/cors_csp/jsondata.php

Source Code:

https://www.webucate.me/cors_csp/cors.zip

 

1 hour ago, Heretic86 said:

I need to be able to pass an HTTPONLY cookie through the AJAX request so that the User is able to use the Sandbox for its intended purposes.

The whole point of an HttpOnly-flagged cookie is that you cannot read or write to it in code. It's right there in the name.

26 minutes ago, requinix said:

The whole point of an HttpOnly-flagged cookie is that you cannot read or write to it in code. It's right there in the name.

Well, according to the link to the following article, the "withCredentials" flag (XMLHttpRequest, credentials: 'include' in fetch()), you CAN have a cookie set with HTTPONLY and STILL send that cookie without allowing script access.  It is an Exception to the HTTPONLY cookie can not be read by scripts rule.

https://medium.com/@_graphx/if-httponly-you-could-still-csrf-of-cors-you-can-5d7ee2c7443

Quote

Enter XMLHttpRequest

XMLHttpRequest is a wonderful function of JavaScript that allows developers to interact with the server and allow for dynamic updates of a page without having to refresh the page. It makes for clean, flashy web apps. Below is the malicious website code with part of the JavaScript used to execute the attack:

However, since it’s JavaScript, we should not be allowed via client-side script to access the cookie, according to the OWASP article on the subject. Normally this would be the case, but the XMLHttpRequest has a property called withCredentials. According to the article, the withCredentials property “Is a boolean that indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies or authorization headers”. So according to everything we have thus far, we can use session cookies programatically unless the HttpOnly flag is set, right?

Wrong. There appears to be an exception to the client-side script access prohibition for XMLHttpRequest that will still allow you to access the session cookie even with the HttpOnly flag set as long as the withCredentials property is set to true. This means that we can still execute CSRF using a victims session cookie that is properly secured with the HttpOnly flag.

So it isnt the HTTPONLY flag that is causing my issue.  I am just not familiar enough with CORS setups.

Anyway, as far as I can tell, it looks like there is an "Origin" header that is supposed to be sent.  Obviously I havent set up my request to include the Origin header, and I think that *might* be why this isnt working?

If you don't need to access the cookie in code and only care that it gets sent through the AJAX request, then yes: withCredentials would do it. Should be pretty easy to figure out whether that solves your problem.

Without error messages or a set of failing request and response headers, there's not much else to do but guess at what's wrong...

Request Headers:

OPTIONS /cors_csp/jsondata.php HTTP/1.1
Host: www.webucate.me
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: null
DNT: 1
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

Response Headers:

HTTP/1.1 200 OK
Date: Wed, 07 Apr 2021 08:44:44 GMT
Server: Apache/2.4.41 (Win64) OpenSSL/1.1.1c PHP/7.4.11
X-Powered-By: PHP/7.4.11
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Origin
Access-Control-Allow-Methods: Content-Type, Access-Control-Allow-Headers, Access-Control-Allow-Origin, Authorization, Allow, POST, OPTIONS
Access-Control-Allow-Origin: https://www.webucate.me
Access-Control-Max-Age: 1
Set-Cookie: TestCookie=123456; expires=Thu, 08-Apr-2021 08:44:44 GMT; Max-Age=86400; path=/; domain=www.webucate.me; secure; HttpOnly; SameSite=Strict
Content-Length: 40
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: application/json; charset=utf-8

Network Tab: Status - Not Allowed

CORS Allow Origin Not Matching Origin

Console.log says this:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://www.webucate.me/cors_csp/jsondata.php. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘https://www.webucate.me’).

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://www.webucate.me/cors_csp/jsondata.php. (Reason: CORS request did not succeed).

---

The origin isnt being sent?  How do I set this up to send the Origin?

Since I have to now guess, can I blame it on COVID?

You can't set the Origin, that's protected, so if it's sending "null" then that probably means you're not running from a suitable location.

That aside, the cookie is SameSite=Strict, so if you're not running from www.webucate.me then you can't use it.

2 hours ago, requinix said:

You can't set the Origin, that's protected, so if it's sending "null" then that probably means you're not running from a suitable location.

That aside, the cookie is SameSite=Strict, so if you're not running from www.webucate.me then you can't use it.

Even if the Iframe source is the same?

1 hour ago, requinix said:

Now I'm lost because I thought this was a CORS question.

The Javascript code that is running and sending AJAX requests. Is it doing so from a document context of www.webucate.me or some other domain?

That is the intent.  Its Iframed so the content can be posted on other websites.  Iframe also allows Sandboxing the code, which if it comes from another domain, should be restricted access to the rest of the window and document content.  And visa versa.  Iframe keeps it as just a Container to prevent malicious code from compromising other sites too.  It should prevent popups, accessing the rest of the document element, keystrokes when not focused, etc.  The cookie is needed for connecting to the Users account to "like" or "share" or whatever.

So yes.  I know I set something up very wrong.  I am not sure if it needs to be treated as samesite or none (which I have experimented with) but it doesnt seem to matter what I do, I dont see the Origin header being sent, and I am not sure why I cant get it to send.  Even on Kubuntu Firefox running in VMware so totally different browser and machine.

iframe or not, if your code is running on the same domain you're trying to send a request to then you don't "need" CORS, and the default behavior of browsers and servers should be fine...

The same request and response you posted. That's the OPTIONS and, presumably, its reply, right? Because they say that the allowed origin is the exact same thing that the error message is complaining is not present.
What are the headers from a failed AJAX request and response?

1 hour ago, requinix said:

iframe or not, if your code is running on the same domain you're trying to send a request to then you don't "need" CORS, and the default behavior of browsers and servers should be fine...

The same request and response you posted. That's the OPTIONS and, presumably, its reply, right? Because they say that the allowed origin is the exact same thing that the error message is complaining is not present.
What are the headers from a failed AJAX request and response?

Should be same as above:

Console (Firefox):

Access to fetch at 'https://www.webucate.me/cors_csp/jsondata.php' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://www.webucate.me' that is not equal to the supplied origin. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
iframe.php:59 POST https://www.webucate.me/cors_csp/jsondata.php net::ERR_FAILED

Network Request Headers:

OPTIONS /cors_csp/jsondata.php HTTP/1.1
Host: www.webucate.me
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: null
DNT: 1
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

Response Headers:

HTTP/1.1 200 OK
Date: Thu, 08 Apr 2021 02:03:52 GMT
Server: Apache/2.4.41 (Win64) OpenSSL/1.1.1c PHP/7.4.11
X-Powered-By: PHP/7.4.11
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Origin
Access-Control-Allow-Methods: Content-Type, Access-Control-Allow-Headers, Access-Control-Allow-Origin, Authorization, Allow, POST, OPTIONS
Access-Control-Allow-Origin: https://www.webucate.me
Access-Control-Max-Age: 1
Set-Cookie: TestCookie=123456; expires=Fri, 09-Apr-2021 02:03:52 GMT; Max-Age=86400; path=/; domain=www.webucate.me; secure; HttpOnly; SameSite=Strict
Content-Length: 40
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json; charset=utf-8

And finally, PHP source (simple test):

<?php

header("Content-Type: application/json; charset=utf-8");


header('Cross-Origin-Resource-Policy: same-site', false);

header('Access-Control-Allow-Credentials: true', false);
header('Access-Control-Allow-Headers: Content-Type, Origin', false);
header('Access-Control-Allow-Methods: Content-Type, Access-Control-Allow-Headers, Access-Control-Allow-Origin, Authorization, Allow, POST, OPTIONS', false);
header('Access-Control-Allow-Origin: https://www.webucate.me', false);
header('Access-Control-Max-Age: 1', false);

header('X-Frame-Options: SAMEORIGIN' );

$cookie = (isset($_COOKIE['TestCookie'])) ? $_COOKIE['TestCookie'] : 'NoCookies';

$time = time();
$msg = array("time" => $time, "cookie" => $cookie);
echo json_encode($msg);

setcookie('TestCookie','123456', [
    'expires' => time() + 86400,
    'path' => '/',
    'domain' => 'www.webucate.me',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Strict',
]);
?>

I know the first thing you will look at, Allow Origin header.  But when I try to disable that, this is what comes up in the console:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://www.webucate.me/cors_csp/jsondata.php. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

So its like it requires it but I cant implement it.  If you want, all the source code is in the zip file in the first post...

Thank you, by the way...

I'll ask once again because I'm pretty sure the correct answer is not what you've been saying:

Is the Javascript code running from a document context of www.webucate.me or something else? Are you perhaps running it as a local file?

11 hours ago, requinix said:

I'll ask once again because I'm pretty sure the correct answer is not what you've been saying:

Is the Javascript code running from a document context of www.webucate.me or something else? Are you perhaps running it as a local file?

Nope.  I am sure.

2 hours ago, requinix said:

I ask because various resources I can find all say that Origin: null means the AJAX request is coming from a file:// location.

I saw the same thing, but it is not.  Its hosted on a web server.  Granted it is a Localhost server, but it still uses an IP, hell, it even has to route through my VPN IP to get back to itself.  I have even gone so far as to set up another Domain Name (hosts file) and make my requests that way so the Iframe part of the page is coming from a different domain and it still doesnt work.  I just dont have any idea where I am doing it wrong!

I am close to giving up on this since I feel like no one has even looked at the code...  I just dont get why I cant send the cookie even though I am sending what I think is all the proper credentials with the request...

      const loadLocalXMLCookie = async function(data = { action : 'getDisplayName' } ) {
        let url = 'jsondata.php';
        const response = await fetch(url, {
          method: 'POST', mode: 'cors', cache: 'no-cache', credentials: 'same-origin',
          headers: { 'Content-Type': 'application/json' },
          redirect: 'follow', referrerPolicy: 'same-origin', body: JSON.stringify(data)
        })

 

Ok, let me try to explain my goal again.

I want users to upload their own scripts that they can run in an iframe.

Since that is pretty dangerous to other users, I think the best thing to do is sandbox scripts in an iframe.  Perhaps I am wrong.  And I can not simply say "never let users run their own scripts" because it directly contradicts my goals.  Running user scripts safely is the MAIN GOAL.

I think it is a good idea to also use CORS so that those scripts do not try to make calls to anything EXCEPT one specific location on my server where their scripts are loaded from.  This includes Inline Scripts.

To load those scripts, I need to be able to send an Ajax / Fetch request to that specific page to load their scripts, which requires a cookie to access which scripts are theirs, and ultimately edit and save those scripts.

Since other users will run those scripts, the Cookie has to be HTTPONLY so one user can not read another users cookies.

But when I send the Ajax / Fetch request to get their data, I cant read their cookie!  I think it is something either in CORS or in the way that I making my javascript requests.

Is this a better explanation?

Edited by Heretic86

A better explanation, but that isn't the problem. As I see it, you're trying to walk the line between security and no security, and you keep falling over onto one side or the other.

There's only one thing I can think of: a subdomain. Your sensitive cookies are restricted to the main domain, user scripts run out of the subdomain, and the main domain does CORS as needed.

This may be your issue

MDN: <iframe>: The Inline Frame element

Quote

...

  • allow-same-origin: If this token is not used, the resource is treated as being from a special origin that always fails the same-origin policy (potentially preventing access to data storage/cookies and some JavaScript APIs).

Try adding allow-same-origin to your sandbox attribute.

 

3 hours ago, kicken said:

This may be your issue

MDN: <iframe>: The Inline Frame element

Try adding allow-same-origin to your sandbox attribute.

 

I will put it back in.  I had it in there initially.  And this is posted above, but this is the error Im getting now:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://www.webucate.me/cors_csp/jsondata.php. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘https://www.webucate.me’).

This is my Content Security Policy:

<?php
header('Cross-Origin-Resource-Policy: same-origin', false);
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

function csp_policy(){
  header("Content-Security-Policy: " .
    "default-src 'none';" .
    "sandbox allow-scripts;" .
    "base-uri 'none';" .
    "img-src 'self';" .
    "style-src 'self' css/*;" .
    "navigate-to 'none';" .
    "form-action 'none';" .
    "script-src 'report-sample' 'unsafe-inline' 'unsafe-eval' " .
                "https://" . $_SERVER['SERVER_NAME'] . "/cors_csp/js/video.js " .
                "https://" . $_SERVER['SERVER_NAME'] . "/cors_csp/jsondata.php " .
                "data: ;" .
    "worker-src 'self';" .
    "media-src https://" . $_SERVER['SERVER_NAME'] . "/cors_csp/playvideo.php;" .
    "connect-src https://" . $_SERVER['SERVER_NAME'] . ";" .
    "child-src 'none';" .
    "report-uri https://" . $_SERVER['SERVER_NAME'] . "/cors_csp/csp_violation/index.php;" .
    "report-to {\"group\" : \"default\", \"max_age\" : 1800, \"endpoints\" : [{ \"url\" : \"https://" . $_SERVER['SERVER_NAME'] . "/cors_csp/csp_violation/index.php\"}]};" .
    "");
}

?>

 

What I would really like is a SIMPLE EXAMPLE that WORKS.  So, Sandboxed Iframe, XHR or Fetch, and PHP must to read a cookie.  I just cant seem to get a handle on the numerous headers that are all involved, and despite all the googling, I cant find any real working examples intended for demonstration purposes.

Think you could help with that?

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.