I have a simple shell script that looks like this :
#!/bin/bash
AMAVIS_VIRUSMAILS="/var/lib/amavis/virusmails"
if [ -d "${AMAVIS_VIRUSMAILS}" ]; then
echo "ok"
fi
/var/lib/amavis and its sub-directories are, of course, owned by the amavis user. Which means that I get :
$ ls /var/lib/amavis/virusmails
ls: cannot access /var/lib/amavis/virusmails: Permission denied
When I run the ls command as another user without root or sudo privileges.
However. If I run the above shell script, it doesn't print "ok". Which, in other words, means the shell script is telling me the directory does not exist. Which is of course not true, it does exist, but I just don't have the permissions to access it.
I get the same result with #!/bin/sh
I therefore have two questions :
Is this the as expected and "as designed" behavior of the -d test ?
How should I best work around the problem so as to avoid false negatives ?
If it makes any difference, this is Ubuntu 14.04LTS, 3.13.0-53-generic. Which means I'm running GNU bash, version 4.3.11(1)-release (x86_64-pc-linux-gnu), or dash(sh) version 0.5.7-4ubuntu1
Which, in other words, means the shell script is telling me the directory does not exist. Which is of course not true, it does exist, but I just don't have the permissions to access it.
Yes. You can tell if do not have access => it does not exists for you (this is not fully proper, as you i.e. won't be able to create such directory, but that's another case anyway and you should be dealing with it in multiuser environment anyway)
Never mind, I just resorted to checking
$EUID -ne 0
Related
I recently came across this snippet on the following site:
https://www.linuxjournal.com/content/writing-secure-shell-scripts
Here's the script:
#!/bin/sh
if [ "$USER" = "root" ] ; then
/bin/cp /bin/sh /tmp/.secretshell
/bin/chown root /tmp/.secretshell
/bin/chmod 4666 root /tmp/.secretshell
fi
exec /bin/ls $*
Let's assume that the person who runs this code has low-level access to the system (i.e. they can write to /tmp/), and that the system is not 'hardened'.
In the link above, the author of the code says that, "This simple little script has created a shell that always grants its user root access to the Linux system."
The idea is that the attacker would write the script above, name it ls, and drop it in the /tmp/ directory on the system. Any user running ls (rather than /bin/ls) in /tmp/ will therefore inadvertently run this script. If the user running ls happens to be root, he/she will trigger the (malicious) code in the enclosing if/fi block. To conceal that anything harmful has happened, the directory listing that the user wants will still execute as expected due to the final exec /bin/ls $* line.
What I don't quite understand is what the final line of the if/fi block is doing. This is how I interpret the first two lines of the if/fi block:
In the line /bin/cp /bin/sh /tmp/.secretshell, the script copies the /bin/sh binary to /tmp/, renaming it to .secretshell, a hidden file. OK fine.
In the line /bin/chown root /tmp/.secretshell, the script changes the owner of .secretshell to root. OK fine.
What I don't quite understand is the line /bin/chmod 4666 root /tmp/.secretshell. As far as I know, I think this line is meant to flip the setuid bit for .secretshell, so that every time .secretshell is run, it is run as its owner (now root). This would (I suppose) give anyone running .secretshell the ability to run sh as root. But there are two things here which seem problematic:
1) How can root be inserted as the second argument to /bin/chmod, when chmod is expecting a directory or file name after the permissions argument?
2) Doesn't the *666 part of the permissions argument make .secretshell non-executable by converting its permissions mask to -rwSrw-rw-? If the intent is to execute .secretshell, how can this be desirable?
Thanks for your help!
The article contains several mistakes and fundamental misunderstandings about shell scripting:
You're right about the extra root, this is probably a copy-paste error.
You're right about the lack of executable permissions. The author did not test their own code.
The whole approach fails on non-Debian based systems like CentOS or macOS where sh is bash, because bash drops suid.
The author claims that ls -l $name where name='. ; /bin/rm -Rf /' will execute the rm. This is false.
The author further appears to claim that ls -l "$name" where name='. `/bin/rm -Rf /`' will execute the command. This is also false.
I would suggest taking the whole article with a grain of salt.
We have two bash scripts to start up an application. The first (Start-App.sh) one sets up the environment and the second (startup.sh) is from a 3rd party that we are trying not to heavily edit. If someone runs the second script before the first the application does not come up correctly.
Is there a way to ensure that the startup.sh can only be called from the Start-App.sh script?
They are both in the same directory and run via bash on Red Hat Linux.
Is there a way to ensure that the startup.sh can only be called from the Start-App.sh script?
Ensure? No. And even less so without editing startup.sh at all. But you can get fairly close.
Below are three suggestions − you can either use one of them, or any combination of them.
The simplest, and probably the best, way is to add a single line at the top of startup.sh:
[ -z $CALLED_FROM_START_APP ] && { echo "Not called from Start-App.sh"; exit 42; }
And then call it from Start-App.sh like so:
export CALLED_FROM_START_APP=yes
sh startup.sh
of course, you can set this environment variable from the shell yourself, so it won't actually ensure anything, but I hope your engineering staff is mature enough not to do this.
You can also remove the execute permissions from startup.sh:
$ chmod a-x startup.sh
This will not prevent people from using sh startup.sh, so there is a very small guarantee here; but it might prevent auto-completion oopsies, and it will mark the file as "not intended to be executed" − if I see a directory with only one executable .sh file, I'll try and run that one, and not one of the others.
Lastly, you could perhaps rename the startup.sh script; for example, you could rename it to do_not_run, or "hide" it by renaming it to .startup. This probably won't interfere with the operation of this script (although I can't check this).
TL;DR:
[ $(basename "$0") = "Start-App.sh" ] || exit
Explanation
As with all other solutions presented it's not 100% bulletproof but this covers most common instances I've come across for preventing accidentally running a script directly as opposed to calling it from another script.
Unlike other approaches presented, this approach:
doesn't rely on manually set file names for each included/sourced script (i.e. is resilient to file name changes)
behaves consistently across all major *nix distros that ship with bash
introduces no unnecessary environment variables
isn't tied to a single parent script
prevents running the script through calling bash explicitly (e.g. bash myscript.sh)
The basic idea is having something like this at the top of your script:
[ $(basename "$0") = $(basename "$BASH_SOURCE") ] && exit
$0 returns the name of the script at the beginning of the execution chain
$BASH_SOURCE will always point to the file the currently executing code resides in (or empty if no file e.g. piping text directly to bash)
basename returns only the main file name without any directory information (e.g. basename "/user/foo/example.sh" will return example.sh). This is important so you don't get false negatives from comparing example.sh and ./example.sh for example.
To adapt this to only allow running when sourced from one specific file as in your question and provide a helpful error message to the end user, you could use:
[ $(basename "$0") = "Start-App.sh" ] || echo "[ERROR] To start MyApplication please run ./Start-App.sh" && exit
As mentioned from the start of the answer, this is not intended as a serious security measure of any kind, but I'm guessing that's not what you're looking for anyway.
You can make startup.sh non-executable by typing chmod -x startup.sh. That way the user would not be able to run it simply by typing ./startup.sh.
Then from Start-App.sh, call your script by explicitly invoking the shell:
sh ./startup.sh arg1 arg2 ...
or
bash ./startup.sh arg1 arg2 ...
You can check which shell it's supposed to run in by inspecting the first line of startup.sh, it should look like:
#!/bin/bash
You can set environment variable in your first script and before running second script check if that environment variable is set properly.
Another alternative is checking the parent process and finding the calling script. This also needs adding some code to the second script.
For example, in the called script, you can check the exit status of this and terminate.
ps $PPID | tail -1 | awk '$NF!~/parent/{exit 1}'
As others have pointed out, the short answer is "no", although you can play with permissions all day but this is still not bulletproof. Since you said you don't mind editing (just not heavily editing) the second script, the best way to accomplish this would be something along the lines of:
1) in the parent/first script, export an environment variable with its PID. This becomes the parent PID. For example,
# bash store parent pid
export FIRST_SCRIPT_PID = $$
2) then very briefly, in the second script, check to see if the calling PID matches the known acceptable parent PID. For example,
# confirm calling pid
if [ $PPID != $FIRST_SCRIPT_PID ] ; then
exit 0
fi
Check out these links here and here for reference.
To recap: the most direct way to do this is adding at least a minimal line or two to the second script, which hopefully doesn't count as "heavily editing".
You can create a script, let's call it check-if-my-env-set containing
#! /bin/bash
source Start-App.sh
exec /bin/bash $#
and replace the shebang (see this) on startup.sh by that script
#! /abs/path/to/check-if-my-env-set
#! /bin/bash
...
then, every time you run startup.sh it will ensure the environment is set correctly.
To the best of my knowledge, there is no way to do this in a way that it would be impossible to get around it.
However, you could stop most attempts by using permissions.
Change the owner of the startup.sh file:
sudo chown app_specific_user startup.sh
Make startup.sh only executable by the owner:
chmod u+x startup.sh
Run startup.sh as the app_specific_user from Start-App.sh:
sudo -u app_specific_user ./startup.sh
When I run lks.sh file in my system it show permission denied:
./lks.sh bash: ./lks.sh: Permission denied
What do I have to do to get this shell script to run?
This is my .sh file:
lokesh = "wait"
if[$lokesh == "wait"]
echo "$lokesh"
else
sudo shutdown -h now
Your script has a few issues.
First the “Permission Denied” is most likely because your script does not have execute rights which would allow the script to actually run. So you need to chmod it like this:
chmod 755 lks.sh
And then you should be able to run it. FWIW, the 7 and 755 gives you—the owner—execute, read & write permissions while the 5 gives group members and others execute & read permissions. Feel free to change that to 744 so you are the only one who can edit that script but others—via 4—can read it. Or even 700 so you are the only one who can ever do anything with that script.
But that said, your variable assignment for this seems off:
lokesh = "wait"
In my experience, there should be no spaces around the = like this:
lokesh="wait"
Next the spacing of this is syntactically incorrect:
if[$lokesh == "wait"]
It should be:
if [ $lokesh == "wait" ]
And finally your whole if/else syntax is incorrect; no then and no closing fi. So here is your final, cleaned up script:
lokesh="wait"
if [ $lokesh == "wait" ]; then
echo "$lokesh"
else
sudo shutdown -h now
fi
That said, the most immediate issue is the execute rights issue, but the other things will definitely choke your script as well.
You haven't made your script executable:
chmod +x lks.sh
Several issues with your shell script:
First as everyone has pointed out it requires executable bit to be turned on in order to run. You can do that by,
chmod a+x lks.sh
then running as you tried will work.
Now as #Giacomo1968 pointed out there are issues with your script:
First you should start all scripts with a shebang. This tells the operating system which interpreter to run for your script:
It should be the first line.
#!/bin/sh
Now that we've picked the interpreter to be Bourn Shell the rest is how to program what you wish correctly, I will list the correct code and explain it line by line
#!/bin/sh
okesh="wait"
if [ $lokesh = "wait" ]; then
echo "$lokesh"
else
sudo shutdown -h now
fi
White spaces matter in shell scripts
okesh="wait"
No spaces when specifying a shell variable, because the existance of = in the first token signifies to the shell that the statement is a variable declaration.
Otherwise if you do it like this:
okesh = "wait"
The shell will attempt to look for a program called okesh and if it finds it, execute it and pass = and "wait" as two command line arguments to it. Chances are you will get okesh: command not found error.
if[$lokesh=="wait"]
you need a space after if and a space between [ and $lokesh and another space between $lockesh and = and a space between = and "wait" and finally one between "wait" and ]
without all the spaces the shell thinks you are looking for a program named if[$lokesh=="wait"]
with first space it realizes you are starting an if block (which requires a closing fi at the very end)
if runs a program and looks at the return value, in this case that program happens to be test utility which has a synonym [. In most *nix systems, including linux, test has a symbolic link [ and when launched as '[' expects ']' as the last parameter. In bash (bourne again shell) that's found on most linux systems '[' is also a built-in function and acts the same way.
See test(1) man page for details on how [ works.
You are missing the mandatory then token, and the end fi token.
if is a built-in keyword. Use help if in bash to read how that works.
I'm trying to get Rails 4.1 to receive bounceback emails but it's been really difficult to even get to this point. I can run the command below in an SSH console when logged in as root, but when I put it in my /etc/valiases file, I get a bounceback from the script saying "the following addresses failed".
runuser -l useraccount -c "cd /home/useraccount/rails_deployments/dev.www/current/bin && rails runner -e development 'EBlast.receive(STDIN.read)'"
/etc/valiases/dev.mydomain.com
eblast-bounce#dev.mydomain.com: "|runuser -l useraccount -c "cd /home/useraccount/rails_deployments/dev.www/current/bin && rails runner -e development 'EBlast.receive(STDIN.read)'""
I've also tried escaping the double-quotes to no avail.
I need to run as useraccount because the RVM environment variables don't exist for root. Running the 1st command in an SSH console when logged in as root works, but not when exim receives an email.
You can't doublequote inside of doublequotes without doing some escaping. Once you start escaping quotes, it can get complicated knowing when you also need to escape other characters as well. Your example doesn't appear to get too complicated, but I suggest a different method.
IMHO you should create a shell script, for example eblast-bounce-script, with the piped commands you want to run. Then set your alias to:
eblast-bounce#dev.mydomain.com: "|/path/to/eblast-bounce-script"
Make sure the make the script executable, and runnable by the user that exim will be calling it as. If you make the script mode 755, owned by root, that should be sufficient.
There are a few things I had to do to work around the problem:
1) Move the runner script into its own file as Todd suggested; nested quotes were causing the script to fail to run.
2) Make the file executable; the permissions were already set to 755.
3) Even though exim was using my username to execute the script, the environment variables such as PATH and HOME were not set at all! This caused ruby to be an unknown command. This caused many other issues because most of the app relies upon RVM and its gemsets. So I couldn't get ruby to run, much less rails. Even if I were to explicitly call the ruby wrapper, spring would break because $HOME wasn't set. Just a cascade of issues because the user environment wasn't being set. I also couldn't just issue su - username -c 'whatever' because the account that exim was using didn't have authority to use su.
So the working setup looks like this:
/etc/valiases/dev.mydomain.com
eblast-bounce#dev.mydomain.com: "|/bin/bash -l -c '/home/useraccount/rails_deployments/dev.www/current/script/receive_eblast_bounce'"
*: ":fail: No Such User Here"
/home/useraccount/rails_deployments/dev.www/current/script/receive_eblast_bounce
D=`pwd`
HOME=/home/useraccount
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
if [[ -s "/home/useraccount/.rvm/scripts/rvm" ]] ; then
source "/home/useraccount/.rvm/scripts/rvm"
fi
cd /home/useraccount/rails_deployments/dev.www/current
./bin/rails runner -e development 'EBlast.receive(STDIN.read)'
cd $D
I'm now having problems with ActionMailer using SSL when it shouldn't, and I don't know if that's related to something I did here, but at least it executes the rails script.
I'm sure this question has been answered before, but I can't find an answer that I like. I would like to write a shell script that executes a very specific script as another user (I want anyone to be able to start postgres as the postgres user). The script will have 710 perms so it will be executable by a certain group but not readable or writable by that group.
Now, I'm pretty sure there's no way to use 'su' without an interactive password prompt. There are lots of good reasons for that and I don't need to be convinced of the merit of those reasons (I'm told that someone savvier than me could grab the password off the processes list which is bad).
Question is, more generally how would I accomplish what I want to do without abusing unix security paradigms? Is there a way to allow user to execute a very specific process as another user?
This sort of situation is exactly what sudo was designed for.
You can create an executable (not a shell script) that launches the script that should run as the postgres user. Change the owner of the executable to the postgres user, and set the setuid bit.
See Best practice to run Linux service as a different user to address Celada's concern.
Well, you could use a simple script to access programmatically to an user using sudo and then execute all code you want.
Here is a simple script:
if [ "$#" -ne 2 ]; then
echo "Usage: "
echo " suprompt <user> <password>"
else
echo $2 | sudo -sS su $1
sudo su $1
fi
This script uses two arguments. The first one is the user you want to be, and the second arg is the password.
It works automatically.
You can change the final statement and do: sudo su $1 -c <command>
I hope this will work for you.