Skip to content
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

unauthenticated server-side request forgery - login page allows ssh to to localhost [CVE-2020-35850] #15077

Closed
passtheticket opened this issue Dec 28, 2020 · 14 comments · Fixed by #18609
Assignees

Comments

@passtheticket
Copy link

Cockpit version: 234
OS: Ubuntu 18.04
Page: login

User can detect open ssh port or another open ports on server that services Cockpit last version. This is a vulnerability that allows an user send request to internal hosts for detecting open ports. So that firewall configuration can be bypassed or the server can be used like gateway by malicious user.
In addition, user induces the application to make an request back to the server that is hosting Cockpit.
For example: if system admin creates iptables rule to drop all packets that come to 22 port or another port, user can detect whether port 22 is open or not.

Assuming that there is a iptables rule which port 22 is open for 127.0.0.1 (loopback interface) but is closed for other interfaces

First HTTP Request:

GET /cockpit+=192.168.1.27:22/login HTTP/1.1
Host: 192.168.1.27:9090
User-Agent: *
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Authorization: Basic dWJ1bnR1OnVidW50dQ==
X-Authorize:
Connection: close
Cookie: cockpit=deleted
Second HTTP Request:

GET /cockpit+=127.0.0.1:22/login HTTP/1.1
Host: 192.168.1.27:9090
User-Agent: *
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Authorization: Basic dWJ1bnR1OnVidW50dQ==
X-Authorize:
Connection: close
Cookie: cockpit=deleted

Steps to reproduce:
On login panel,
1-Click Other Options.
2-Set 127.0.0.1 to Connect to field and send request with incorrect credentials.
3-Intercept the request with Burp Suite
4- If ssh service is open on port 22 and credentials are wrong, server returns "401 Authentication Failed" response.
5-If user tries connect to a port that accepts data for ssh connection , server returns "401 Authentication failed: no-host" response and waits 10 seconds.
6- If user tries connect to a closed port , server returns "401 Authentication failed: no-host" response and without waiting.
7- If ssh service is open on port 22 and credentials are correct, server returns "200" response:

PoC video 1
PoC video 2
PoC video 3

@martinpitt
Copy link
Member

@passtheticket and I had a previous email exchange about this. Reproducing/summarizing here.

It seems to me that the firewall bypass is the primary issue here, right? Everything in your steps works exactly as designed. There is no need to intercept and edit the HTTP request (as in step 3), you can just directly set "Connect to:" to 127.0.0.1. This is even the standard mode of operation on CoreOS, Atomic, and similar container OSes (authentication docs, cockpit/ws container).

There are two cases to consider:

  1. Standard Cockpit installation with local user accounts. Then it does not make sense to firewall off SSH, but allowing the Cockpit port (9090). You can always log into the machine via cockpit directly, then open a channel to the local SSH port, and talk to the channel via the websocket. I.e. a cockpit session is strictly "more powerful" than a firewall. I.e. if you don't want
    people to log into a remote server, then firewall both SSH and Cockpit.

    I.e. there is nothing to "fix" here, this is just what Cockpit does.

  2. Bastion host. This means that you have a separate little machine/VM/container with the cockpit web server, and only use SSH to connect to other machines. This avoids having to run the (comparatively insecure) webserver on production machines.

    In this case it is true that you can also specify "localhost" as login target, and perhaps unexpectedly circumvent the firewall. This could perhaps be improved by disallowing localhost logins by default, and requiring a new option or value of RequireHost= to allow localhost logins also.

I don't think 2. is a big real-life issue, to be honest. A typical bastion host has no user accounts/passwords and either does not have SSH at all (if it's just a container) or would have SSH anyway to administer the bastion host.

Video 1.mp4 roughly corresponds to my "second use case" of a bastion host. Here we can think about preventing localhost logins by default, and enabling it through a config option. Let's track that in this issue.

Video 2.mp4 is more or less my "first use case" that I described in my previous email. As the cockpit webserver is strictly more powerful/capable than ssh, I don't consider this "firewall bypass" a security issue.

Video 3.mp4 (probing another port) is similar, if we disable localhost for cockpit-ssh by default on the login page, that would be avoided as well.

@martinpitt martinpitt changed the title unauthenticated server-side request forgery unauthenticated server-side request forgery - login page allows ssh to to localhost by default Dec 28, 2020
@martinpitt
Copy link
Member

My initial classification of this is somewhere between "enhancement" and "mild unexpected security-related issue", so let's not rush this. For people who are concerned about this, the immediate remediation is to configure SSH to not accept connections on the loopback interface. (Really, firewalls are overrated!)

One option is to document exactly that fact and remediation in the RequireHost= option in cockpit.conf(5).

A backwards-incompatible alternative is to change RequireHost=true to always resolve the Connect to: name, and if it resolves to localhost (127.0.0.0/8 or ::1), deny the connection attempt by default. We then need another option like SSHLocalhost=true to re-enable this. My worry is that this will break a standard cockpit/ws container installation on Fedora CoreOS/Atomic, as localhost ssh is the primary use case there. I.e. I think this would throw out the baby with the bathwater, and break this real-world use case for a rather theoretical issue.

