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

Security fix breaks guake -e #2042

Closed
midnight-wonderer opened this issue Feb 20, 2022 · 29 comments
Closed

Security fix breaks guake -e #2042

midnight-wonderer opened this issue Feb 20, 2022 · 29 comments

Comments

@midnight-wonderer
Copy link

In version 3.8.5, execute_command has been removed from DBus
#1796

The discussion there implies that -e option won't affect by this change.
In reality, it does break the functionality

Here the stack trace

Traceback (most recent call last):
  File "$HOME/.local/bin/guake", line 8, in <module>
    sys.exit(exec_main())
  File "$HOME/.local/lib/python3.10/site-packages/guake/main.py", line 633, in exec_main
    if not main():
  File "$HOME/.local/lib/python3.10/site-packages/guake/main.py", line 552, in main
    remote_object.execute_command(options.command)
  File "$HOME/.local/lib/python3.10/site-packages/dbus/proxies.py", line 72, in __call__
    return self._proxy_method(*args, **keywords)
  File "$HOME/.local/lib/python3.10/site-packages/dbus/proxies.py", line 141, in __call__
    return self._connection.call_blocking(self._named_service,
  File "$HOME/.local/lib/python3.10/site-packages/dbus/connection.py", line 652, in call_blocking
    reply_message = self.send_message_with_reply_and_block(
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.UnknownMethod: Traceback (most recent call last):
  File "$HOME/.local/lib/python3.10/site-packages/dbus/service.py", line 662, in _message_cb
    (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
  File "$HOME/.local/lib/python3.10/site-packages/dbus/service.py", line 254, in _method_lookup
    raise UnknownMethodException('%s is not a valid method' % method_name)
dbus.exceptions.UnknownMethodException: org.freedesktop.DBus.Error.UnknownMethod: Unknown method: execute_command is not a valid method

To reproduce, simply try -e option.

$guake --support

<details><summary>$ guake --support</summary>

Guake Version:		3.8.5

Vte Version:		0.60.3

Vte Runtime Version:	0.60.3

--------------------------------------------------
GTK+ Version:		3.24.20

GDK Backend:		<GdkX11.X11Display

--------------------------------------------------
Desktop Session: gnome

--------------------------------------------------
Display: :0

RGBA visual: True

Composited: True

* Monitor: 0 - XWAYLAND0
    * Geometry:		1920 x 1080 at 0, 0
    * Size:		480 x 270 mm²
    * Primary:		True
    * Refresh rate:	59.962 Hz
    * Subpixel layout:	unknown
@midnight-wonderer
Copy link
Author

midnight-wonderer commented Feb 20, 2022

Not sure how D-Bus work, but can we somehow limit D-Bus access only to the command from the same user running guake, and restore the functionality?

From what I read, D-Bus is kind of an IPC mechanic, very much like HTTP.
If you handle the command insecurely, it is probably not a D-Bus problem, the issue is probably about how you handle the command.
Fixing this by not exposing the D-Bus endpoint doesn't seem to be a good solution, from the data available to me at the moment; for the same reason why we won't fix web vulnerability by denying access to ports 80, and 443.

I know so little about this thing, but if nothing else works, can we just create a secret (e.g. from: /dev/urandom) known only to guake, and use HMAC + timestamp + some other things to sign the command send via D-Bus. This way, only guake, can communicate with its own instances; denying access from some random application.

@Davidy22
Copy link
Collaborator

Hrm, when Guake is run via terminal it's always going to be a separate instance from the one you want to interact with. It contacts the current running Guake instance via dbus by name, and then it can send commands. Somehow, we need to generate a secret that only a running instance of Guake and another instance of Guake can access, a random secret of some kind might be nice if there's a way to only make it viewable to instances of Guake. Not sure how we can pass the secret between the two instances in a way that can't be replicated by other programs.

Actually, the exploit that I was alerted to and dealt with would allow malicious actors to gain the ability to execute commands as root if Guake is in a super user state. Since the the goal of the exploit is to gain root, if there's a secret stored in a location only accessible with super user privileges, I think that deals with the issue. Most people aren't running Guake with sudo normally, so it wouldn't normally have the ability to access information in a place with root privileges so I think there'll need to be a one-time setup of a public/private key pair. So the way it'd go is:

  • Prior setup - user goes to preferences, clicks on button to enable -e, enters password and Guake generates and stores a private key in root only place and public key where it can see it normally

  • When invoking -e - Attempts to view private key, fails if not invoked with sudo. Otherwise uses private key to authenticate the dbus call.

Have I missed an angle of attack here? I can run this by the person who alerted us to the initial issue as well, since they were savvy enough to spot the original vulnerability.

@midnight-wonderer
Copy link
Author

If we are going that route, why not generate the secret on the first run?
It seems much simpler.

For instance, store it as a flat file at ~/.guake/dbus-key and set the permission to 0600.
This way we can guarantee that the user can only interact with guake instances run by themselves.

@Davidy22
Copy link
Collaborator

Hum, is that sufficient? I'll include that in the message I send the security guy. Wouldn't this allow another installed program running at user level to perform the exploit?

@midnight-wonderer
Copy link
Author

If they have access to the file with 0600 permission, they have no reason to try running a command as that user; they already have higher privileges.

I'm glad we can discuss solutions and hope we can come up with a good resolution.
Thank you for the follow-up.

@ivoshm
Copy link

ivoshm commented Feb 27, 2022

I'm not a D-Bus expert, but if I remember correctly, there are two type of D-Buses - one "system D-Bus" and more "session D-Buses", which are only accessible in a given individual user login sessions.
I would expect Guake to use exclusively "session D-Bus" and then the implied attack vector isn't so critical/possible.

Excerpt from Wikipedia: https://en.wikipedia.org/wiki/D-Bus
... In practice, this means that any user process can connect to the system bus and to its current session bus, but not to another user's session buses, or even to a different session bus owned by the same user. ...

@Davidy22
Copy link
Collaborator

Davidy22 commented Mar 1, 2022

Still waiting on the reply from the security guy and was a bit busy last few days, can take a shot at this while waiting maybe. Going to try get it checked more than usual, really don't want to accidentally reintroduce the issue.

@Davidy22
Copy link
Collaborator

Davidy22 commented Mar 4, 2022

Initial implementation with notes in #2051. File set with 400 permissions post creation, exploit that was initially reported still possible, may need revision.

@The-Plottwist
Copy link

The-Plottwist commented Mar 22, 2022

Any news?

By the way, how does other terminals like gnome-terminal or xfce4-terminal implement this feature?
Maybe they have a solution about it.

@ivoshm
Copy link

ivoshm commented Mar 22, 2022

Sorry, I've studied the proposed solution a bit and I feel that it doesn't solve the problem.

After all, the basic problem is that the background guake process that handles D-BUS calls is (stupidly) running as root.

Isn't it easier to just add to original dbusiface.py something like this:

   def execute_command(self, command):
     if os.geteuid() == 0:
          sys.stdout.write(" Guake is running as root, unable to use execute_command")
        else:
          self.guake.execute_command(command)     

(sorry - I no speak Pythonicano 😥)

Because if I'm regular (non-root) user and my guake is running under my effective id, there is IMHO no security limitation to use execute_command via DBUS call and there is no need to mess with "sudo" and "cryptography".

@Davidy22
Copy link
Collaborator

Davidy22 commented Mar 22, 2022

Actually, Guake can execute root commands even when it isn't running as root. As a terminal, a Guake session that isn't running as root can be escalated to root privileges by typing sudo, which you likely do often, or by entering su -. Both of these will put a terminal into a state where a password is no longer needed to execute a command that would normally require root privileges, though the first one only temporarily and only under some admittedly common configurations. os.geteuid() will tell us if Guake was started with sudo guake but then if the user types sudo once and enters the five minute period where the terminal stops asking you to keep typing your password os.geteuid() will continue to report that the user is not root even when the exploit is possible.

Although, actually, maybe there's something in the VTE docs for checking if the terminal is in a root state. Maybe this is better than requiring that the guake -e end be run as sudo, or maybe we do both.

@midnight-wonderer
Copy link
Author

@ivoshm
Root escalation is only a part of the issue.
Even if you start your guake terminal as a non-root user, other users can run commands as you.
e.g., Someone else can steal your key by sending this: cp ~/.ssh/id_ed25519 /tmp && chmod 0666 /tmp/id_ed25519 via dbus.

@ivoshm
Copy link

ivoshm commented Mar 23, 2022

@ivoshm Root escalation is only a part of the issue. Even if you start your guake terminal as a non-root user, other users can run commands as you. e.g., Someone else can steal your key by sending this: cp ~/.ssh/id_ed25519 /tmp && chmod 0666 /tmp/id_ed25519 via dbus.

That's not true (at least) on my Manjaro - Guake uses session D-BUS which is connected to my user session in graphical environment. Other users (except "root" of course) can't connect to it.

Example with Guake 3.8.1 when user1001 try to send command to user1000 session D-BUS

user1001$ DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus gdbus call -e -d org.guake3.RemoteControl \
-o /org/guake3/RemoteControl -m org.guake3.RemoteControl.execute_command "id > /tmp/guake_exec_id.txt"
Error connecting: Could not connect: Permission denied

@midnight-wonderer
Copy link
Author

Good to know D-BUS has something in place already. Then, this is just a misunderstanding on my part.
I obviously never worked with D-BUS, so I assumed the worst and proposed based on the assumption that D-BUS is similar to TCP.
Then, I can't comment on your solution because it needs a deeper understanding of D-BUS.

More importantly, can we step back a little?
After reading David's scenario, I don't feel confident in this anymore; it is getting messier.

Is it only me, or is everyone here just to configure the startup tabs?
I mean this thing:
Screenshot
I just want a tab on guake start where I can control my AirCon via shell. So I simply configure it to standby for my command.

guake -r "AirCon Controller" -e "netcat -u 192.168.<AirCon IP Address> <Control Port>"

I don't actually care if I can remote control guake or not. I am fine with this feature gone forever if I can config startup tabs by other means.
Can someone tell me? Why on earth do you want to remote control the terminal apart from the use case I described.
We might be better off removing the remote controlling feature after all.

@ivoshm
Copy link

ivoshm commented Mar 23, 2022

@Davidy22

I got it - attack vector is possibility to send commands into terminal with "elevated" shell.

Quick and dirty solution:

import psutil

p = psutil.Process(357426) 
# This number is PID of shell running in terminal tab (Guake knows it, probably in Terminal.pid)

can_exec = True
puid = p.uids().effective

for i in p.children(recursive=True):
  if (i.uids().effective != puid):
    can_exec = False
    break

if can_exec:
  print("YOU CAN RUN execute_command")
else:
  print("YOU CAN NOT RUN execute_command - some subprocess of shell is running under other UID")

Just for record: terminal including sudo cached credentials is identifiable by exit code of sudo -vn

@Davidy22
Copy link
Collaborator

Checking ID catches if the user in su - but I don't believe it detects the temporary state where a terminal remembers the user's sudo password and stops asking for it if you entered it in the last five minutes.

If the -e command was being used solely for startup scripts, what about reforming it to always run the command in a new tab? I think that dodges any escalated privilege hijacking concerns, just need a moment to try and brainstorm any way a fresh shell could still have root.

@midnight-wonderer
Copy link
Author

Let me ping @ghostshadow to see if he has other inputs. He seems to be another person who cares about this feature.

To summarize, based on my understanding, the main topic is not about D-Bus anymore; letting other terminals input something into guake is the issue in itself. Even with the same user, there can be risks of privilege escalation by other means (e.g. sudo memorization).
We can either:

  • Prevent all security issues caused by the feature and bring back the old behavior.
  • Or remove the feature entirely; this is not why we are here but it can be the way out.

Do you guys actually want to control guake from other terminals or just want to configure startup tabs?
I believe if all just want to configure startup tabs, there must be a safer way.

@The-Plottwist
Copy link

The-Plottwist commented Mar 24, 2022

Sorry for the late response, I'm busy lately.

Do you guys actually want to control guake from other terminals or just want to configure startup tabs? I believe if all just want to configure startup tabs, there must be a safer way.

Yes, I do use this feature as to configure my startup tabs, not to control guake from other terminals. However, instead of configuring guake to execute some script file (like you do @midnight-wonderer), I have my own set of startup scripts which initiates at login as a systemd service and one of those scripts tells guake to spawn a new tab, rename that tab and execute some command.

Here is the code I'm using: guake -n "$HOME" -r "$screen_name" #-e "screen -r $screen_name"

So, it makes sense to have this feature to affect only new tabs, which I believe what the xfce4-terminal does. I may be wrong but looking at the --help output of the xfce4-terminal:

General Options:
  -h, --help; -V, --version; --disable-server; --color-table; --preferences;
  --default-display=display; --default-working-directory=directory

...

Tab Options:
  -x, --execute; -e, --command=command; -T, --title=title;
  --dynamic-title-mode=mode ('replace', 'before', 'after', 'none');
  --initial-title=title; --working-directory=directory; -H, --hold;
  --active-tab; --color-text=color; --color-bg=color

Window Options:
  --display=display; --geometry=geometry; --role=role; --drop-down;
  --startup-id=string; -I, --icon=icon; --fullscreen; --maximize; --minimize;
  --show-menubar, --hide-menubar; --show-borders, --hide-borders;
  --show-toolbar, --hide-toolbar; --show-scrollbar, --hide-scrollbar;
  --font=font; --zoom=zoom

makes me think that executing commands from other terminals is not allowed but for spawning a new tab. You cannot spawn a new window with an arbitrary command neither do tell a running instance to execute a command remotely.

@Davidy22
Copy link
Collaborator

I couldn't think of any way to hijack privileges on a fresh terminal so I'm open to going that way too. I'll put it to a vote, all in favor of reforming -e to only make a fresh tab to run in leave a spaceship reaction, all in favor of making -e sudo leave an eyes emoji

@midnight-wonderer
Copy link
Author

midnight-wonderer commented Mar 25, 2022

I was thinking something along the line of dedicated config sections for startup tabs; it can be something simple, like JSON config.
But, I don't contribute code, so I don't have much say in it, naturally.

If there needs to be something between these two, the rocket one works for me too.
Added that I want to be able to create a new tab with a startup command and also set the name of the tab.

@Davidy22
Copy link
Collaborator

New approach created, was significantly simpler, still want to see it get reviewed, @mlouielu, just to make sure I haven't gone and reintroduced a hole somewhere

@The-Plottwist
Copy link

Oh, I am relieved that this is finally settled.

It has been a nice discussion.

Good work everyone, cya.

@leomayer
Copy link

leomayer commented Jun 5, 2022

Any updates on this issue? Since its still not working :-(

@Davidy22
Copy link
Collaborator

Merged the resolution after far too much waiting. I'm just going to stop requesting reviews since other reviewers are fully absent now

@vasilakisfil
Copy link

vasilakisfil commented Aug 17, 2022

Can we bring back the original functionality somehow? Even if that means that you have to use a token or an authentication, whatever I don't know, but I think we cut down too much for security here. I think security is paramount, but the solution is not to remove features but to make them secure.

I used to use execute_command to run specific commands on my guake tabs from vim. Can we just allow commands that target non-root tabs. I think that security would be the same as the existing new-tab functionality.

@midnight-wonderer
Copy link
Author

@vasilakisfil
I don't know if it helps, but to recap the highlight of the discussion:

The vulnerability initially focused on dBus; however, later on, it was determined that letting 3rd party's application pose as if they are users themself is a problem; these include exposing an interface with the same effect as a user typing into the terminal.

Even with non-root tabs, the sudo seems to remember the password for a period of time.
Of course, it is just a vulnerability, not exploitation; we simply don't know what we don't know.
If you caught that part of the discussion already, my apology for the interruption.

@cmalard
Copy link

cmalard commented Oct 24, 2022

Still having the issue, did I missed something? ^^

Traceback (most recent call last):
  File "/usr/bin/guake", line 33, in <module>
    sys.exit(load_entry_point('guake==3.9.0', 'console_scripts', 'guake')())
  File "/usr/lib/python3/dist-packages/guake/main.py", line 648, in exec_main
    if not main():
  File "/usr/lib/python3/dist-packages/guake/main.py", line 567, in main
    remote_object.execute_command(options.command)
  File "/usr/lib/python3/dist-packages/dbus/proxies.py", line 72, in __call__
    return self._proxy_method(*args, **keywords)
  File "/usr/lib/python3/dist-packages/dbus/proxies.py", line 141, in __call__
    return self._connection.call_blocking(self._named_service,
  File "/usr/lib/python3/dist-packages/dbus/connection.py", line 652, in call_blocking
    reply_message = self.send_message_with_reply_and_block(
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.UnknownMethod: Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/dbus/service.py", line 662, in _message_cb
    (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
  File "/usr/lib/python3/dist-packages/dbus/service.py", line 254, in _method_lookup
    raise UnknownMethodException('%s is not a valid method' % method_name)
dbus.exceptions.UnknownMethodException: org.freedesktop.DBus.Error.UnknownMethod: Unknown method: execute_command is not a valid method
$ guake --support

Guake Version: 3.9.0

Vte Version: 0.68.0

Vte Runtime Version: 0.68.0


GTK+ Version: 3.24.33

GDK Backend: <GdkX11.X11Display


Desktop Session: ubuntu-xorg


Display: :0

@Davidy22
Copy link
Collaborator

3.9 should be it, is there an additional older version of guake installed running that's receiving the commands?

@orazdow
Copy link

orazdow commented Feb 10, 2023

Does this issue mean you cannot start guake from another command in a specific directory?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants