Beware of wilcards paths in sudo commands

7 minute read

Say you want to allow a non-root user on Linux to execute a couple of scripts as root or another user with more privileges. A common way of doing this is to make an entry in the sudoers file.

If the scripts are written in Python, it could look something like this:

johndoe ALL=(ALL) /usr/bin/python3 /opt/utils/*.py

Essentially, this means that the user johndoe can execute /usr/bin/python3 /opt/utils/*.py on any machine (ALL) as any user ((ALL)).

If the scripts in /opt/utils/ are not writable for johndoe and there is no way for him to create new files in that directory, this looks okay at first.

What could go wrong?

The problem, though, is that if johndoe can write anywhere else (which is always the case), he can easily get a root shell (or do literally anything else) due to the wildcard character in the path argument to python3.

For example via a simple path traversal:

sudo /usr/bin/python3 /opt/utils/../../home/johndoe/gotcha.py

* matches any character (including whitespace, as we see further below), so a user is free to modify the path as desired.

So while the wildcard sounds like an easy way to solve the initial problem, it is always better to be explicit about what a user can do (either by putting a restricting regular expression in place or – even better – always explicitly targeting a file or declaring a static argument), for example by listing all the full commands with the different arguments that are allowed to be executed or by building a small wrapper program.

Not a problem specific to interpreted scripts

This is not solely a problem related to executing scripts with an interpreter (like the above example) but applies in general.

The sudo documentation presents another example using cat:

%operator ALL = /bin/cat /var/log/messages*

Here, users in the operator group are supposed to cat only messages files. But simply adding a second argument using whitespace (which is covered by *) would allow them to read any file:

sudo cat /var/log/messages /etc/shadow

The suggested safer alternative from the documentation is:

%operator ALL = /bin/cat ^/var/log/messages[^[:space:]]*$

This works fine when your path points to a file (which is expected when targeting /var/log/messages). If messages were a directory and not a file, you would have the same issue as above, though.

Conclusion: always be explicit in your sudoers file about what you want to allow.

Like to comment? Feel free to send me an email or reach out on Twitter.

Did this or another article help you? If you like and can afford it, you can buy me a coffee (3 EUR) ☕️ to support me in writing more posts. In case you would like to contribute more or I helped you directly via email or coding/troubleshooting session, you can opt to give a higher amount through the following links or adjust the quantity: 50 EUR, 100 EUR, 500 EUR. All links redirect to Stripe.