13

Are there any methods to check what you are actually executing from a bash script?

Say your bash script is calling several commands (for example: tar, mail, scp, mysqldump) and you are willing to make sure that tar is the actual, real tar, which is determinable by the root user being the file and parent directory owner and the only one with write permissions and not some /tmp/surprise/tar with www-data or apache2 being the owner.

Sure I know about PATH and the environment, I'm curious to know whether this can be additionally checked from a running bash script and, if so, how exactly?

Example: (pseudo-code)

tarfile=$(which tar)
isroot=$(ls -l "$tarfile") | grep "root root"
#and so on...
  • 2
    If you are so paranoid, then use your own binaries! – Ipor Sircer Jan 12 '17 at 9:59
  • 8
    In addition to which not correctly saying what tar will do, as answered by xhienne, ls could be hacked to return false info about the file(s), if any. Also grep could be hacked to return false info; that could be avoided by using shell matching instead, but then shell could be hacked. And shell could be hacked to give wrong results from type in the first place -- or replaced entirely since replacability of the shell was an important innovation of Unix compared to 50-year-old OSes. See Ken Thompson's 1984 Turing address. It's turtles all the way down. – dave_thompson_085 Jan 12 '17 at 12:24
  • 2
    I cannot answer this for Linux - only AIX - which has a component called Trusted Execution (TE) - that has a database with signatures (i.e., more extensive than an MD5 checksum. When TE is active AND a file is in the database you can choose whether the program runs - or only warns that it did not match the database. Further, there are two other settings: TEP (trusted execution PATH) and TLP (trusted LIBrary PATH). Only programs in TEP may be executed and libraries may only be loaded with the directory is included in TLP. In Linux I there is something called 'AppArmor' that may help you. – Michael Felt Jan 12 '17 at 12:35
  • 1
    You can have this kind of safety, but not from a script -- by the time your script is executing in an uncontrolled environment, it's too late. For all you know everything you can see is a chroot set up by an attacker. – Charles Duffy Jan 12 '17 at 18:38
  • 2
    ...if you want to have a system that's trusted all the way down, you need to go the ChromeOS approach: Have your firmware signed with a key embedded in your hardware; your bootloader/kernel verified by the firmware; your root OS partition read-only using block-level signatures for verification; etc. There are also approaches similar to what @MichaelFelt discusses available -- see the Integrity Measurement Architecture -- but the performance impact is higher and the level of integrity reduced (since checking binary signatures doesn't help you with attacks via non-executable content). – Charles Duffy Jan 12 '17 at 18:41
24

Instead of validating binaries you're going to execute, you could execute the right binaries from the start. E.g. if you want to make sure you're not going to run /tmp/surprise/tar, just run /usr/bin/tar in your script. Alternatively, set your $PATH to a sane value before running anything.

If you don't trust files in /usr/bin/ and other system directories, there's no way to regain confidence. In your example, you're checking the owner with ls, but how do you know you can trust ls? The same argument applies to other solutions such as md5sum and strace.

Where high confidence in system integrity is required, specialized solutions like IMA are used. But this is not something you could use from a script: the whole system has to be set up in a special way, with the concept of immutable files in place.

  • Which breaks when different distributions choose to put binaries in /bin instead of /usr/bin. – Damian Yerrick Jan 12 '17 at 16:22
  • IMA is one of the two production-ready approaches to this -- the other is the dm-verity approach taken by ChromeOS to do block-level validation of the rootfs. – Charles Duffy Jan 12 '17 at 18:47
  • @DamianYerrick Fair remark. Set $PATH to both those paths then, if multiple distribution support is needed. – Dmitry Grigoryev Jan 12 '17 at 21:49
  • AIX TE (with or without RBAC) would be a third "production-ready" kernel-builtin that will accomplish this - maybe more. TE, once enabled to be more than passive - will prevent files from being opened and/or programs from being executed. Additionally, applications and library usage can be set to be exclusively on TEP (trusted execution path) or TLP (trusted library path). See ibm.com/support/knowledgecenter/en/ssw_aix_61/… for basic info – Michael Felt Jan 25 '17 at 15:16
6

If an intruder has gained access to your system and is able to modify your $PATH (which should not include /tmp under any circumstances), then it's too late to start worrying about the ownerships of the executables.

Instead you should read about how to deal with an intrusion.

Better to concentrate on avoiding intrusion altogether.

If you have a system where these sorts of things matter, then it may be a good idea to isolate the parts of it that needs to be public from the parts that needs to be private, as well as performing an audit of the modes of communication between these.

4

It is possible to some extent by verifying the md5sum of a file. Thus on systems that use apt package management - in my particular case, Ubuntu 16.04 - there is the file /var/lib/dpkg/info/tar.md5sums, which stores the md5 sums of all files that came from tar during installation. So you could write a simple if-statement that checks whether the output of md5sum /bin/tar matches what is in that file.

