-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Cors for alternate local addresses #8642
Conversation
The problem is not people using |
private boolean isLocal(@NonNull String hostString) { | ||
URI uri = URI.create(hostString); | ||
String host = uri.getHost(); | ||
return "localhost".equals(host) || "127.0.0.1".equals(host); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return "localhost".equals(host) || "127.0.0.1".equals(host); | |
return "localhost".equals(host) || host.startsWith("127"); |
this is the most accurate you get without reading the systems network settings and it matches the whole of 127.0.0.0/8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when someone builds a website 127.0.0.malicious.com
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my bad not thinking about this, it would need to be coupled with something like host.matches("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I didn't want to cause a regular expression match to be executed for every request 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using manual splitting and parsing makes it about ~30-50% slower from my immature and very vague performance tests.
On my local machine the test takes ~2ms with regex ~4ms with custom parsing, Still adding 2ms to a request seem less than ideal (ofc this 2ms depend on the machine/CPU, JIT and runtime warmup) i dont have a better way in mind atm
The only other thing i could think about is using the NetworkInterface
API to check and adress for a loopback bound
The test i used
class Main {
public static void main(String[] args) {
var start = System.currentTimeMillis();
var rest = Arrays.stream("127.0.0.1".split("\\.",4))
.allMatch(s -> {
try{
Integer.parseInt(s);
return true;
}catch(Exception e){return false;}
});
var end = System.currentTimeMillis();
System.out.println("Non regex : "+(end-start)+"ms");
start = System.currentTimeMillis();
rest = "127.0.0.1".matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
end = System.currentTimeMillis();
System.out.println("Regex : "+(end-start)+"ms");
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats interesting but on my system 127.0.0.2
resolves on the default project using Arch Linux. Maybe on other systems it works different?
curl --location --request GET 'https://launch.micronaut.io/create/default/com.example.demo?lang=JAVA&build=GRADLE&test=JUNIT&javaVersion=JDK_17' --output demo.zip
unzip demo.zip
cd demo
./gradlew run
(different terminal) curl 127.0.0.3:8080
(different terminal) curl 127.200.200.203:8080
On my machine both curl commands pass with 404 not found json template which means the server is reached
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a reproducer as described in #8642 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think osx only supports 127.0.0.1, which is why I'm not seeing it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a simple ubuntu docker container should solve your OSX reproduction problem :)
...-server-tck/src/main/java/io/micronaut/http/server/tck/tests/cors/CorsSimpleRequestTest.java
Outdated
Show resolved
Hide resolved
...-server-tck/src/main/java/io/micronaut/http/server/tck/tests/cors/CorsSimpleRequestTest.java
Show resolved
Hide resolved
['.*bar$', '.*foo$'] | 'asdfasdf foo' | ||
['.*bar$', '.*foo$'] | 'asdfasdf bar' | ||
['.*bar$', '.*foo$'] | 'http://asdfasdf.foo' | ||
['.*bar$', '.*foo$'] | 'http://asdfasdf.bar' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test was failing as the origin was not a valid Origin format of:
Origin: null
Origin: <scheme>://<hostname>
Origin: <scheme>://<hostname>:<port>
And I now parse it with URI to get the hostname
We have to protect against these two scenarios, and we should protect without incurring any performance costs if possible. If you run this sample app locally. https://github.com/sdelamo/simple-request-attacks And you visit in a browser that does not protect against localhost-driven attach such as Chrome: Either: https://sdelamo.github.io/simple-request-attacks/stopEndpoint.html Or The former URL sends a request against The former has been patched since 3.8.1, and the latter will be patched via this PR. If you have another attack scenario with a reproducible example, please submit a PR against the attack demo https://sdelamo.github.io/simple-request-attacks, and I will be happy to re-evaluate and patch it. But we have to be careful not to incur performance costs. |
Thanks for the repo! I showed off the problem i mentioned with my PR : sdelamo/simple-request-attacks#1 |
Ignore me... 😿 I'm now thinking we need to check the server address rather than the HOST header address to reduce the variables... |
@nbrugger-tgm Right.... I think that's it... Tested it inside a container with
And only the last request worked... |
Looks good to me, but I think that your implementation using Also it just came to my mind that you also support websockets so maybe check |
Thanks for all the help with this @nbrugger-tgm 😎👍 I added a page https://sdelamo.github.io/simple-request-attacks/stopChecks.html to hopefully make testing easier |
private boolean isOriginLocal(@NonNull String hostString) { | ||
URI uri = URI.create(hostString); | ||
String host = uri.getHost(); | ||
return "localhost".equals(host) || "127.0.0.1".equals(host); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use SocketUtils.LOCALHOST
constant
return hostString.startsWith("http://localhost") | ||
|| hostString.startsWith("https://localhost") | ||
|| hostString.startsWith("http://127.") | ||
|| hostString.startsWith("https://127.") | ||
|| hostString.startsWith("ws://localhost") | ||
|| hostString.startsWith("wss://localhost") | ||
|| hostString.startsWith("ws://127.") | ||
|| hostString.startsWith("wss://127."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we improve the performance of this with a check that the first char is w
or h
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a pre-check for a host of length 0 and that the first char must be h or w
http-server/src/main/java/io/micronaut/http/server/cors/CorsFilter.java
Outdated
Show resolved
Hide resolved
Kudos, SonarCloud Quality Gate passed! |
It was raised in #8560 (comment) that we have not completed the circle with CORS and localhost drive-by protection
This PR adds a check for 127.0.01 in addition to localhost.
Currently it has a couple of missing parts which are up for discussion:
localhost
and the request is 127.0.0.1, we currently allow this (and vice versa). Again this is not spec compliant (I think), but if we have an origin of either of the two, I'm currently happy with the risk.Please comment with your thoughts on these 3 issues (and any others you think about)