4

I am prototyping a simple web app front end that needs to fetch JSON data from my server. The server itself works fine -- I can click on the link, and the JSON data shows up in the browser. But the following simple script fails:

    fetch('https://x.x.x.x:8000')   //  MY URL FAILS
    // fetch('https://jsonplaceholder.typicode.com/todos/1')  // ALTERNATE URL WORKS
    .then(function() {
        alert("Successful")
    })
    .catch(function() {
        alert("Failure")
    }); 

I'm completely new to this sort of front-end work (and to Javascript in general), so I might be overlooking an obvious reason, but the two that come to mind are

  1. my server uses a self-signed certificate for testing purpose; and/or
  2. I'm using a non-standard port.

The first of these possible explanations seems more likely.

Accessing the web page generates a bunch of errors, none of which mean anything to me (except for not finding the favicon):

enter image description here

I will temporarily post the full URL in a comment below, in case anyone else wants to see what happens, but I would delete it once a working solution is suggested.

Grant Petty
  • 1,151
  • 1
  • 13
  • 27
  • 1
    TEMPORARY COMMENT: Here's the server URL: https://precip.aos.wisc.edu:8000/?lat=43&lon=-89 – Grant Petty Apr 15 '21 at 23:03
  • For reference, this is the actual error I get `net::ERR_CERT_AUTHORITY_INVALID` – SuperStormer Apr 15 '21 at 23:04
  • That's expected for a self-signed certificate, right? Can I make fetch ignore that? – Grant Petty Apr 15 '21 at 23:05
  • Does this answer your question? [Is it possible to ignore ssl verification for fetch api in react app?](https://stackoverflow.com/questions/49567402/is-it-possible-to-ignore-ssl-verification-for-fetch-api-in-react-app) – SuperStormer Apr 15 '21 at 23:05
  • If I'm understanding the second-to-last answer on that page, it's not possible with my current setup. I do have a signed certificate for the machine, but I don't know how to make flask use it to serve data via https. I'll see if I can figure that out. – Grant Petty Apr 15 '21 at 23:09
  • This type of question - about how to handle a security feature - is probably better suited to security.stackexchange, even though it came up in the context of software development. – CBHacking Apr 15 '21 at 23:30

2 Answers2

6

Just had the same problem and stumbled upon the solution by accident. It is possible by just making the user open the self-signed site, click on 'Show more' and 'Accept the risk and continue'. After doing that, fetch requests go through like nothing ever went wrong.

It works on Firefox:

enter image description here

and Chrome:

enter image description here

This method just has the caveat that you have to do the setup, and on Chrome it displays 'Not secure' even when the rest of the page is secure.

enter image description here

But if you need HTTPS locally, this works like a charm. Hope this helps the people who came here from Google :)

EDIT:

Also worth mentioning, I tested it on localhost but it works everywhere.

enter image description here

lxhom
  • 650
  • 4
  • 15
  • 1
    i had this issue and aside from doing what you said i also had to add `mode: "no-cors"` as an option for fetch – cabiste Aug 19 '23 at 17:54
5

To answer your question as asked, no, you definitely can't use fetch to force the client (browser) to ignore cert errors. Especially in cross-origin requests (and going from one port to another is cross-origin), that would be a HUGE security hole. It would allow anybody who could get a man-in-the-middle position on a victim's network (not hard) to steal information from the victim's HTTPS connections using fraudulent certificates to intercept the HTTPS requests and responses.

You might be able to force server-side JS (in Node or similar) to ignore cert validation errors, since in that case you (hopefully!) control the code the server is running. But it doesn't look like that's what you're doing, and in a web page, somebody else (the server owner) controls what code you (the browser) are running, so you definitely can't let that code turn off important security features!


Attack scenario for if JS could turn off cert validation:

Suppose you and I both control web servers. I, a malicious attacker, would like to intercept the traffic between your users and your web server. I even have a man-in-the-middle (MitM) network position on some of your users! However, you are of course using TLS (via HTTPS), so I can't decrypt or modify the traffic.

However, your users sometimes connect to my server as well, not knowing it is malicious (maybe I mostly use it to serve relatively innocuous stuff, like a comment system or analytics tools, so lots of sites embed my scripts). My server can tell when a browser requests content from an IP address where I could launch an MitM attack, and serve them malicious scripts.

Now, in the real world, this doesn't matter! Sites don't trust other sites, because of the Same-Origin Policy, a critical browser security feature. My site (or the scripts I serve) can cause your users to submit requests to any other server that I choose, but they can't read the responses (if the other server is cross-origin), and they can't turn off certificate validation so my MitM position is mostly useless.

However, suppose that there was a way - as you propose - for scripts to tell the browser "it's ok, just trust this one particular self-signed cert when making this request". This changes everything. My MitM host will generate a self-signed cert (and corresponding private key) for your site, and send the cert to my own web server. When a potential victim loads a script from me, it only only contains instructions to make HTTP requests to your site, it also specifies that the browser should trust the self-signed certificate that my MitM node generated.