That of course assumes that the file itself hasn't been tampered with. This of course can only happen when attacker has gotten root/sudo access, at which point all bets are off.

  • 8
    But how do you validate /usr/bin/md5sum? – Dmitry Grigoryev Jan 12 '17 at 12:55
  • If an attacker is able to replace /bin/tar or /usr/bin/tar, it is very likely they can also simply replace md5sum or /var/lib/dpkg/info/tar.md5sums. Or $SHELL. – Jonas Schäfer Jan 12 '17 at 16:14
  • 1
    I think I already mentioned in last paragraph, that for such thing to occur , an attacker would need to gain root access to the system, and at that point anything is possible. In cases , where attacker doesn't have root access , but can alter PATH variable for a user or create an alias where tar points to different binary , that will work. When a system is compromised on the root level, you have one option then - nuke it from the orbit – Sergiy Kolodyazhnyy Jan 12 '17 at 20:42
3

Yes, there is a method: the builtin type. Contrary to the which command which only search in your PATH, type will tell you whether the command name is actually a reserved keyword, a builtin, an alias, a function or a disk file.

$ type -t foobar || echo "Not found"
Not found

$ type -t echo
builtin

$ enable -n echo; type -t echo; type -p echo
file
/usr/bin/echo

$ echo() { printf "(echoing) %s\n" "$*"; }; type -t echo
function

$ alias echo="/bin/echo 'I say: ' "; type -t echo
alias

In addition type -a will give you all the candidates for your command (from the first to the last choice):

$ type -a echo
echo is aliased to `/bin/echo 'I say: ' '
echo is a function
echo () 
{ 
    printf "(echoing) %s\n" "$*"
}
echo is a shell builtin
echo is /usr/local/bin/echo
echo is /bin/echo

Finally, if you are only concerned about the binaries on your disk, you can use type -Pa to obtain all the binaries in your PATH (same order as above):

$ type -Pa tar
/home/me/bin/tar                <= oh oh, is this normal?
/bin/tar

That said, type alone won't tell you exactly what command will be called in the end. For example, if your tar is an alias that calls a binary (e.g. alias tar="/tmp/tar") then type will tell you this is an alias.

  • type -a includes all forms (e.g. both alias and external program) – dave_thompson_085 Jan 12 '17 at 12:19
  • Thank you @dave, it's indeed interesting, I have updated my answer – xhienne Jan 12 '17 at 18:31
  • 1
    type will tell you inasfar as bash knows, but if we're under control from a malicious attacker, there's no reason to believe that what bash thinks it knows reflects actual truth. For all you know there's an LD_PRELOAD module intercepting every single C-library call you make. – Charles Duffy Jan 12 '17 at 18:39
  • 1
    @CharlesDuffy You are right of course. I didn't want to answer towards the security angle. I'm just proposing an answer to the question at the top: "Are there any methods to check what you are actually executing from a bash script" and proposed an alternative to which. – xhienne Jan 12 '17 at 18:53
  • I have never seen enable before. I used the advice from these answers to run type enable to find out it's a shell built-in and then help enable to see what it does. – Joe Jan 13 '17 at 22:29
3

You can check what commands are exactly being executed by a script by using strace. For example:

strace -f -e execve ./script.sh

With the following script:

#!/bin/bash
touch testfile.txt
echo "Hello" >> testfile.txt
cat testfile.txt
rm testfile.txt

strace will tell you the exact path to the commands executed when used with -e execve parameter:

execve("./script.sh", ["./script.sh"], [/* 69 vars */]) = 0 
Process 8524 attached
[pid  8524] execve("/usr/bin/touch", ["touch", "testfile.txt"], [/* 68 vars */]) = 0 
[pid  8524] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=8524, si_status=0, si_utime=0, si_stime=0} --- 
Process 8525 attached [pid > 8525] execve("/bin/cat", ["cat", "testfile.txt"], [/* 68 vars */]) = 0
Hello [pid  8525] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=8525, si_status=0, si_utime=0, si_stime=0} --- 
Process 8526 attached [pid > 8526] execve("/bin/rm", ["rm", "testfile.txt"], [/* 68 vars */]) = 0
[pid  8526] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=8526, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

Parameters (from strace man):

-f: Trace child processes as they are created by currently traced processes as a result of the fork(2), vfork(2) and clone(2) system calls. Note that -p PID -f will attach all threads of process PID if it is multi-threaded, not only thread with thread_id = PID.

-e trace=file: Trace all system calls which take a file name as an argument. You can think of this as an abbreviation for -e trace=open,stat,chmod,unlink,... which is useful to seeing what files the process is referencing. Furthermore, using the abbreviation will ensure that you don't accidentally forget to include a call like lstat in the list.

  • 3
    This isn't by any means usable by a script to perform automated testing, and there's no particular reason to believe that strace hasn't itself been subverted. – Charles Duffy Jan 12 '17 at 18:43
0

Linux os is based on files and many commands executed on linux will likely resolve to some changes in files located on your machine. Because of that maybe is the best solution for your problem. You can test your commands for any changes on file system before its going to be executed.

enter image description here

There is 'strace' command which decompiles your command in parts...

enter image description here

If you really want to go deep you want to checkout decompilers for the scripts that its going to be executed. In other words you must check assembler interpretation of that command. For bash there objdump -d. Linux bin scripts are mainly created with C programming language so use good C decompiler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.