高级shell

Table of Contents

1 trap

假设有一个shell脚本,它在中途会产生一些临时文件,但在工作作期间突然被中断执行了,那这些临时文件是不能自动清理的。有了trap,就可以捕获中断信号,然后做相应的文件清理。

trap可以想象为shell的异常处理,trap根据捕获进程信号来执行指定的命令。

命令格式:

trap command 信号

Linux具体的信号可以通过man获得: man 7 signal。只是trap命令写信号时不需要“SIG”开头。

例:

trap 'echo bye' INT

while (true); do
    echo '1'
done

当Ctrl+c中断脚本时,会执行echo bye。注意trap的位置,要在写在被“保护”的代码之前。

2 eval

将字符串交给shell再次扫描并执行,比如x文件的内容:

$ cat x
ls /etc

然后用eval可以让x里的内容再次执行:

eval $(cat x)

例,远程另外一台机器登录我的笔记本,无法ssh-add:

$ ssh-add
Could not open a connection to your authentication agent.

这个时候需要执行ssh-agent的内容:

$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-mVzghWGagC5W/agent.11859; export SSH_AUTH_SOCK;
SSH_AGENT_PID=11860; export SSH_AGENT_PID;
echo Agent pid 11860;

便可以用eval:

$ eval $(ssh-agent)
Agent pid 11864

谨慎使用eval,eval会产生一定的安全隐患,如果输入源不可控就有可能产生任意命令执行漏洞。

3 shell安全

由于shell简单灵活,一不小心就掉进坑里,我们要时刻注意一些问题,这些问题有可能会造成安全隐患。

3.1 LD_PRELOAD环境变量

在国外网上流传了这么一段脚本,号称运行后就可获得root权限:

#!/bin/sh
echo "1|nux r007 3xp10|7 by 1c4m7uf"
cd /tmp
cat >ex.c <<eof
int getuid() { return 0; }
int geteuid() { return 0; }
int getgid() { return 0; }
int getegid() { return 0; }
eof
gcc -shared ex.c -oex.so
LD_PRELOAD=/tmp/ex.so sh
rm /tmp/ex.so /tmp/ex.c

执行结果如下:

bash test.sh
1|nux r007 3xp10|7 by 1c4m7uf
sh-4.3# id
uid=0(root) gid=0(root) 组=0(root),1000(lu4nx) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
sh-4.3#

这里要注意脚本用到了LD_PRELOAD环境变量,它可以指定一个动态链接库在加载其他.so(比如glibc)之前优先加载。上面的代码实质上编译了一个动态链接库,那段C代码重新实现了getuid、geteuid、getgid和getegid,并且全部返回0(返回0表示root),等同劫持了getuid等函数。shell权限在判断是root时,提示符会变成“#”;而id命令也是调用了这几个函数。这里实际上只是个假象,并没有真正获得root权限,但要注意这个环境变量可能导致其他程序逻辑判断。

3.2 通配符问题

shell在遇到字符“*”时,会把它转变成当前目录下全部文件,比如目录下有a、b和c三个文件,当执行“ls *”时,shell会将最终执行的命令转变成“ls a b c”。

假如目录下有一个文件名叫“-l”:

$  ls
-l

当执行:ls *时,-l会被展开成参数:

$  ls *
总用量 0
-rw-rw-r--. 1 lu4nx lu4nx 0 12月  8 10:37 -l

利用这个特性,我们可以把文件名构造得像参数一样,很容易引起安全隐患。

例1

当前目录下的两个文件:

$ ls
-c  '__import__('\''os'\'').system('\''id'\'')'

当执行python *时就会出问题:

$  python *
uid=1000(lu4nx) gid=1000(lu4nx) 组=1000(lu4nx) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

例2 当目录下有这几个文件时:

$ ls
-exec  id

执行find命令:

$  find * \;
uid=1000(lu4nx) gid=1000(lu4nx) 组=1000(lu4nx) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
uid=1000(lu4nx) gid=1000(lu4nx) 组=1000(lu4nx) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
uid=1000(lu4nx) gid=1000(lu4nx) 组=1000(lu4nx) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

唯一鸡肋的地方是命令必须加“;”,通过对Bash的源码调试,发现如果创建了一个叫“;”的文件,通配符展开时,“;”比较靠前,导致无法正确解析。展开后的命令如下:

find ; -exec id