So currently my favorite is the first, i.e. documentation plus writing an integration test case that proves that disallowing localhost connections in sshd works as documented.

@mvollmer, @allisonkarlitskaya, @stefwalter , I'd appreciate your opinion on this!

@passtheticket : Many thanks for scrutinizing the login process, it is much appreciated!

@passtheticket
Copy link
Author

Intercept process is required! Using Burp is necessary because you need to constantly request different ports to detect this vulnerability. You cannot do this on the login panel.
It is clear what SSRF means for me. It is not only firewall bypass, but also I can send requests to an internal server with Cockpit.
Users cannot restrict access to these servers as they are already connected to these servers with the Cockpit.
The measure of this vulnerability is clear. The user should be prevented from accessing servers that he does not have access to using the Cockpit Can a user make requests to the target server's lo interface? No. He can do this using the cockpit. It can also send requests to internal servers in this way. This is what SSRF means.
Also, I would like to remind you that the user can determine whether a port is open without password even if iptables rule is aplied.
Additionally it should be ensured that the cockpit-ssh process only connects to a user-specified port for mitigation.
@martinpitt : thank you.

@martinpitt
Copy link
Member

you need to constantly request different ports

You can specify a port, like myserver:1234. That's necessary, as we can't assume that SSH is always running on port 22.

Users cannot restrict access to these servers as they are already connected to these servers with the Cockpit.