The victim's browser would then start the request, attempting to establish a TLS connection to your server. My MitM node would intercept the request, and reply with its self-signed certificate. Normally the browser would reject that, but in this case it doesn't because you created a way to tell browsers to accept a particular self-signed cert. Therefore, the victim's browser trusts my self-signed certificate. The actual request never even makes it to your server. The victim's browser, believing itself to be interacting with the legitimate server (yours) rather than with my MitM host, sends an HTTP request containing secrets such as cookies and/or API keys / bearer tokens / login credentials / etc. My MitM intercepts that (as it's intercepting all traffic), decrypts it (because it is in fact one end of the TLS tunnel, this is trivial), and can access the victim's account on your server. (My MitM host can also duplicate the responses from your server that the victim would usually see, to keep them unsuspecting. The MitM host can even tamper with this responses, if I want it to mislead the user.)


The usual way to solve this is to install the server's certificate as trusted in the browser (or in the OS). That way, the browser will recognize the certificate's issuer (itself) as valid, and consider the certificate valid.

What happens if you go to https://x.x.x.x:8000/ in the browser directly? If you get a certificate error, well, that's your problem: the browser doesn't trust the certificate of the server hosted on that port. You should have an opportunity to temporarily or permanently trust that certificate (exact details will depend on the browser).

Note that, of course, doing this on your own computer won't fix it for anybody else's computer. They'd need to manually trust your certificate too.


The actual solution is, of course, to install a trusted certificate. Perhaps you should try Let's Encrypt or similar, for a nice free cert that every client will trust without extra shenanigans?

CBHacking
  • 1,984
  • 16
  • 20
  • I do have a trusted certificate for the machine/domain in question, and so access to Apache via https and port 443 works without any problem or warning. But I have not been able to figure out how to get flask to use the system-wide signed certificate on its assigned port. If I simply point flask to the certificates with flask run --cert=cert.pem --key=key.pem, it's apparently not able to see the private key, even if I run it with sudo. Since it may take time to solve this, I was hoping for an interim way to access the server without security checking. – Grant Petty Apr 15 '21 at 23:42
  • After some experimentation, I believe that the site/port in question is using a trusted certificate. Google Chrome's security diagnostics confirm this. Yet the fetch call posted at the top still doesn't work. I am considering opening a separate question on that, now that the https access appears to be working. – Grant Petty Apr 16 '21 at 02:07
  • Hello! This answer explains why it is not possible to force fetch to ignore cert errors, and I understood and agree. However, I am not looking to do that (and tbh neither OP it seems). What I want is to tell fetch to accept one additional specific certificate as trustworthy. Can it be done? (not by installing it on the browser, but via javascript). – Pedro A Oct 14 '22 at 16:46
  • @PedroA "accept one additional specific certificate" is exactly the same, security-wise, as "ignore cert errors". After all, the MitM attacker could always pre-generate the cert or certs they will use for interception, and tell the victim's browser to trust just that one particular additional cert for each request... thus completely bypassing the server authentication function which is integral to the security of HTTPS. – CBHacking Oct 15 '22 at 01:09
  • @CBHacking How would the attacker "tell the victim's browser to trust just that one particular additional cert"? I don't see how that would be done unless the attacker already has control of the JavaScript my frontend is executing. – Pedro A Oct 17 '22 at 02:41
  • @CBHacking The scenario in my mind is: I am serving HTML from address x.x.x.x using HTTPS (from Let's Encrypt, for example). The HTML I served includes javascript code that calls fetch to y.y.y.y. However, y.y.y.y serves HTTPS using a self-signed certificate. I (the author of x.x.x.x) trust it, but my user's machines don't (at least not by default). Instead of asking my users to preconfigure their browsers to trust this specific certificate, I think it should be possible to modify what I'm serving from x.x.x.x so that it just works. After all, my users trust x.x.x.x (me), and I trust y.y.y.y. – Pedro A Oct 17 '22 at 02:54
  • @PedroA You're forgetting that I, who control z.z.z.z and would like to intercept your browser's communication with y.y.y.y (or indeed x.x.x.x) could, in this scenario, "modify what I'm server" to tell anybody who lands on z.z.z.z (lots of ways to make that happen) to make a request to one of your sites and just go ahead and trust _MY_ self-signed cert (not yours! Mine! The one I'm using to MitM your users!) and then, boom, I can steal their sessions and monitor their data and tamper with responses and all the rest. – CBHacking Oct 17 '22 at 04:06
  • Thanks! I think I get it now. Would you be willing to add all this into your answer (since comments are "ephemeral")? Also if you could elaborate on an example of "to make a request to one of your sites"? The only thing I could think of is if you serve on z.z.z.z a clone (or even iframe) of x.x.x.x (tricking them into thinking they are in x.x.x.x) with all fetch calls patched to trust your mitm cert. However, I feel that if an user was tricked into thinking they were on x.x.x.x, the attacker can do a lot of damage already, regardless of my self-signed cert. So I'd love a simpler example... :) – Pedro A Oct 17 '22 at 14:28