本系列文章主旨在于介绍一些漏洞类型产生的基本原理,探索最基础的解决问题的措施,不排除有些语言或者系统提供的安全的API可以更好地更直接地解决问题,也不排除可以严格地输入验证来解决。
OS命令行注入主要是由于在执行命令时,由于使用不可信的数据来源来组装命令或者直接作为命令来执行造成的。如果程序运行时,没有遵守最少权限原则,由于攻击者可以控制执行的命令,所以,一旦被攻击者利用,就可以控制整个服务器。诸多语言中都有执行操作系统的命令的API或者类,再调用这些接口的时候,需要特别注意。 不同语言的执行操作系统的API或者类如下:
语言 | 类/模块 | API |
C | stdlib.h | int execl(cONst char *path, const char *arg, ...); |
int execlp(const char *file, const char *arg, ...) | ||
int execv(const char *path, char *const argv[]) | ||
int execvp(const char *file, char *const argv[]) | ||
int execve(const char *path, char *const argv[], char *const envp[]) | ||
PHP | 无 | system ( string $command [, int &$return_var ] ) : string |
exec ( string $command [, array &$output [, int &$return_var ]] ) : string | ||
passthru ( string $command [, int &$return_var ] ) : void | ||
popen ( string $command , string $mode ) : resource | ||
proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd = NULL [, array $env = NULL [, array $other_options = NULL ]]] ) : resource | ||
ftp_raw ( resource $ftp_stream , string $command ) : array | ||
ftp_exec ( resource $ftp_stream , string $command ) : bool | ||
shell_exec ( string $cmd ) : string | ||
imap_open ( string $mailbox , string $username , string $password [, int $options = 0 [, int $n_retries = 0 [, array $params = array() ]]] ) : resource | ||
Java | java.lang.Runtime | Process exec(String command) |
Process exec(String[] cmdarray) | ||
Process exec(String[] cmdarray, String[] envp) | ||
Process exec(String[] cmdarray, String[] envp, File dir) | ||
Process exec(String command, String[] envp) | ||
Process exec(String command, String[] envp, File dir) | ||
java.lang.ProcessBuilder | ProcessBuilder command(List<String> command) | |
ProcessBuilder command(String... command) | ||
ProcessBuilder(List<String> command) | ||
ProcessBuilder(String... command) | ||
Python | os | system(command) |
popen | ||
commands | getstatusoutput | |
subprocess | popen | |
C# | System.Diagnostics.Process | public static System.Diagnostics.Process Start (string fileName, string arguments, string userName, System.Security.SecureString password, string domain); |
public static System.Diagnostics.Process Start (string fileName, string userName, System.Security.SecureString password, string domain); | ||
public static System.Diagnostics.Process Start (string fileName, string arguments); | ||
public static System.Diagnostics.Process Start (string fileName); | ||
System.Diagnostics.ProcessStartInfo | public ProcessStartInfo (string fileName); | |
public ProcessStartInfo (string fileName, string arguments); | ||
public void set_FileName(string fileName) | ||
public void set_Arguments(string arguments) | ||
System.CodeDom.Compiler | public static void ExecWait (string cmd, System.CodeDom.Compiler.TempFileCollection tempFiles); | |
public static int ExecWaitWithCapture (string cmd, System.CodeDom.Compiler.TempFileCollection tempFiles, ref string outputName, ref string errorName); | ||
public static int ExecWaitWithCapture (IntPtr userToken, string cmd, System.CodeDom.Compiler.TempFileCollection tempFiles, ref string outputName, ref string errorName); | ||
public static int ExecWaitWithCapture (string cmd, string currentDir, System.CodeDom.Compiler.TempFileCollection tempFiles, ref string outputName, ref string errorName); | ||
public static int ExecWaitWithCapture (IntPtr userToken, string cmd, string currentDir, System.CodeDom.Compiler.TempFileCollection tempFiles, ref string outputName, ref string errorName); | ||
Objective C | 无 | 可以直接调用C的API执行系统命令 |
当调用以上API时,就需要小心了。
OS命令行注入的PHP代码示例,例如下:
$command = $_POST["cmd"];
system($command);
如果用户输入的内容是:ls -l /curdir/;rm -rf /......../somedir, 由于分号“;”在Linux和UNIX系统下是命令的分隔符,系统会首先执行ls命令,然后执行rm命令。 由此可见,接受不可信来源的数据作为执行命令的全部或者一部分,都是十分危险的。
导致命令注入的最根本的原因就是由于一些分隔符或者逻辑操作符的特殊作用,导致通过这些字符可以执行额外的命令,主要的命令行操作符和逻辑操作符描述如下:
分 隔 符 | 描 述 |
; | 如果每个命令都被一个分号(;)所分隔,那么命令会连续地执行下去 |
&& | 执行错误检查命令,如果其左侧的命令不返回预期的结果,其右侧的命令就不会执行 |
& | 不执行错误检查和运行所有命令 |
|| | 若遇到可以成功执行的命令,那么命令停止执行,即使后面还有正确的命令。假如命令一开始就执行失败,那么就会执行 || 后的下一个命令,直到遇到可以成功执行的命令为止,假如所有的都失败,则所有这些失败的命令都会被尝试执行一次 |
| | 即使遇到可以成功执行的命令,命令也会继续执行下去,并且会显示最后一个命令的执行结果 |
如果接受的输入中含有这些字符将会造成命令行注入攻击,关于命令行注入的预防,根据操作系统的不同,预防方法也有区别:
Linux系统:
使用的方法是先把命令用户单引号或者双引号括起来,例如:ls -l /curdir/,就变成'ls -l /curdir/' 或者“ls -l /curdir/”,对于其中的特殊字符,再试用反斜杠\进行转义处理。
字 符 | 说 明 |
'(单引号) | 使用单引号进行的转义称为硬转义,其内部所有的Shell元字符、通配符都会被关掉。注意,硬转义中不允许出现'(单引号),如果出现,需要先使用反斜杠转义 |
"(双引号) | 使用双引号进行的转义称为软转义,其内部只允许出现特定的Shell元字符:$用于参数代换、` 用于命令代替。一些特殊的字符仍然可以得到解析,如$var会被变量的值代替、`cmd`命令会得到执行。因此使用双引号转义是不安全的。 |
\(反斜杠) | 去除其后紧跟的元字符或通配符的特殊意义,例如:" 就需要转义为\" 。 |
Windows:
Windows的特殊字符集与Linux有区别,主要的特殊字符如下:
特殊字符 | 转义方式 | 说明 |
| ^ & | 使用^+字符 | ^|代表|,^&代表&,^^代表^ |
" | 使用""代替 | Hello"World=> "Hello""World" |
对于使用两个双引号代替一个双引号的处理方式,可以考虑一下,如果一个字符串中含有连个连续的双引号该怎么处理呢? 例如:Execute""Comand,处理之后的结果应该是什么?
如果有不妥之处,希望可以留言指出。谢谢!