Right, that's what I meant with "opening the cockpit port is strictly more powerful than SSH". If you can successfully log in to Cockpit, then you have the equivalent power of an SSH session. If you can't log in (wrong credentials), then you can still probe whether a port is open, that is correct (but you can't establish a full TCP connection). I suppose the latter is what your issue is about, and what I proposed to address above. I mostly just wanted to ensure that we are really talking about the same thing.

Additionally it should be ensured that the cockpit-ssh process only connects to a user-specified port for mitigation.

What do you mean by this? The user can already specify a port. If you don't, then :22 (ssh) is the default, but that's not in the least security relevant -- it's just convenience. Just as you don't have to specify :443 in every URL, or -p 22 in every ssh command.

@passtheticket
Copy link
Author

passtheticket commented Dec 29, 2020

Using Burp is necessary because you need to constantly request different ports to detect this vulnerability. and port scanning or ssh service detecting.
Right, cockpit is more powerful than SSH but if you can login the web interface. I mean that user can request other ports by using Burp. This process is like nmap SYN scan, it does not full TCP connection during port scanning.

Diagram

@passtheticket
Copy link
Author

passtheticket commented Dec 29, 2020

In this scenario , this vulnerability is not so critical . However, SSRF can cause to vital security issue and I will continue my research . I believe that there is a ssrf vulnerability. @martinpitt, I hope we agree.

@martinpitt martinpitt changed the title unauthenticated server-side request forgery - login page allows ssh to to localhost by default unauthenticated server-side request forgery - login page allows ssh to to localhost by default [CVE-2020-35850] Jan 7, 2021
@martinpitt
Copy link
Member

I talked this over with @mvollmer, and we agreed about the proposal in general.

  • RequireHost=true: Resolve host name and refuse 127.0.0.0/8 and ::1 by default. Maybe/hopefully g_inet_address_get_is_loopback() does that.
  • RequireHost=allow-localhost or maybe =all, or RequireHostLocalhost=yes will re-enable connecting to localhost.
  • Ensure that this does not break standard cockpit/ws container deployment on CoreOS.

@mvollmer
Copy link
Member

@passtheticket, we couldn't figure out how to fix this properly in Cockpit. What you report as a vulnerability is basic functionality of Cockpit, in our understanding.
To remove the vulnerability, people would have to stop using Cockpit, or configure their firewall rules to only allow what they are comfortable with.
Do you maybe have a concrete change proposal that we could consider for Cockpit? Please re-open if you have.

@passtheticket
Copy link
Author

passtheticket commented Feb 12, 2021

@mvollmer , I shared my finding as publicly.
https://docs.unsafe-inline.com/0day/cokpit-version-234-server-side-request-forgery-cve-2020-35850
https://www.exploit-db.com/exploits/49397
If you think that the vulnerability is basic functionality of Cockpit, I can't suggest anything for fix. It doesn't matter for me, also i can still exploit it during penetration tests. I completed my task by reporting this issue, the choice is yours.
Sincerely

@eddiez9
Copy link

eddiez9 commented Mar 25, 2021

Hi @mvollmer,

Chiming in I think there might be a bit of misunderstanding here.

A user probably shouldn't be able to use this to find open ports from the perspective of the Cockpit server with the remote connect feature. It's a result of having the different failure modes presented when failing to connect to a host.

A fix could be to always just return "401 Authentication Failed" for failures, and getting rid of this response code "401 Authentication failed: no-host". This might result in a bit worse UX as users can no longer tell whether they got their password wrong or if the host isn't allowing a connection but this is no different to username enumeration mitigations (e.g. https://blog.nvisium.com/time-based-username-enumeration). Instead you'd always just show the user 'connection failed'.

You'd also have to find a way so that response times are similar, e.g. by adding a 10 second +- a few miliseconds delay for the other scenarios so you can't guess what's happening based on the response time.

@francoataffarel
Copy link

its possible getting RCE?

@cardassian-tailor
Copy link

halp, root pleaze

@martinpitt
Copy link
Member

Admins who are concerned about this, can set LoginTo=false, see https://cockpit-project.org/guide/latest/cockpit.conf.5.html

@martinpitt
Copy link
Member

Reopening, as this currently only disables the "Connect to:" field, but not direct URL logins. I'll fix that.

@martinpitt martinpitt reopened this Apr 5, 2023
@martinpitt martinpitt assigned martinpitt and unassigned mvollmer Apr 5, 2023
@martinpitt martinpitt changed the title unauthenticated server-side request forgery - login page allows ssh to to localhost by default [CVE-2020-35850] unauthenticated server-side request forgery - login page allows ssh to to localhost [CVE-2020-35850] Apr 5, 2023
martinpitt added a commit to martinpitt/cockpit that referenced this issue Apr 5, 2023
The current documentation of LoginTo= isn't very specific about what
exactly happens with a "false" value; but it is plausible for an admin
to assume that "false" would disallow logging into a remote host
completely -- not merely hide the "Connect to:" field and then allowing
a direct URL login anyway.

It is sometimes important to disallow direct SSH logins from the login
page on publicly exposed bastion hosts, as this functionality allows
unauthenticated remote users to:

 - scan the internal network for existing hosts, which might otherwise
   not be accessible directly from the internet
   (Fixes cockpit-project#18540, https://bugzilla.redhat.com/show_bug.cgi?id=2167006)

 - scan the cockpit-ws host or internal network hosts for open ports
   (Fixes cockpit-project#15077, https://bugzilla.redhat.com/show_bug.cgi?id=2018741)

So change ws to reject direct URL logins with `LoginTo=false`. This
happens most naturally in cockpit_session_launch(), as we still want to
allow remote URLs from the shell's host switcher in already
authenticated sessions. This will not produce a very friendly error
message, but it doesn't have to be -- at that point specifying direct
URLs can be considered hacking anyway.

Clarify the documentation accordingly.
martinpitt added a commit to martinpitt/cockpit that referenced this issue Apr 6, 2023
The current documentation of LoginTo= isn't very specific about what
exactly happens with a "false" value; but it is plausible for an admin
to assume that "false" would disallow logging into a remote host
completely -- not merely hide the "Connect to:" field and then allowing
a direct URL login anyway.

It is sometimes important to disallow direct SSH logins from the login
page on publicly exposed bastion hosts, as this functionality allows
unauthenticated remote users to:

 - scan the internal network for existing hosts, which might otherwise
   not be accessible directly from the internet
   (Fixes cockpit-project#18540, https://bugzilla.redhat.com/show_bug.cgi?id=2167006)

 - scan the cockpit-ws host or internal network hosts for open ports
   (Fixes cockpit-project#15077, https://bugzilla.redhat.com/show_bug.cgi?id=2018741)

So change ws to reject direct URL logins with `LoginTo=false`. This
happens most naturally in cockpit_session_launch(), as we still want to
allow remote URLs from the shell's host switcher in already
authenticated sessions. This will not produce a very friendly error
message, but it doesn't have to be -- at that point specifying direct
URLs can be considered hacking anyway.

Clarify the documentation accordingly.
martinpitt added a commit that referenced this issue Apr 7, 2023
The current documentation of LoginTo= isn't very specific about what
exactly happens with a "false" value; but it is plausible for an admin
to assume that "false" would disallow logging into a remote host
completely -- not merely hide the "Connect to:" field and then allowing
a direct URL login anyway.

It is sometimes important to disallow direct SSH logins from the login
page on publicly exposed bastion hosts, as this functionality allows
unauthenticated remote users to:

 - scan the internal network for existing hosts, which might otherwise
   not be accessible directly from the internet
   (Fixes #18540, https://bugzilla.redhat.com/show_bug.cgi?id=2167006)

 - scan the cockpit-ws host or internal network hosts for open ports
   (Fixes #15077, https://bugzilla.redhat.com/show_bug.cgi?id=2018741)

So change ws to reject direct URL logins with `LoginTo=false`. This
happens most naturally in cockpit_session_launch(), as we still want to
allow remote URLs from the shell's host switcher in already
authenticated sessions. This will not produce a very friendly error
message, but it doesn't have to be -- at that point specifying direct
URLs can be considered hacking anyway.

Clarify the documentation accordingly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants