Chrome extension unable to get data from a server after switch to https due to Origin Control - ajax

I've been battling with Chromium, jQuery and AJAX. My extension takes some resources from some sites using ajax, then parse the results and store it somewhere. One of those sites decided to do the jump to https. Neat. I just have to change the http for https, and fix any problem I hit down the road. No joy.
I use the following ajax method to get the stuff (is the smallest unit I could reproduce):
$.ajax({
url: "https://reader.japanzai.com/search/",
type: 'POST',
data: {
'search': "onna"
},
beforeSend: function (xhr) {
xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
},
headers: {
'Access-Control-Allow-Origin': '*'
}})
Lets ignore for a while that I've set the headers twice, since just using one don't work either and throw the result:
OPTIONS https://reader.japanzai.com/search/ No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'chrome-extension://nhjloagockgobfpopemejpgjjechcpfd' is therefore not allowed access. jquery.js:5
x.support.cors.e.crossDomain.send jquery.js:5
x.extend.ajax jquery.js:5
(anonymous function) VM4000:2
InjectedScript._evaluateOn VM3770:581
InjectedScript._evaluateAndWrap VM3770:540
InjectedScript.evaluate VM3770:459
XMLHttpRequest cannot load https://reader.japanzai.com/search/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'chrome-extension://nhjloagockgobfpopemejpgjjechcpfd' is therefore not allowed access. lab.html:1
As you can see, it says that the request header is not present, which is true following the http request:
Request URL:https://reader.japanzai.com/search/
Request Method:OPTIONS
Status Code:200 OK
Request Headersview parsed
OPTIONS https://reader.japanzai.com/search/ HTTP/1.1
origin: chrome-extension://nhjloagockgobfpopemejpgjjechcpfd
access-control-request-method: POST
dnt: 1
accept-encoding: gzip,deflate,sdch
host: reader.japanzai.com
accept-language: es,en-US;q=0.8,en;q=0.6
method: OPTIONS
user-agent: Mozilla/5.0 (X11; Linux i686 (x86_64)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
accept: */*
url: /search/
scheme: https
version: HTTP/1.1
access-control-request-headers: access-control-allow-origin, accept, content-type
Response:
HTTP/1.1 200 OK
cache-control: post-check=0, pre-check=0, max-age=0
content-encoding: gzip
content-type: text/html
date: Thu, 13 Feb 2014 22:58:03 GMT
expires: Sat, 01 Jan 2000 00:00:01 GMT
last-modified: Thu, 13 Feb 2014 22:58:03 GMT
pragma: no-cache
server: nginx/1.5.8
set-cookie: ci_session=U3Q14aq8Q7W4KVifDyLi7%2B3lppr%2FS4BNmW1kD9t60H7dz73M5KMs1jgBo8ZrilKoswn63PvCl1sOldPs1RCl6NdiP1VZeekoMTISyQenQZU0F8yUC0odw6WuMUE5I%2FY%2Bfvddv2YH06b2pxyyNBDZnNn%2BLnLzPrRYBXHuAjFbTyucX%2FMLUUM2cwKLzaK3ILKPhxy8FXW%2FI%2F9EPPbwo%2B8nmbPwOeqDfpDdu61F5yzUU8KjfUo7MwwFIXyGWtqbjbF3PCKNZrY%2F3Cj77DgCpcCbTTKZ%2BVzrdw16oGVDg1dP8lQgSof89rLNqUlQSj60tCVzZ27oPNh9OvvTNJ92tYkTHDukG4dyv21yM4M3PACZ%2FKVNP0i2UWHbBujADPSsrGJhJxPzBsuRDLcPtDcBtuaXA4LLMoGoYW6SxYk%2BseltMvk%3D; expires=Fri, 14-Feb-2014 00:58:03 GMT; path=/
set-cookie: ci_session=tQT8qmNRnMRN2Oj3moCdZg9VNEEsPxi3t88g2SpYQxahFr%2FpiEpQFzsO2mLTp1bPlsGLmqQGnMUiuwFpLYNIneNHtU%2BoKkVOcnR8ZKxPd0FDrkW%2BqT0N2IIsV%2BC%2FXQX%2BZUkLg1E4iP6u%2F0%2Fjk1t%2BAwcwhoC0M3zODuEKv1l9JMFo%2B1g4%2BhIOp%2FHTzBnlMvE2KjanXJR55F3DOHdyi4MvQb1vzgWEZTTAfhZ3bkQPkKe41ZCJYQTw%2FrDfry8n2h43UKPc1IF4tWp%2BKh0yhux%2FsBn84meT3xR%2Bpba9ffeZObrQyVomKlmJg9oRkKvlhR4MlNsiIeIZEvtP52ns0X1uF%2B7Pg6RpcMihe1u2S0%2Fbz5wm75vQ6tyykmFp5qfnoDgXB6J7RmbBQy4GTOFEA2zqN3V6QXT71cSn%2B1ARd9GtNMA%3D; expires=Fri, 14-Feb-2014 00:58:03 GMT; path=/
status: 200 OK
strict-transport-security: max-age=31536000
vary: Accept-Encoding
version: HTTP/1.1
x-powered-by: PHP/5.4.4-14+deb7u7
So, I'm missing something obvious here or there's just no way to do this?

I followed abraham advice and added the site to the permissions field in the manifest. It worked, the only bad thing is that if another site decides to move to https I need to release a new version, so this is what I ended doing:
"permissions" : [
"tabs",
"*://*/*",
"https://ssl10.ovh.net/*",
"unlimited_storage",
"clipboardWrite",
The important part here is "*://*/*" which includes anything from http and https.

Related

Set-Cookie doesn't set the cookie

There is a well-known Google XSS game (https://xss-game.appspot.com/) that allows you to learn how to find and exploit XSS bugs. The advance to the next level occurs after the user injects a script to pop up a JavaScript alert().
Previously, the advance to the next level did not cause problems, but now something seems to be broken in this game because even after the alert is displayed on the screen, the following message appears when you try to move next:
Based on your browser cookies it seems like you haven't passed the previous level of the game. Please go back to the previous level and complete the challenge.
Let's try to figure out what's going on.
The source code (game.js, lines 11-24) of the site shows that the following code is responsible for moving to the next level:
function levelSolved() {
if (!userOpenedAlert) {
return;
}
var oReq = new XMLHttpRequest();
oReq.onload = function () {
if (oReq.readyState != 4) return;
document.getElementById('next-controls').style.display = "block";
eval(oReq.responseText);
};
oReq.open("GET", window.location.toString() + '/record', true);
oReq.send();
}
Request https://xss-game.appspot.com/level1/record looks like this:
General:
Request URL: https://xss-game.appspot.com/level1/record
Request Method: GET
Status Code: 200
Referrer Policy: strict-origin-when-cross-origin
Response Headers:
alt-svc: h3=":443"; ma=2592000
cache-control: no-cache
content-length: 0
content-type: text/html; charset=utf-8
date: Wed, 31 Aug 2022 18:14:26 GMT
expires: Wed, 31 Aug 2022 18:14:26 GMT
server: Google Frontend
set-cookie: level1=f148716ef4ed1ba0f192cde4618f8dc5; Path=/; Expires=Wed, 22 Jul 2022 12:34:56 GMT; HttpOnly
x-cloud-trace-context: 31ad2e6a5cdd8b63c39bad66e783535b;o=1
Request Headers:
:authority: xss-game.appspot.com
:method: GET
:path: /level1/record
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
accept-encoding: gzip, deflate, br
accept-language: q=0.9,en-US;q=0.8,en;q=0.7
sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-dest: document
sec-fetch-mode: navigate
sec-fetch-site: none
sec-fetch-user: ?1
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
So, as you can see, there is the Set-Cookie response header that should set the cookie. If set this cookie manually by the browser, then everything starts working normally, and the advance to the next level happens without problems.
The question actually is why the Set-Cookie header does not work in this case? The request is not cross-origin, and you can even execute it not as an XMLHttpRequest, but as a regular GET request, but this does not work anyway.
Let's help Google to fix this game ;)
The server sends Expires=Wed, 22 Jul 2022 12:34:56 GMT within the cookie. So there are no reasons to set this already expired cookie

Certain cookies are blocked cross domain when using an ajax request

I'm using a react app running on localhost:3000 which makes ajax requests to our website. We recently switched our authentification system from using WordPress authentification to https://github.com/delight-im/PHP-Auth.
Since then, using the same settings inside ajax and on our web server, our authentification cookies are not sent cross domain. However, it's working when requesting them from the same domain.
Our request:
fetchLoginStatus = () => {
const ajax = new XMLHttpRequest();
ajax.withCredentials = true;
ajax.open("POST", "https://our-website.com/src/php/checkLoggedIn.php");
ajax.onload = () => {
const response = JSON.parse(ajax.responseText);
};
ajax.send();
};
Our request headers (from localhost:3000):
:authority: my-website.com
:method: POST
:path: /src/php/checkLoggedIn.php
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
content-length: 0
cookie: plesk-items-per-page; plesk-sort-field, phpMyAdmin; databases-active-list-state-collapsed; plesk-list-type; io=R_dL3fjUEYe64ykHAAAp; isAsyncProgressBarCollapsed=true; PLESKSESSID; plesk-sort-dir;
origin: https://localhost:3000
referer: https://localhost:3000/
Our response headers (we are running an nginx server):
access-control-allow-credentials: true
access-control-allow-headers: origin, x-requested-with, content-type
access-control-allow-methods: PUT, GET, POST, DELETE, OPTIONS access-`
control-allow-origin: https://localhost:3000
cache-control: no-store, no-cache, must-revalidate
content-encoding: br
content-type: text/html; charset=UTF-8
date: Sun, 10 Mar 2019 15:26:08 GMT
expires: Thu, 19 Nov 1981 08:52:00 GMT pragma:
no-cache server: nginx
set-cookie: PHPSESSID=someId;
path=/; SameSite=Lax status: 200
vary: Accept-Encoding
x-powered-by: PleskLin`
When I don't send the request cross-domain PHPSESSID is inside the cookies of my request headers. However when I send the request from localhost:3000 it's not there.
Does somebody know how I can send the PHPSESSID from localhost too?
Thanks for any help in advance!
Asked the same question inside the github repository and the owner solved it.
https://github.com/delight-im/PHP-Auth/issues/154
Solution:
vendor/delight-im/auth/src/UserManager.php
Replace Session::regenerate(true); with Session::regenerate(true, null);
vendor/delight-im/auth/src/Auth.php
Replace #Session::start(); with #Session::start(null);
Replace Session::regenerate(true); with Session::regenerate(true, null);
After $cookie->setSecureOnly($params['secure']); append $cookie-
>setSameSiteRestriction(null); in all three (!) occurrences

Browser auth popup not showing up in case of ajax cors with basic authentication request

From a web page of domain A, I am firing up an ajax request to domain B in order to get JSON for which basic auth is configured on domain B. I have access to the code on both the domains. I configured the all the required CORS header on domain B (Even made Access-Control-Allow-Origin header value specific and not "*", after reading some stackoverflow) What I am expecting is browser basic auth pop up, But POST request just fails with 401.I can see that server has responded with expected response header for PRE-FLIGHT OPTION request, below the request & response headers of the OPTION & actual POST method call that happens
***OPTION REQUEST***
Host: DOMAIN_B:8085
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: x-requested-with
Referer: http://DOMAIN_A:2280/app/
Origin: http://DOMAIN_A:2280
Connection: keep-alive
***OPTION RESPONSE***
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://DOMAIN_A:2280
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Headers: Content-Type,Authorization,x-requested-with
Access-Control-Max-Age: 1
Allow: GET,POST
Content-Type: text/html; charset=utf-8
Content-Length: 8
Date: Fri, 04 Jan 2019 12:48:48 GMT
Connection: keep-alive
*** ACTUAL POST REQUEST***
Host: DOMAIN_B:8085
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://DOMAIN_A:2280/app/
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 105
Origin: http://DOMAIN_A:2280
Connection: keep-alive
*** ACTUAL POST REQUEST***
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Vary: X-HTTP-Method-Override, Origin
Access-Control-Allow-Origin: http://DOMAIN_A:2280
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST,GET,PUT,DELETE
Access-Control-Allow-Headers: Content-Type,Authorization,x-requested-with
Access-Control-Max-Age: 1
WWW-Authenticate: Basic realm=artist
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Date: Fri, 04 Jan 2019 12:48:48 GMT
Connection: keep-alive
So its expected that browser looking at the response of the POST call(401 HTTP code & WWW-Authenticate header) should get prompted to show the native authentication pop up, But it's not doing so. I am not sure what I am doing wrong here. Showing custom form to capture the credential and passing them in "Authorization" header using btoa function is not an option
Appreciate any help, I am ripping my hair apart here!!!
use basic-auth npm plugin
const auth = require('basic-auth');
app.use(function (request, response, next) {
var user = auth(request);
console.log("user => ",user);
if (!user || !user.name || !user.pass) {
response.set('WWW-Authenticate', 'Basic realm="example"');
return response.status(401).send();
}
return next();
});

rxjs5 Observable.ajax ignores explicitly set HTTP headers

I'm getting my feet wet with redux-observable and OAuth2 authentication. I'm stuck at the point where I have to POST adding Authorization header to my HTTP request. The header is has not been added. Instead, I see any custom-set header names as values of Access-Control-Request-Headers, and that's it.
This is a redux-observable 'epic':
const epicAuth = function(action$){
return action$.ofType(DO_AUTHENTICATE)
.mergeMap(
action => Rx.Observable.ajax( authRequest(action.username, action.password))
.map( response => renewTokens(response))
.catch(error => Rx.Observable.of({
type: AJAX_ERROR,
payload: error,
error: true,
}))
)
}
This is my request object:
const authRequest = function(username, password){
return {
url: TOKEN_PROVIDER + '?grant_type=password&username=' + username + '&password=' + password,
method: 'POST',
responseType: 'json',
crossDomain: true,
withCredentials: true,
headers: {
'Authorization': 'Basic <base64-encoded-user#password>',
}
}
}
The HTTP headers captured:
http://localhost:8082/api/oauth/token?grant_type=password&username=xxx&password=yyy
OPTIONS /api/oauth/token?grant_type=password&username=xxx&password=yyy HTTP/1.1
Host: localhost:8082
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
HTTP/1.1 401
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
WWW-Authenticate: Basic realm="MY_REALM/client"
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 1098
Date: Wed, 01 Nov 2017 17:57:38 GMT
It all ends up with 401 response, since the Authorization header was not sent. I have tested the Oauth2 endpoint manually with Postman tool, and all went well: I've got a valid access token, could renew it, etc. CORS is enabled on server side.
What am I missing here?
The client code is working correctly.
You've captured the OPTIONS cors request, which is asking the server if it is OK to POST the Authorization header (see the Access-Control-Request-Headers: authorization).
Make sure that you've configured CORS correctly on your server. It shouldn't be trying to authenticate OPTIONS calls. It should instead be sending a proper response which tells the browser if it is allowed to make the POST call.

How to make Flask/ keep Ajax HTTP connection alive?

I have a jQuery Ajax call, like so:
$("#tags").keyup(function(event) {
$.ajax({url: "/terms",
type: "POST",
contentType: "application/json",
data: JSON.stringify({"prefix": $("#tags").val() }),
dataType: "json",
success: function(response) { display_terms(response.terms); },
});
I have a Flask method like so:
#app.route("/terms", methods=["POST"])
def terms_by_prefix():
req = flask.request.json
tlist = terms.find_by_prefix(req["prefix"])
return flask.jsonify({'terms': tlist})
tcpdump shows the HTTP dialog:
POST /terms HTTP/1.1
Host: 127.0.0.1:5000
User-Agent: Mozilla/5.0 (X11; Linux i686; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: application/json; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: http://127.0.0.1:5000/
Content-Length: 27
Pragma: no-cache
Cache-Control: no-cache
{"prefix":"foo"}
However, Flask replies without keep-alive.
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 445
Server: Werkzeug/0.8.3 Python/2.7.2+
Date: Wed, 09 May 2012 17:55:04 GMT
{"terms": [...]}
Is it really the case that keep-alive is not implemented?
The default request_handler is WSGIRequestHandler.
Before app.run(), Add one line,
WSGIRequestHandler.protocol_version = "HTTP/1.1"
Don't forget from werkzeug.serving import WSGIRequestHandler.
Werkzeug's integrated web server builds on BaseHTTPServer from Python's standard library. BaseHTTPServer seems to support Keep-Alives if you set its HTTP protocol version to 1.1.
Werkzeug doesn't do it but if you're ready to hack into the machinery that Flask uses to instantiate Werkzeug's BaseWSGIServer, you can do it yourself. See Flask.run() which calls werkzeug.serving.run_simple(). What you have to do boils down to BaseWSGIServer.protocol_version = "HTTP/1.1".
I haven't tested the solution. I suppose you do know that Flask's web server ought to be used for development only.

Resources