Allow bash script to be run as root, but not sudo

5,858

Solution 1

The only way I could think of is to check one of the SUDO_* environment variables set by sudo:

#!/usr/bin/env sh

if [ "$(id -u)" -eq 0 ]
then
    if [ -n "$SUDO_USER" ]
    then
        printf "This script has to run as root (not sudo)\n" >&2
        exit 1
    fi
    printf "OK, script run as root (not sudo)\n"
else
    printf "This script has to run as root\n" >&2
    exit 1
fi

Notice that of course this solution is not future proof as you cannot stop anyone from setting a variable before running the script:

$ su
Password:
# SUDO_USER=whatever ./root.sh
This script has to run as root (not sudo)
# ./root.sh
OK, script run as root (not sudo)

Solution 2

Another option would be to check if the grandparent process name is "sudo":

#!/bin/sh
if [ "$(id -u)" -eq 0 ]
then
  if [ $(ps -o comm= -p $(ps -o ppid= -p $$)) = "sudo" ]
  then
    echo Running under sudo
  else
    echo Running as root and not via sudo
  fi
else
  echo Not running as root
fi

Solution 3

The information about which user logged in is available in /proc/self/loginuid. EDIT due to comments: That file does not seem to exist on all systems. I tested and it is available on Centos 6, Fedora 32, Fedora 33 and Ubuntu 20.04, all in standard x86_64 setups. If we login as our user and than use sudo or su to become root, this will not change /proc/self/loginuid and it will be some non-zero value. If we directly log in as root, then cat /proc/self/loginuid will return 0. Note that this file can NOT be modified, even root cannot do this. EDIT due to Stéphane Chazelas' comment: Root can overwrite this file using echo 0 > /proc/self/loginuid. However, this can be prevented by setting auditctl --loginuid-immutable.

The script to check for real root (if auditctl --loginuid-immutable is set) could look like

#!/bin/bash
loginuid=$(cat /proc/self/loginuid)
echo $loginuid
if [[ $loginuid -ne 0 ]]; then
    echo "You did not log in as root."
    exit
fi

Solution 4

Avoid sudo in root bash script?

Preamble: Care sharing root account!!

Unfortunely, there is no resistant way... Please read carefuly upto last paragraph

Once you give root access to someone, they could do anything, including editing your script!!

For sample, if user hit sudo su -, then variables SUDO_* doesn't exist anymore...

First quick way using pstree

So simplier way to search for sudo presence in whole current tree, seem to use pstree:

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
pstree -s $$ | grep -q '\bsudo\b' && die "Can't be run under sudo"

With ps only, you could loop over ps ho ppid:

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
pid=$$
while read pid name foo < <(ps ho ppid,cmd $pid) && ((pid>1));do
    [ "$name" = "sudo" ] && die "Can't be run under sudo"
done

Regarding comment about renamed sudo

If sudo command is renamed or copied, then instead of looking for command name, look for UID in whole parent tree. So script is same than previous, but searching for UID >= 1000 in parent tree:

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
pid=$$
while read pid uid < <(ps ho ppid,uid $pid) && ((pid>1));do
    ((uid>999)) && die "Can't be run under sudo"
done

Because we are speaking about Un*x

To be correct, avoid using fixed statical datas, use of UID_MIN from /etc/login.defs:

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
while read fld val;do
    case $fld in UID_MIN ) UIDMIN=$val ;break ;; esac
done </etc/login.defs
((UIDMIN)) || die Getting UID_MIN.
pid=$$
while read pid uid < <(ps ho ppid,uid $pid) && ((pid>1));do
    (( uid >= UIDMIN )) && die "Can't be run under sudo"
done

Workaround for executing this by using sudo anyway

But all this is someting fragile:

$ sudo su -
# screen -D -R  # apt install screen if not installed

Now hit Ctrl + a , then d to be detached. Type exit or hit Ctrl + d to return in user mode...

Then simply:

$ sudo screen -x

Now, you'll be logged in a root login session. No trace of any sudo.

# ps $PPID
 PID TTY      STAT   TIME COMMAND
 26367 ?        Ss     0:00 SCREEN -D -R
# ps ho ppid $PPID
 1
# set | grep SUDO
                                         # <-- nothing here!

Conclusion

As chepner rightly commented: sudo is drawn to give specifics access to specifics tools:

Nothing about sudo requires it to give you root access; that's just the default behavior everyone is familiar with. sudo can be configured to allow you to do only very specific things, including not gain root access at all – chepner

Care to configure them correctly, before using fragile workaround!

See:

apropos sudo

And read carefully

man sudo.conf
man sudoers

Regarding logname

Have a look at correct Stéphane Chazelas's answer! This could be the best answer for a homework!!

Again, lot of workaround, like: echo 0 > /proc/self/loginuid...

About /proc/self/loginuid under Linux

