从0到1:CTFer成长之路
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2 命令执行漏洞

通常情况下,在开发者使用一些执行命令函数且未对用户输入的数据进行安全检查时,可以注入恶意的命令,使整台服务器处于危险中。作为一名CTFer,命令执行的用途如下:技巧型直接获取flag;进行反弹Shell,然后进入内网的大门;利用出题人对权限的控制不严格,对题目环境拥有控制权,导致其他队伍选手无法解题,这样在时间上会占一定优势。

在CTF中,命令执行一般发生在远程,故被称为远程命令执行,即RCE(Remote Command Exec),也被称为RCE(Remote Code Exec)。本节的RCE皆为远程命令执行。

本节将阐述常见的RCE漏洞和绕过WAF的方案,再通过一些经典题目让读者对CTF中的RCE题目有所了解。

2.2.1 命令执行的原理和测试方法

下面介绍命令注入的基本原理,包括cmd.exe、bash程序在解析命令的时候会存在哪些问题、在不同的操作系统中执行命令会存在哪些异同点等,以及在CTF题目中应该如何进行测试,直到最终获取flag。

2.2.1.1 命令执行原理

在各类编程语言中,为了方便程序处理,通常会存在各种执行外部程序的函数,当调用函数执行命令且未对输入做过滤时,通过注入恶意命令,会造成巨大的危害。

下面以PHP中的system()函数举例:

该代码的正常功能是调用操作系统的echo程序,将从d参数接收的字符串作为echo程序的输入,最终system()函数将echo程序执行的结果返回在网页中,其在操作系统执行的命令为“echo for test”,最终在网页显示为“for test”,见图2-2-1。

图2-2-1

当改变d参数为“for test%26%26 whoami”时,网页会多出whoami程序的执行结果,这是因为当前在系统执行的命令为“echo for test&&whoami”,见图2-2-2。

图2-2-2

通常为了解决URL中的歧义表达,会将一些特殊字符进行URL编码,“%26”便是“&”的URL编码。为什么注入“&&”字符就可以造成命令注入呢?类似的还有其他什么字符吗?

在各类编程语言中,“&&”是and语法的表达,一般通过如下格式进行调用:

当两边的表达式都为真时,才会返回真。类似的语法还有or,通常用“||”表示。注意,它们存在惰性,在and语法中,若第一个表达式的结果为假,则第二个表达式不会执行,因为它恒为假。与or语法类比,若第一个表达式为真,则第二个表达式也不会执行,因为它恒为真。

所以,命令注入就是通过注入一些特殊字符,改变原本的执行意图,从而执行攻击者指定的命令。

2.2.1.2 命令执行基础

在测试前,我们需要了解cmd.exe、bash程序在解析命令时的规则,掌握Windows、Linux的异同点。

1.转义字符

系统中的cmd.exe、bash程序执行命令能够解析很多特殊字符,它们的存在让BAT批处理和bash脚本处理工作更加便捷,但是如果想去掉特殊字符的特殊意义,就需要进行转义,所以转义字符即为取消字符的特殊意义。

Windows的转义字符为“^”,Linux的转义字符为“\”,分别见图2-2-3和图2-2-4。可以看到,原本存在特殊意义的“&”被取消意义,从而在终端中输出。

图2-2-3

图2-2-4

2.多条命令执行

在命令注入中通常需要注入多条命令来扩大危害,下面是一些能够构成多条命令执行的字符串:Windows下,&&、||、%0a;Linux下,&&、||、;、$()、``、%0a、%0d。图2-2-5、图2-2-6分别为Windows和Linux下的多条命令执行。图2-2-5中显示了“noexist||echo pwnpwnpwn”,noexist程序本身不存在,所以报错,但是通过注入“||”字符,即使前面报错,还会执行后面的“echo pwnpwnpwn”命令。

在上面的例子中,“&&”和“||”利用条件执进行多条命令执行,“%0a”和“%0d”则是由于换行而可以执行新的命令。另外,在Linux中需要注意,双引号包裹的字符串“$()”或“``”中的内容被当作命令执行,但是单引号包括的字符串就是纯字符串,不会进行任何解析,见图2-2-7。

图2-2-5

图2-2-6

图2-2-7

3.注释符号

与代码注释一样,当合理利用时,命令执行能够使命令后面的其他字符成为注释内容,这样可以降低程序执行的错误。

Windows的注释符号为“::”,在BAT批处理脚本中用得较多;Linux的注释符号为“”,在bash脚本中用得较多。

