代码审计-RCE审计

GinTvT 发布于 2024-11-06 103 次阅读 安全学习 预计阅读时间: 7 分钟


Remote Code Execute 远程代码执行

由其他漏洞产⽣的外部代码执⾏

名词解释:⼀段当前语⾔代码的字符串被动态执⾏、或者其他语⾔的代码被放⼊沙箱执⾏了,⽐如反序列化、远程⽂件包含、webshell上传、SSTI等等

PHP代码执行函数

eval() //把字符串作为PHP代码执⾏
assert() //检查⼀个断⾔是否为 FALSE,可⽤来执⾏代码
preg_replace() //执⾏⼀个正则表达式的搜索和替换
call_user_func() //把第⼀个参数作为回调函数调⽤
call_user_func_array() //调⽤回调函数,并把⼀个数组参数作为回调函数的参数
array_map() //为数组的每个元素应⽤回调函数
$a($b) //动态函数

举例

<?php
$a='a'.'ssert';
$a($_POST["a"])
?>

Python代码执行函数

exec(string) # Python代码的动态执⾏
eval(string) # 返回表达式或代码对象的值
execfile(string) # 从⼀个⽂件中读取和执⾏Python脚本

Java

Java基本没有直接能够执行代码的函数,通过反序列化来动态执行字符串

Remote Command Execute 远程命令执⾏

输入的字符串被引入了执行外部命令的函数,且没有过滤

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 前⼀个命令的输出作为后⼀个命令的输⼊

fd

file descriptor,文件描述符,本质是一个索引

$$ --> linux下当前进程的pid

/proc --> linux伪⽂件系统 ---》进程相关的信息挂载在这⾥

三个特殊的fd

0 --> 标准输入 stin

1 --> 标准输出 stout

2 --> 标准错误 sterr

反弹shell

Linux进程创建

Linux区分内核态和用户态,用户态进行的所有动作,都是通过调用system call(系统调用)向内核发起请求,最终在内核态执行返回

Linux进程创建流程(使用strace跟进调试)

安装strace

yum -y install strace

使用strace

strace -tt -f -e trace=process python3 test.py

-tt:表示时间戳

-f:跟踪由当前进程创建的所有子进程

-e trace=process:只跟踪与进程相关的系统调用,包括 fork、vfork、clone、execve、exit、wait等系统调用

execve:是一个system call,用于执行一个程序,用新的程序替换当前进程的内存空间,包括代码、数据和堆栈

/usr/local/bin/python3//执行程序的路径,这里指向python解释器
["python3", "test.py"]//命令行参数
0x7ffdda236ea8 /* 24 vars */ = 0 //表示环境变量,返回值0表示调用成功
ARCH_SET_FS:设置FS寄存器的值//
0x7f715b9f0740:设置的值
返回0表示成功

child_stack=NULL:子进程的堆栈,设置为NULL表示内核自动为子进程分配堆栈

flags=CLONE_PARENT_SETTID|SIGCHLD:创建新进程时,内核将新进程的TID写入parent_tidptr指向的内存位置,父进程在子进程创建后立即知道子进程的TID

SIGCHLD:这个标志表示当子进程终止时,内核会向父进程发送 SIGCHLD 信号,通知父进程子进程已经终止

parent_tidptr=0x7ffc8eb40f70)= 10793

  • parent_tidptr 参数是一个指向父进程 TID 的指针。内核会将新创建子进程的 TID 写入这个指针所指向的内存位置,0x7ffc8eb40f70是一个内存地址,也是一个指针,指向父进程的TID,clone系统调用的返回值10793表示新创建子进程的PID
sh -c '/usr/bin/echo $input$'
/usr/bin/echo $input$

两者的差别在于第一个以字符输出数据,执行whoami的效果为whoami

第二个以字符串输出,输出的为input

以下面为例继续分析

该代码是直接执行的命令,对于分隔符并没有解析,在其中并没有调用shell

PHP产生的原因,是代码的执行过程中,通过了shell去执行,在命令拼接的过程中很容易出现RCE

<?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
?>

python

如果在

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);
 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);
 }
 }
}

Process pro = Runtime.getRuntime().exec(cmd);

在这里中,并没有直接调用shell去执行123';ping baidu.com -c 3;echo '456

只是单纯的执行了命令,并没有通过shell去执行,是完全没有bash -c

所以无论在Windows和Linux下,执行的结果都是,两者的区别在前文有过举例

'HELLO 123 ; ping baidu.com ; echo 456'

(小tips:在Windows下,;并不会被当作命令分隔符进行执行,在Linux下会被当作分隔符进行执行,这里是收集的资料在学习中产生的误解,特此进行一个记录与批示,可自行执行echo 123;echo 456观察两者的区别)

小总结

system类

由fork-->走到execve,会走bash -c

execve类

走execve,直接执行命令,输入只做成固定进程的参数,不会走bash -c,类似于

subprocess.call(cmd, shell=False)

Runtime.getRuntime().exec()

不能说绝对不能被RCE,取决于这个参数能不能被注入

Linux参数

https://gtfobins.github.io

Windows参数

https://lolbas-project.github.io
此作者没有提供个人介绍。
最后更新于 2025-04-23