Please read interesting laolux's answer about this!

die() { echo >&2 ${0##*/} Error: "$@"; exit 1;}
read lUid </proc/self/loginuid || die "Can't access procfile"
((lUid)) && die "You must be logged as root."

( This syntax avoid forks! )

But anyway

  • script could be copied and edited
  • depending on config/kernel, this kernel entry could be spoofed
  • Sudoer could create cron entry for initiating special screen session as root. (cron and screen are not the only way for doing things like this! Just the first coming to my mind. )

Solution 5

I suggest checking out process list strings and see if the user is running the program using sudo

contype=`tty | cut -d '/' -f 3`
tty="$contype/`tty | cut -d '/' -f 4`"

if [ "$(id -u)" -eq 0 ]
then
    res=`ps ax | grep "$tty" | grep "$0" | grep "sudo"`
    if [ $? == 0 ]
    then
        echo "You should not run the script using sudo!"
        exit 2
    else
        echo "Done."
    fi
else
    echo "You are not root. Run this script as root."
    exit 2
fi

That variable res is simply for you if you want to filter results again.

Arkadusz code is nice but unfortunately it can be bypassed really easy...

Share:
5,858
PK001
Author by

PK001

Updated on September 18, 2022

Comments

  • PK001
    PK001 over 1 year

    I'm new here and new to bash/linux.

    My teacher gave me an assignment to allow a script to be run only when you're "really" root and not when you're using sudo. After two hours of searching and trying I'm beginning to think he's trolling me. Allowing only root is easy, but how do I exclude users that run it with sudo?

    This is what I have:

    if [[ $EUID -ne 0 ]]; then
      echo "You must be root to run this script."
      exit
    fi
    
    • Markus
      Markus over 3 years
      try this: sudo whoami... I'm not sure that the problem is defined particularly clearly. sudo whoami claims you're root. Even if that doesn't count, what if you run a shell with sudo, does that count as using sudo?
    • mikem
      mikem over 3 years
      Take away sudo all and enumerate only the commands a user should be allowed to run as root, excluding your script from that list.
    • Kusalananda
      Kusalananda over 3 years
      How would you stop the sudo user from removing the restriction from the script once you have put it in place? There is no difference between the root user logged in from a console and the root user accessing the system via sudo. The foolproof solution woud be to simply uninstall sudo.
    • Mark Morgan Lloyd
      Mark Morgan Lloyd over 3 years
      I think that any answer has to consider the possibility that the lecturer is ignoring (or hasn't thought of) the possibility of running sudo su and intends that the solution prevents the script from starting to run rather than aborting if it doesn't like its execution environment. As such I agree with @mikem and would highlight man sudoers -> SECURITY NOTES since some of their caveats aren't relevant if the script can't be edited or renamed.
    • Olivier Dulac
      Olivier Dulac over 3 years
      your best bet is to restrict users to not be able to execute the script via sudo, using answer superuser.com/a/735286/174998
    • Kusalananda
      Kusalananda over 3 years
      Is the root user to be allowed to execute the script through sudo?
    • Andrew Henle
      Andrew Henle over 3 years
      On any secure system, there are no shared accounts. There will be no such thing as "really" root - the actual user will always be known. Limitations like this are worse than useless - they give you a false sense of security when all they really do is maybe slow down malicious actors for a few seconds. You'll think you're secure while in reality you're being pwned by every half-wit with an internet connection who can spell "Google".
    • chepner
      chepner over 3 years
      Nothing about sudo requires it to give you root access; that's just the default behavior everyone is familiar with. sudo can be configured to allow you to do only very specific things, including not gain root access at all.
    • Joshua
      Joshua over 3 years
      @JaredSmith: He probably wants an anti-bonehead solution not a secure solution. Else sudo-user could copy the script and take the check out.
    • F. Hauri
      F. Hauri over 3 years
      @MarkMorganLloyd By running sudo su -, than screen detach an re-attach, You'll be in a root login shell. See my answer
    • F. Hauri
      F. Hauri over 3 years
      @chepner I've cited your comment in my answer
    • Mark Morgan Lloyd
      Mark Morgan Lloyd over 3 years
      @FHauri I know you can get a shell, and so apparently do you. But as I explicitly said: the lecturer might not, or might be attempting to not ask that question.
  • pLumo
    pLumo over 3 years
    +1, but this can still be tricked by something like sudo bash -c 'unset SUDO_USER=; my_command;'. I would not rely on it.
  • PK001
    PK001 over 3 years
    It might not be good for actual work, but it will probably get my assignment done, which is what matters at the moment. Thank you very much!
  • doneal24
    doneal24 over 3 years
    This fails if the user is logged in on tty/1 or another physical device.
  • zbx0310
    zbx0310 over 3 years
    Thanks for alerting me. I've edited the code...
  • waltinator
    waltinator over 3 years
    The paranoids among us would begin with env | grep -E 'SUDO|PPID|PID, and validate each via /usr/bin/ps and other command line tools. One could check each of env | sort, if one wished.
  • F. Hauri
    F. Hauri over 3 years
    Care! If user run sudo su -, variables SUDO_* doesn't exist anymore! See unix.stackexchange.com/a/626764/27653
  • piojo
    piojo over 3 years
    This fails when run under "sudo su" or "sudo bash". I think you need to check multiple levels of processes.
  • Nick Kennedy
    Nick Kennedy over 3 years
    On Raspberry Pi OS (which is a Debian-based OS), this file doesn’t seem to exist
  • Nick Kennedy
    Nick Kennedy over 3 years
    In common with other solutions, this fails if the sudo command has been renamed
  • Jeff Schaller
    Jeff Schaller over 3 years
    Granted; you could extend this to loop over the PPID process tree, but I thought I'd cover the most common usage of ./script or sudo ./script, to point out the core idea of checking for "sudo" as being the parent process or not.
  • laolux
    laolux over 3 years
    That's surprising. I checked Fedora 32, 33, Centos 6 and ubuntu 20.04 and they all have that file. I checked on x86_64 only, not on arm systems.
  • Radovan Garabík
    Radovan Garabík over 3 years
    @NickKennedy FWIW it does not exist on my ODROID U3 (armhf xenial) as well
  • Davis Yoshida
    Davis Yoshida over 3 years
    This can be circumvented by sudoing then running a process that disowns right?
  • Jeff Schaller
    Jeff Schaller over 3 years
    This answer attempts to address the question "how do I exclude users that run it with sudo?" in the straightforward interpretation, not as an exhaustive security measure. I don't know what the professor was aiming for, but maybe parent processes were on the syllabus and they thought this would be an instructive exercise. Any simple measure can be fooled, as we've seen so far with the straightforward SUDO_USER environment variable and these process checks.
  • Michael Homer
    Michael Homer over 3 years
    I believe this requires CONFIG_AUDIT=y, on Linux, but on such a system this is the right answer (though I imagine the actual goal was to read the manual and find SUDO_USER).
  • Joshua
    Joshua over 3 years
    If you would like to see a script that defeats /proc/self/loginuid I think I can provide.
  • Joshua
    Joshua over 3 years
    Update: I know I can provide. It's possible to convince init to re-exec itself, which means it's possible to convince init to re-exec anything you darn well please.
  • F. Hauri
    F. Hauri over 3 years
    Care, if user run screen,. detach then re-attach, you can't see anything more than a root login shell!! See last paragraph at: unix.stackexchange.com/a/626764/27653
  • Stéphane Chazelas
    Stéphane Chazelas over 3 years
    sudo sh -c 'echo 0 > /proc/self/loginuid && cat /proc/self/loginuid' outputs 0 for me on Ubuntu 20.04 with a 5.4.0-58-generic Linux kernel
  • Stéphane Chazelas
    Stéphane Chazelas over 3 years
    Can be changed with auditctl --loginuid-immutable.
  • Wastrel
    Wastrel over 3 years
    @Mark I knew someone would say that. The question doesn't specify that the script itself has to "decide" who can run it, just that root can run it but not a sudo user. It occurred to me that this was what his instructor was looking for. Maybe I'm wrong.
  • Joshua
    Joshua over 3 years
    @laolux: Although once I have root I can defeat it outright as I said earlier.
  • Joseph Sible-Reinstate Monica
    Joseph Sible-Reinstate Monica over 3 years
    auditctl --loginuid-immutable doesn't make this perfect either. In particular, it only prevents you from changing your loginuid once it's been set, but there are ways that you can gain control of, or start, a process where it never gets set at all.
  • Joseph Sible-Reinstate Monica
    Joseph Sible-Reinstate Monica over 3 years
    Bypassable by running screen inside of sudo
  • laolux
    laolux over 3 years
    @JosephSible-ReinstateMonica yes, but I could imagine restricting sudo to execute let's say bash only. That way it would automatically be set on your shell. However, I am not sure about inheritance rules, so there could be a loop.
  • laolux
    laolux over 3 years
    @Joshua yes, on systemd it is very easy to re-exec init: init u. Unfortunately I do not know enough about systemd to know how to change the configuration, but I could imagine that changing the configuration may be restricted. For example, on Fedora kexec is outright blocked, so even root cannot do as it pleases.
  • F. Hauri
    F. Hauri over 3 years
    Please try this: IFS=/ read foo{,} tty < <(tty), then ps --tty $tty fw! Anyway, this fail if sudo is renamed, if user run sudo su -, then screen...
  • F. Hauri
    F. Hauri over 3 years
    Best attended answer, I think! You've been cited in my answer