2.2.1.3 命令执行的基本测试

在面对未知的命令注入时,最好通过各种Fuzz来确认命令注入点和黑名单规则。一般命令的格式如下:

下面以ping-nc 1 www.baidu.com为例构建Fuzz列表。

程序名:ping。

参数:-nc。

参数值:1和www.baidu.com。

程序名与参数值之间的字符串:空格。

整个命令。

参数值有时较为复杂,可能是部分可控的,被双引号、单引号包裹,这时需要注入额外的引号来逃逸。比如,构造Fuzz列表:

再通过将Fuzz列表插入命令点后,通过查看自己服务器的Web日志来观察是否存在漏洞。

2.2.2 命令执行的绕过和技巧

本节介绍在CTF中解答命令执行题目的技巧,命令执行的题目需要把控的因素比较多,如权限的控制、题目接下来的衔接。但是命令执行比较简单、粗暴,经常存在技巧性绕过的考点。

2.2.2.1 缺少空格

在一些代码审计中经常会禁止空格的出现或者会将空格过滤为空,下面将讲解如何突破。例如,对于如下PHP代码:

将cmd参数中的空格过滤为空,导致执行“echo pwnpwn”命令失败,见图2-2-8。

图2-2-8

但是在命令中间隔的字符可以不只是空格(URL编码为“%20”),还可以利用burp suite对%00~%ff区间的字符串进行测试,可以发现还能用其他字符进行绕过,如“%09”“%0b”“%0c”等。

利用burp suite进行Fuzz,见图2-2-9。再次输入“%09”字符,即“echo%09pwnpwnpwn”,就能发现可以绕过空格的限制,见图2-2-10。

图2-2-9

图2-2-10

以上只是其中一种通用去Fuzz未知情况的方式。若将“%0a”“%0d”等不可见字符都禁止,还可以通过字符串截取的方式获取空格。

1.Windows下

例如,命令如下:

其中,“”相当于截取符,表示获取环境变量%ProgramFiles%的值,一般为C:\Program Files。所以,以上命令表示,从第10个开始且获取一个字符串,也就是空格,见图2-2-11。

图2-2-11

2.Linux下

Linux中也有一些绕过空格执行的方式:

bash有效,zsh、dash无效:

读取文件时:

$IFS$9:Linux存在IFS(Internal Field Separator)环境变量,即内部字段分隔符,定义了bash shell的命令间隔字符,一般为空格。注意,当只注入$IFS时,即执行的命令结果为echo$IFSaaa,可以发现解析后的$IFSaaa变量是不存在的,所以需要间隔符来避免,通常使用“$9”。“$9”表示为当前系统Shell进程的第9个参数,通常是一个空字符串,即最终能成功执行的命令为“echo$IFS$9aaa”。

当然,还可以使用“${IFS}”进行注入,或者在某些平台下通过修改IFS变量为逗号来进行注入,即“;IFS=,;”,见图2-2-12。

图2-2-12

2.2.2.2 黑名单关键字

在CTF比赛中,有时会遇上黑名单关键字,如对cat、flag等字符串进行拦截,这时可以用下面的方式绕过。

1.利用变量拼接

其中,a变量为c,b变量为at,最终$a$b是cat。c变量为he,d变量为llo,最终${c}${d}为hello,所以在这里执行的命令是“cat hello”。

2.使用通配符

在通配符中,“?”代表任意一个字符串,“*”则代表任意个字符串。

可以看到,上面通过cat、type命令,结合通配符,实现了对黑名单字符串的绕过。

3.借用已有字符串

若是禁用“<>?”等字符串,则可以借用其他文件中的字符串,利用substr()函数截取出某个具体字符。绕过执行结果见图2-2-13。

图2-2-13

2.2.2.3 执行无回显

在CTF中,我们经常遇到命令执行的结果不在网页上显示的情况,这时可以通过以下几种方式获取执行结果。

在开始前,推荐搭建一个VTest平台https://github.com/opensec-cn/vtest,以便测试。搭建完成后,开始测试,测试代码如下:

1.HTTP通道

假设自己的域名为example.com,下面以获取当前用户权限为例。

在Windows下,目前只能通过相对复杂的命令进行外带(如果未来Windows支持Linux命令,将更加方便数据外带):

通过for命令,将echo hello执行的结果保存在%x变量中,然后拼接到URL后。

以上命令执行后,默认浏览器会被系统调用打开并访问指定的网站,最终可以在平台上面获取echo hello命令的执行结果,见图2-2-14。

