代码审计的角度分析RCE
一、代码审计的角度分析sql注入
- 这里首先做一个解释,由其他漏洞产生的外部代码(非预设代码)执行,不是我这篇文章所讲的RCE。比如反序列化、远程文件包含、webshell上传等等
1.代码审计的角度分析sql注入
-
因为需求设计,程序代码里面有时候也会把用户的输入作为代码的一部分进行执行,也就造成了远程代码执行漏洞。
-
就是一段当前语言代码的字符串被动态执行或者其他语言的代码被放入沙箱执行了
-
举例
-
PHP
-
php中可以进行远程代码执行的函数有很多,也常常被一些webshell来做免杀利用。如果我们的输入可以走到以下的函数作为参数,那么就有可能有远程代码执行
eval() //把字符串作为PHP代码执行 assert() //检查一个断言是否为 FALSE,可用来执行代码 preg_replace() //执行一个正则表达式的搜索和替换 call_user_func() //把第一个参数作为回调函数调用 call_user_func_array() //调用回调函数,并把一个数组参数作为回调函数的参数 array_map() //为数组的每个元素应用回调函数 $a($b) //动态函数
-
-
python
-
python中能进行代码执行的函数也不多。如果我们的输入可以走到以下的函数作为参数,那么就有可能有远程代码执行
exec(string) # Python代码的动态执行 eval(string) # 返回表达式或代码对象的值 execfile(string) # 从一个文件中读取和执行Python脚本
-
-
java
- java中能够直接执行代码的函数基本没有,都是调用反序列化来动态执行字符串,所以这里暂时没有。
-
2.Remote Command Execte 远程命令执行
-
一般出现这种漏洞,是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口。比如我们常⻅的路由器、防火墙、入侵检测(硬件设备、工业交付)等设备的web管理界面上。
-
仅当Web应用程序代码包含操作系统调用(外壳程序、shell)并且调用中使用了用户输入时,才可能进行OS命令注入攻击。它们不是特定于语言的,命令注入漏洞可能会出现在所有让你调用系统外壳命令的语言中:C,Java,PHP,Perl,Ruby,Python等。
-
就是一段输入的字符串被引入了执行外部命令的函数,并且没被过滤
-
举例
-
php
exec //执行一个外部程序 passthru //执行外部程序并且显示原始输出 proc_open //执行一个命令,并且打开用来输入/输出的文件指针。 shell_exec //通过 shell 执行命令并将完整的输出以字符串的方式返回 system //执行外部程序,并且显示输出
-
python
os.system() #执行系统指令 os.popen() #popen()方法用于从一个命令打开一个管道 subprocess.call #执行由参数提供的命令
-
java
Runtime.getRuntime().exec() //调用外部程序 ProcessBuilder() //创建操作系统进程
-
二、Linux进程的创建
- 关于进程的知识可以看这里
1.fd
-
linux下的文件描述符(file descriptor)是linux下一个重要的进程概念(本质上是一个索引)。
-
我们知道在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。
-
在操作这些所谓的文件的时候,我们每操作一次就找一次名字,这会耗费大量的时间和效率。所以Linux中规定每一个文件对应一个索引,这样要操作文件的时候,我们直接找到索引就可以对其进行操作了。
-
文件描述符(file descriptor)就是内核为了高效管理这些已经被打开的文件所创建的索引
$$ --> linux下当前进程的pid /proc --> linux伪文件系统 -->进程相关的信息挂载在这里
2.fork和exec的区别,具体的可以看这里
3.linux进程的创建大致流程
-
当执行命令时
bash -c whoami
其实在内核态执行了以下动作
bash(pid:52350) --> sys_fork --> bash(pid:52796) --> sys_execve --> /bin/whoami(pid:52796)
-
通过strace命令跟踪系统调用
strace -tt -f -e trace=process python3 glc.py
glc.py文件内容如下
import os if __name__ == '__main__': name = '123";ping baidu.com -c 100;echo "456' cmd = 'echo "HELLO ' + name + '"' os.system(cmd)
执行结果
19:16:30.096006 execve("/usr/bin/python3", ["python3", "glc.py"], 0x7fff4d9f23c8 /* 53 vars */) = 0 19:16:30.102415 arch_prctl(ARCH_SET_FS, 0x7f24bef43740) = 0 19:16:30.296789 clone(strace: Process 7078 attached child_stack=NULL, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7fff7093d8c0) = 7078 [pid 7077] 19:16:30.297246 wait4(7078, <unfinished ...> [pid 7078] 19:16:30.297548 execve("/bin/sh", ["sh", "-c", "echo \"HELLO 123\";ping baidu.com "...], 0x7fff7093e130 /* 53 vars */) = 0 [pid 7078] 19:16:30.301139 arch_prctl(ARCH_SET_FS, 0x7fa23ea89740) = 0 HELLO 123 [pid 7078] 19:16:30.312523 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fa23ea89a10) = 7079 strace: Process 7079 attached [pid 7078] 19:16:30.313402 wait4(-1, <unfinished ...> [pid 7079] 19:16:30.314059 execve("/usr/bin/ping", ["ping", "baidu.com", "-c", "100"], 0xfafe00 /* 52 vars */) = 0 [pid 7079] 19:16:30.518025 arch_prctl(ARCH_SET_FS, 0x7fcac708f740) = 0 [pid 7078] 19:16:33.347373 <... wait4 resumed>0x7ffc4e5aa630, 0, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) [pid 7077] 19:16:33.347435 <... wait4 resumed>0x7fff7093d8f0, 0, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) [pid 7079] 19:16:33.347549 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7078] 19:16:33.347589 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7077] 19:16:33.347603 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7078] 19:16:33.347674 wait4(-1, <unfinished ...> [pid 7077] 19:16:33.347698 wait4(7078, <unfinished ...> [pid 7078] 19:16:35.367728 <... wait4 resumed>0x7ffc4e5aa630, 0, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) [pid 7079] 19:16:35.367769 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7077] 19:16:35.367787 <... wait4 resumed>0x7fff7093d8f0, 0, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) [pid 7077] 19:16:35.367827 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7078] 19:16:35.367872 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7077] 19:16:35.367897 wait4(7078, <unfinished ...> [pid 7078] 19:16:35.367945 wait4(-1, PING baidu.com (110.242.68.66) 56(84) bytes of data. <unfinished ...> [pid 7079] 19:16:35.764792 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7078] 19:16:35.765288 <... wait4 resumed>0x7ffc4e5aa630, 0, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) [pid 7077] 19:16:35.765373 <... wait4 resumed>0x7fff7093d8f0, 0, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) [pid 7078] 19:16:35.766578 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7077] 19:16:35.766646 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7078] 19:16:35.766791 wait4(-1, <unfinished ...> [pid 7077] 19:16:35.766859 wait4(7078, 64 bytes from baidu.com (110.242.68.66): icmp_seq=1 ttl=128 time=33.2 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=2 ttl=128 time=37.4 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=3 ttl=128 time=171 ms <unfinished ...> [pid 7079] 19:16:42.264233 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7078] 19:16:42.267415 <... wait4 resumed>0x7ffc4e5aa630, 0, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) [pid 7077] 19:16:42.267480 <... wait4 resumed>0x7fff7093d8f0, 0, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) [pid 7078] 19:16:42.267542 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7077] 19:16:42.267591 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} --- [pid 7078] 19:16:42.267650 wait4(-1, <unfinished ...> [pid 7077] 19:16:42.267674 wait4(7078, 64 bytes from baidu.com (110.242.68.66): icmp_seq=4 ttl=128 time=36.2 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=5 ttl=128 time=33.5 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=6 ttl=128 time=34.0 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=7 ttl=128 time=36.8 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=8 ttl=128 time=32.5 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=9 ttl=128 time=33.1 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=10 ttl=128 time=32.9 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=11 ttl=128 time=32.5 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=12 ttl=128 time=32.5 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=13 ttl=128 time=33.4 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=14 ttl=128 time=32.7 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=15 ttl=128 time=33.3 ms 64 bytes from baidu.com (110.242.68.66): icmp_seq=16 ttl=128 time=34.2 ms strace: Process 7077 detached <detached ...> strace: Process 7078 detached strace: Process 7079 detached --- baidu.com ping statistics --- 26 packets transmitted, 26 received, 0% packet loss, time 29386ms rtt min/avg/max/mdev = 32.511/39.050/171.088/26.444 ms 456
三、不同函数的RCE
-
c/php/python下的system()/popen()函数
<?php $name = $_GET['name']; $cmd = 'echo "Hello '.$name.'"'; var_dump("system", system($cmd)); //sh -c echo '</br>'; var_dump("exec",exec($cmd)); //sh -c echo '</br>'; var_dump("shell_exec",shell_exec($cmd)); //sh -c echo '</br>'; var_dump("popen");$x = popen($cmd, 'r');var_dump($x);var_dump(fread($x,1024)); //sh -c echo '</br>'; $a = array(); var_dump("proc_open");$x = proc_open($cmd, $a,$b); //sh -c ?>
php中大多数执行外部命令的函数,其实都是调用sh -c 去执行
system($input$) 执行 sh -c '$input' 可以转化为 bash(pid:1) --> sys_fork --> bash(pid:2) --> sys_execve --> /bin/whoami(pid:2)
-
python的subprocess.call函数
import os if __name__ == '__main__': name = '123";ping baidu.com -c 100;echo "456' cmd = 'echo "HELLO ' + name + '"' os.system(cmd)
仔细观察下面两段代码的异同
import os import subprocess if __name__ == '__main__': name = '123";ping baidu.com -c 100;echo "456' cmd = 'echo "HELLO ' + name + '"' subprocess.call(cmd, shell=True)
import os import subprocess if __name__ == '__main__': name = '123";ping baidu.com -c 100;echo "456' cmd = ['echo', '"HELLO ' + name + '"'] subprocess.call(cmd, shell=False)
- shell=True参数会让subprocess.call接受字符串类型的变量作为命令,并调用shell去执行这个字符串,当shell=False是subprocess.call只接受数组变量作为命令,并将数组的第一个元素作为命令,剩下的全部作为该命令的参数
-
java的Runtime.getRuntime().exec和ProcessBuilder()
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class main { public static void main(String[] args) { try { String name = "123';ping baidu.com -c 3;echo '456"; String cmd = "echo 'HELLO " + name + "'"; Process pro = Runtime.getRuntime().exec(cmd); InputStream in = null; in = pro.getInputStream(); BufferedReader read = new BufferedReader(new InputStreamReader(in)); String result = read.readLine(); System.out.println("INFO:"+result); } catch (IOException e) { throw new RuntimeException(e); } } }
四、总结
1.system类
- 如果是执行system函数,或者类似system函数,他们都是直接走的fork–>execve流程(调用外部sh -c),这种情况下,我们的输入被拼接加入到作为bash -c的参数,而bash -c是支持shell语法的,所以我们能够很轻易的进行拼接、绕过,这种也是最常⻅的RCE攻击,简单的一笔。
2.execve类
- 比如Runtime.getRuntime().exec()和subprocess.call(cmd, shell=False)这两者,走的流程是直接execve,在这种情况下,我们的输入只能作为固定进程的参数,那么我们就没办法用shell语法了,与任何拼接都没有关系了。