How to correctly secure this Bash Script? (Sudo) - linux

I wanted to make this Script kinda Secure, so it cannot be direct exploited.
I have a Bash file called Test.sh, I gave a User Sudo rights on it:
user ALL=(root) NOPASSWD: /home/user/Test.sh
So the User has full Sudo permission to this file.
Next Step was to ensure that the User cannot edit this file with:
chown root:root /home/user/Test.sh
chmod u=rwx,g=rwx,o=rx /home/user/Test.sh
The File for example contains this Command
if [ "$1" = "run" ]; then
sudo -u ${2} ${3};
fi
OR
`sudo cp -R /home/test/test/${2}/* /home/${3}/test/
useradd ${2} -r -d /home/${2} -s /bin/bash
userdel -r ${2}
Basically the User could even Login as root and fuck things up.
So my first thought was, lets check if the Home folder exists like that:
if [ -d "/home/$2" ] && [ "$2" != "" ]; then
Which would prevent such things like run crap as root and only let them log into th other users like i want. Or do i think wrong?
I would also check that the Command begins with /home/....
grep '^/home/....' $3
So, is that enought? or Not? I guess I should filter also ;

Related

Linux shell script to know if the directory (or file) has 777 permission

We give the upmost permission to a file or directory, using this command:
sudo chmod -R 777 directory
Now I want to know if this command is already executed for a given directory.
I know I can use -r for read, -w for write, and -x for execution, in [ test ] blocks.
But I want to know two things:
Is it also a directory?
Does it have those permissions for everyone?
How can I get that info?
Update
Based on #Barmar comment, I came up with this. But it's not working:
if [ stat /Temp | grep -oP "(?<=Access: \()[^)]*" == '' ]; then
echo '/Temp folder has full access'
else
sudo chmod -R 777 /Temp
fi
This command works though:
stat /Temp | grep -oP "(?<=Access: \()[^)]*"
# prints => 0777/drwxrwxrwx
How should I fix the syntax error of my if-else statement?
You don't need to process the output of stat with grep; you can ask stat to only produce the specific information you want. See the man page regarding the --format option. We can for example write:
# ask stat for the file type and mode, and put those values into $1
# and $2
set -- $(stat --format '%F %a' /Temp)
if [[ $1 == directory ]]; then
if [[ $2 == 777 ]]; then
echo "/Temp folder has full access"
else
sudo chmod -R 777 /Temp
fi
else
echo "ERROR: /Temp is not a directory!" >&2
fi
A simple example:
#!/bin/bash
function setfullperm(){
[ -d $1 ] && \
(
[ "$(stat --format '%a' $1)" == "777" ] && \
echo "Full permissions are applied." || \
( echo "Setting full permissions" && sudo chmod -R 777 $1 )
) || \
( echo "$1 is not a directory !" && mkdir $1 && setfullperm $1 )
}
export setfullperm
Source the script:
$ source example.sh
Set full permissions (777) on any directory, it tests if the directory exists in the first place, if not it will create it and set the permissions.
It will export the function setfullperm to the shell so you can run it:
>$ setfullperm ali
ali is not a directory !
mkdir: created directory 'ali'
Setting full permissions
>$ setfullperm ali
Full permissions are applied.
If using zsh (But not other shells), you can do it with just a glob pattern:
setopt extended_glob null_glob
if [[ -n /Temp(#q/f777) ]]; then
echo '/Temp folder has full access'
else
sudo chmod -R 777 /Temp
fi
The pattern /Temp(#q/f777) will, with the null_glob and extended_glob options set, expand to an empty string if /Temp is anything but a directory with the exact octal permissions 0777 (And to /Temp if the criteria are met). For more details, see Glob Qualifiers in the zsh manual.
I don't recommend using stat for this. Though widespread, stat isn't POSIX, which means there's no guarantee that your script will work in the future or work on other platforms. If you're writing scripts for a production environment, I'd urge you to consider a different approach.
You're better off using ls(1)'s -l option and passing the file as an argument. From there you can use cut(1)'s -c option to grab the file mode flags.
Get file type:
ls -l <file> | cut -c1
Also, don't forget about test's -d operator, which tests if a file is a directory.
Get owner permissions:
ls -l <file> | cut -c2-4
and so on.
This approach is POSIX compliant and it avoids the shortcomings of using stat.

Unix: 'su user' not working and remains root inside SSH if condition [duplicate]

I've written a script that takes, as an argument, a string that is a concatenation of a username and a project. The script is supposed to switch (su) to the username, cd to a specific directory based upon the project string.
I basically want to do:
su $USERNAME;
cd /home/$USERNAME/$PROJECT;
svn update;
The problem is that once I do an su... it just waits there. Which makes sense since the flow of execution has passed to switching to the user. Once I exit, then the rest of the things execute but it doesn't work as desired.
I prepended su to the svn command but the command failed (i.e. it didn't update svn in the directory desired).
How do I write a script that allows the user to switch user and invoke svn (among other things)?
Much simpler: use sudo to run a shell and use a heredoc to feed it commands.
#!/usr/bin/env bash
whoami
sudo -i -u someuser bash << EOF
echo "In"
whoami
EOF
echo "Out"
whoami
(answer originally on SuperUser)
The trick is to use "sudo" command instead of "su"
You may need to add this
username1 ALL=(username2) NOPASSWD: /path/to/svn
to your /etc/sudoers file
and change your script to:
sudo -u username2 -H sh -c "cd /home/$USERNAME/$PROJECT; svn update"
Where username2 is the user you want to run the SVN command as and username1 is the user running the script.
If you need multiple users to run this script, use a %groupname instead of the username1
You need to execute all the different-user commands as their own script. If it's just one, or a few commands, then inline should work. If it's lots of commands then it's probably best to move them to their own file.
su -c "cd /home/$USERNAME/$PROJECT ; svn update" -m "$USERNAME"
Here is yet another approach, which was more convenient in my case (I just wanted to drop root privileges and do the rest of my script from restricted user): you can make the script restart itself from the correct user. This approach is more readable than using sudo or su -c with a "nested script". Let's suppose it is started as root initially. Then the code will look like this:
#!/bin/bash
if [ $UID -eq 0 ]; then
user=$1
dir=$2
shift 2 # if you need some other parameters
cd "$dir"
exec su "$user" "$0" -- "$#"
# nothing will be executed beyond that line,
# because exec replaces running process with the new one
fi
echo "This will be run from user $UID"
...
Use a script like the following to execute the rest or part of the script under another user:
#!/bin/sh
id
exec sudo -u transmission /bin/sh - << eof
id
eof
Use sudo instead
EDIT: As Douglas pointed out, you can not use cd in sudo since it is not an external command. You have to run the commands in a subshell to make the cd work.
sudo -u $USERNAME -H sh -c "cd ~/$PROJECT; svn update"
sudo -u $USERNAME -H cd ~/$PROJECT
sudo -u $USERNAME svn update
You may be asked to input that user's password, but only once.
It's not possible to change user within a shell script. Workarounds using sudo described in other answers are probably your best bet.
If you're mad enough to run perl scripts as root, you can do this with the $< $( $> $) variables which hold real/effective uid/gid, e.g.:
#!/usr/bin/perl -w
$user = shift;
if (!$<) {
$> = getpwnam $user;
$) = getgrnam $user;
} else {
die 'must be root to change uid';
}
system('whoami');
This worked for me
I split out my "provisioning" from my "startup".
# Configure everything else ready to run
config.vm.provision :shell, path: "provision.sh"
config.vm.provision :shell, path: "start_env.sh", run: "always"
then in my start_env.sh
#!/usr/bin/env bash
echo "Starting Server Env"
#java -jar /usr/lib/node_modules/selenium-server-standalone-jar/jar/selenium-server-standalone-2.40.0.jar &
#(cd /vagrant_projects/myproj && sudo -u vagrant -H sh -c "nohup npm install 0<&- &>/dev/null &;bower install 0<&- &>/dev/null &")
cd /vagrant_projects/myproj
nohup grunt connect:server:keepalive 0<&- &>/dev/null &
nohup apimocker -c /vagrant_projects/myproj/mock_api_data/config.json 0<&- &>/dev/null &
Inspired by the idea from #MarSoft but I changed the lines like the following:
USERNAME='desireduser'
COMMAND=$0
COMMANDARGS="$(printf " %q" "${#}")"
if [ $(whoami) != "$USERNAME" ]; then
exec sudo -E su $USERNAME -c "/usr/bin/bash -l $COMMAND $COMMANDARGS"
exit
fi
I have used sudo to allow a password less execution of the script. If you want to enter a password for the user, remove the sudo. If you do not need the environment variables, remove -E from sudo.
The /usr/bin/bash -l ensures, that the profile.d scripts are executed for an initialized environment.

Retain root privileges during long processes

I have a bash script that makes a backup of my data files (~50GB). The script is basically something like this:
sudo tar /backup/mydata1 into old-backup-1.tar
sudo tar /backup/mydata2 into old-backup-2.tar
sudo rsync /mydata1 to /backup/mydata1
sudo rsync /mydata2 to /backup/mydata2
(I use sudo because some of the files are owned by root).
The problem is that after every command (because it takes a long time) I loose root privileges and if I'm not present at the computer then the su prompt gets timed out and the script ends in the middle of the job.
Is there a way to retain su privileges during the entire script? What is the best way to approach this situation? I prefer to run the script under my user.
With a second shell:
sudo bash -c "command1; command2; command3; command4"
Perhaps like this:
#!/bin/bash -eu
exec sudo /bin/bash <<'EOF'
echo I am $UID
whoami
#^the script
EOF
Alternatively, you could put something like:
if ! [ $(id -u) -eq 0 ]; then
exec sudo "$0" "$#"
fi
at the top.

"usermod: UID '0' already exists" why?

As part of my Docker image I have the following ENTRYPOINT script:
#!/bin/bash
set -e
if [ -z "${UID}" ]; then
uid=1000;
else
uid=${UID};
fi
if [ -z "${GID}" ]; then
gid=1000;
else
gid=${GID};
fi
echo "UID: $uid"
echo "GID: $gid"
data_dir="/var/www/html"
usermod -u "$uid" www-data && groupmod -g "$gid" www-data
chown -R www-data:root "$data_dir"
if [ -d "$data_dir" ]; then
chgrp -RH www-data "$data_dir"
chmod -R g+w "$data_dir"
find "$data_dir" -type d -exec chmod 2775 {} +
find "$data_dir" -type f -exec chmod ug+rw {} +
fi
composer_cache_dir="/var/www/.composer"
mkdir -p "$composer_cache_dir"
chown -R www-data:root "$composer_cache_dir"
if [ -d "$composer_cache_dir" ]; then
chgrp -R www-data "$composer_cache_dir"
chmod -R g+w "$composer_cache_dir"
fi
a2enmod rewrite expires
rm -f /var/run/apache2/apache2.pid
source /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND "$#"
The image compiles successfully. When I try to run the container by running the following command docker run -it temp bash I got the following output:
UID: 0
GID: 1000
usermod: UID '0' already exists
Enabling module rewrite.
Enabling module expires.
To activate the new configuration, you need to run:
service apache2 restart
AH00112: Warning: DocumentRoot [/var/www/html/public_html] does not exist
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
Why my UID is 0? I can't find where is my error here, any help is more than welcome.
Update:
Changing this lines on the script:
if [ "$UID" != 0 ]; then
uid=1000;
else
uid=${UID};
fi
Still making it to assign 0 to uid, is because of the var name? (I have rebuilt the image using docker build --no-cache -t temp . so no cache is being used)
Because $UID in Bash expands to the current user id of the shell, and it's never empty, so [ -z "${UID}" ] is never true. So, you're setting uid=${UID};, probably uid=0 if the script runs as root.
Then usermod -u 0 www-data complains because it tries to change the UID of www-data to zero, but that's already in use, and it doesn't do that without the -o flag. (And you'd never want to do that anyway...)
If you want to test if $UID is zero, use [ "$UID" == 0 ]. Or [[ $UID == 0 ]] in Bash. Or [ "$UID" -eq 0 ] for a numeric comparison, not that it matters.
(I'm not exactly sure if changing the uid of www-data to 1000 is a good idea in any case, you already must have the user for usermod to be able to change its uid. But that wasn't the question.)
Bash:
UID
Expands to the user ID of the current user, initialized at shell startup. This variable is readonly.
usermod:
-u, --uid UID
The new numerical value of the user's ID.
This value must be unique, unless the -o option is used. The value must be non-negative.

How to make a script run commands as root

I'm new to Ubuntu and bash scripts, but I just made runUpdates.sh and added this to my .profile to run it:
if [ -f "$HOME/bin/runUpdates.sh" ]; then
. "$HOME/bin/runUpdates.sh"
fi
The problem I'm having is, I want the script to run as if root is running it (because I don't want to type my sudo password)
I found a few places that I should be able to do sudo chown root.root <my script> and sudo chmod 4755 <my script> and when I run it, it should run as root. But it's not...
The script looks good to me. What am I missing? -rwxr-xr-x 1 root root 851 Mar 23 21:14 runUpdates.sh*
Can you please help me run the commands in this script as root? I don't really want to change the sudors file, I really just want to run the commands in this script at root (if possible).
#!/bin/sh
echo "user is ${USER}"
#check for updates
update=`cat /var/lib/update-notifier/updates-available | head -c 2 | tail -c 1`;
if [ "$update" = "0" ]; then
echo -e "No updates found.\n";
else
read -p "Do you wish to install updates? [yN] " yn
if [ "$yn" != "y" ] && [ "$yn" != "Y" ]; then
echo -e 'No\n';
else
echo "Please wait...";
echo `sudo apt-get update`;
echo `sudo apt-get upgrade`;
echo `sudo apt-get dist-upgrade`;
echo -e "Done!\n";
fi
fi
#check for restart
restartFile=`/usr/lib/update-notifier/update-motd-reboot-required`;
if [ ! -z "$restartFile" ]; then
echo "$restartFile";
read -p "Do you wish to REBOOT? [yN] " yn
if [ "$yn" != "y" ] && [ "$yn" != "Y" ]; then
echo -e 'No\n';
else
echo `sudo shutdown -r now`;
fi
fi
I added the user is to debug, it always outputs my user not root, and prompts for the sudo password (since I'm calling the commands with sudo) or tells me are you root? (if I remove sudo)
Also, is there a way to output the update commands stdout in real time, not just one block when they finish?
(I also tried with the shebang as #!/bin/bash)
setuid does not work on shell scripts for security reasons. If you want to run a script as root without a password, you can edit /etc/sudoers to allow it to be run with sudo without a password.
To "update in real time", you would run the command directly instead of using echo.
Its not safe to do, you should probably use sudoers but if you really need/want to, you can do it with something like this:
echo <root password> | sudo -S echo -n 2>/dev/random 1>/dev/random
sudo <command>
This works because sudo doesn't require a password for a brief window after successfully being used.
SUID root scripts were phased out many years ago if you really want to run scripts as root you need to wrap them in an executable, you can see an example on how to do this on my blog:
http://scriptsandoneliners.blogspot.com/2015/01/sanitizing-dangerous-yet-useful-commands.html
The example is how to change executable permissions and place a filter around other executables using a shell script but the concept of wrapping a shell script works for SUID as well, the resulting executable file from the shell script can be made SUID.
https://help.ubuntu.com/community/Sudoers

Resources