图2-2-14

但是其缺陷是调用浏览器后并不会关闭,并且遇上特殊字符、空格时会存在截断问题,所以可以借用powershell进行外带数据。在Powershell 2.0下,执行如下命令:

这里是对echo hello的执行结果进行Base64编码,然后通过Web请求将结果发送出去。

在Linux下,由于存在管道等,因此极其方便数据的传输,通常利用curlwget等程序进行外带数据。例如:

上面便是利用多条命令执行中的“`”和“$()”进行字符串拼接,最终通过curl、wget等命令向外进行请求,从而实现了数据外带,见图2-2-15。

图2-2-15

2.DNS通道

经常我们会以ping来测试DNS外带数据,ping的参数在Windows与Linux下有些不同。如限制ping的个数,在Windows下是“-n”,而在Linux下是“-c”。为了兼容性处理,可以联合使用,即“ping-nc 1 test.example.com”。

在Linux下:

在Windows下相对复杂,主要利用delims命令进行分割处理,最终拼接到域名前缀上,再利用ping程序进行外带。

<1>获取计算机名:

<2>获取用户名:

3.时间盲注

网络不通时,可以通过时间盲注将数据跑出来,主要借用“&&”和“||”的惰性;在Linux下可使用sleep函数,在Windows下则可以选择一些耗时命令,如ping-n 5 127.0.0.1

4.写入文件,二次返回

有时会遇上网络隔离的情况,time型读数据将会极其缓慢,可以考虑将执行命令结果写入到Web目录下,再次通过Web访问文件从而达到回显目的。例如,通过“>”重定向,将结果导出到Web目录http://www.nu1l.com/exec/3.php?cmd=whoami>test下,再次访问导出文件http://www.nu1l.com/exec/test,便可以得到结果,见图2-2-16。

图2-2-16

2.2.3 命令执行真题讲解

CTF比赛中单纯考查命令注入的题目较为少见,一般会将其组合到其他类型的题目,更多的考点偏向技巧性,如黑名单绕过、Linux通配符等,下面介绍一些经典题目。

2.2.3.1 2015 HITCON BabyFirst

PHP代码如下:

题目为每人创建一个沙盒目录,然后通过正则“^\w+$”进行字符串限制,难点在于正则的绕过。因为正则“/^\w+$/”没有开启多行匹配,所以可以通过“\n”(%0a)换行执行其他命令。这样便可以单独执行touch abc命令:

再新建文件1,内容设置为bash反弹shell的内容,其中192.168.0.9为VPS服务器的IP,23333为反弹端口。然后利用Python的pyftpdlib模块搭建一个匿名的FTP服务,见图2-2-17。

图2-2-17

最后使用busybox中的ftp命令获取文件:

将IP转换为十进制,即192.168.0.9的十进制为3232235529,可以通过ping验证最终请求的IP是否正确的。

转换脚本如下:

服务器监听端口情况见图2-2-18。

图2-2-18

最终整个解题过程如下。利用FTP下载反弹Shell脚本:

然后执行Shell脚本:

2.2.3.2 2017 HITCON BabyFirst Revenge

PHP代码如下:

上面的代码中最关键的限制便是命令长度限制,strlen($_GET['cmd'])<=5意味着每次执行的命令长度只能小于等于5。

解决方法是利用文件名按照时间排序,最后使用“ls-t”将其拼接。当然,在拼接的过程中,可以利用“\”接下一行字符串,即将touch程序用“\”分开,见图2-2-19。

最终,整个解题过程如下:写入ls-t>g到_文件;写入payload;执行_,生成g文件;最后执行g文件,从而反弹Shell。利用脚本如下:

图2-2-19

其中生成g文件的内容见图2-2-20。

图2-2-20

图2-2-20

2.2.3.3 2017 HITCON BabyFirst Revenge v2

PHP代码如下:

这就是之前BabyFirst Revenge的升级版本,限制命令长度只能小于等于4。其中,ls>>_不能使用。

在Linux下,“*”的执行效果类似“$(dir*)”,即dir出来的文件名会被当成命令执行。

t的顺序是比s靠后,所以可以找到h并加在t前面,以提高这个文件名最后排序的优先级。所以,在“*”执行时,其实执行的命令为:

最终,v文件的内容是:

接下来写入一个rev文件,然后使用“*v”命令,因为只有rev、v两个带v的文件,所以其执行的命令是“rev v”,再将逆转的v文件内容放入x文件。

最终,x文件的内容是:

后面写payload的方式与v1解题一样。