高级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