PHP备赛CTF知识点②--清风

PHP备赛CTF知识点②–清风

7.PHP代码执行漏洞

代码执行漏洞(仅个人理解):利用执行漏洞函数将所接受的参数作为代码去执行,并且拼接内容可被访问者控制,也就是把传入的参数给拼接进去了,造成了额外的代码执行,也就造成了代码执行漏洞。

命令执行漏洞,也称为命令注入漏洞,是指攻击者通过在某个程序中注入恶意命令,从而获得执行权限。这种漏洞的原理是,当程序需要执行一些系统命令时,如果没有对用户输入的数据进行充分的过滤和限制,那么攻击者就可以通过输入一些特殊的字符,将自己的命令注入到原来的命令中,从而掌控程序。

命令command注入

system	        执行外部程序,并且显示输出
exec/shell_exec	通过 shell 环境执行命令,并且将完整的 输出以字符串的方式返回
Pcntl_exec	    在当前程序空间执行指定程序
passthru	    执行外部程序并且显示原始输出
popen	        打开进程文件指针
Proc_open	    执行一个命令,并且打开用来输入/输出 的文件指针

代码code注入

函数	作用
eval()	            把字符串 code 作为PHP代码执行
assert()	        检查一个断言是否为false
preg_replace()	    执行一个正则表达式的搜索和替换
create_function()	创建一个匿名函数并且返回函数名称
call_user_func()/call_user_func_array()	把第一个参数作为回调函数调用
usort()/uasort()	使用用户自定义的比较函数对数组中的值 进行排

8.反序列化

1.序列化

序列化是将对象转换为字符串以便存储传输的一种方式。而反序列化恰好就是序列化的逆过程,反序列化会将字符串转换为对象供程序使用。在PHP中序列化和反序列化对应的函数分别为serialize()和unserialize()。

------个人理解:将对象转化为数组或者字符串形式

例如:

array(
'name' => 'Tom',       
'age' => 20,
'city' => 'New York',
);
============转化后:
a:3:{s:4:"name";s:3:"Tom";s:3:"age";i:20;s:4:"city";s:8:"New York";}
2.反序列化

当程序在进行反序列化时,会自动调用一些函数,例如__wakeup(),__destruct()等魔法函数,但是如果传入函数的参数可以被用户控制的话,用户可以输入一些恶意代码到函数中,从而导致反序列化漏洞。

------个人理解:将字符串或数组转化为对象格式

例如:将上面例子反过来看

3.序列化函数与反序列化函数

------序列化函数serialize()

------反序列化函数unserialize()

当我们在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,用于保存对象的值方便之后的传递与使用。

注意:①序列化与成员方法无关,只与成员属性有关,及魔法函数的调用有关。②反序列化后会创建对象。

测试代码

<?php
 class Stu{
    public $name = 'aa';
    public $age = 18;
    public function demo(){
        echo "你好啊";
    }
  }
$stu = new Stu(); //创建对象即实例化对象
$stus = serialize($stu);//进行序列化
echo $stus;
echo "<br>".'==============='."<br>";
var_dump(unserialize($stus));//进行反序列化
?>

输出结果

在这里插入图片描述

4.魔法函数
(1)__construct()

__construct():析构函数,实例化一个对象时,会自动执行__construct()魔术方法

(2)__destruct()

__destruct():析构函数,在对象的所有引用被删除或者当对象被销毁时执行。

注意:__destruct()是可以在反序列化之后触发

__destruct()代码演示

<meta charset='UTf-8'>
<?php
 class Stu{
    public $name = 'aa';
    public $age = 18;
    public function demo(){
        echo "你好啊";
    }
    public function __construct(){
      echo "<br>触发construct成功<br>";
    }
    public function __destruct(){
    echo "<br>触发destruct成功<br>";
    }
}
$stu = new Stu(); //实例化对象
$stus = serialize($stu);//进行序列化
echo $stus;//打印序列化
echo "<br>".'==============='."<br>";
var_dump(unserialize($stus));//进行反序列化
echo "<br>".'==============='."<br>";
?>

__destruct()输出结果:
在这里插入图片描述

__destruct()简单解释:

1.首先实例化对象,调用__construct()
2.打印序列化
3.打印反序列化
4.打印反序列化,调用__destruct()
5.所有代码结束,对象被销毁,再次调用__destruct()
==================================================
__destruct()一共触发了两次,第一次触发原因:反序列化所生成的对象在最后释放时触发了__destruct()。第二次触发原因:实例化一个对象后程序结束,对象最终被销毁,从而触发__destruct()。
(3)__sleep()

序列化serialize()函数会先检查类中是否存在__sleep(),如果存在,则会先被调用(删除不必要的属性,只返回需要系列化的成员属性),然后在执行序列化操作

注意:①先触发__sleep()魔法函数,然后再执行—序列化操作。

②__sleep()魔法函数需要配置返回值,序列化时会删除未配置的属性,但是不影响此后的反序列化。(如果未配置返回值,则序列化输出N;和反序列化输出NULL)

 public function __sleep(){
     echo "触发__sleep成功";
    return array('成员属性1','成员属性2');
 }

__sleep()代码演示

<meta charset='UTf-8'>
<?php
 class Stu{
    public $name = 'aa';
    public $age = 18;
    public function demo(){
        echo "你好啊";
    }
    public function __construct(){
      echo "<br>触发construct成功<br>";
    }
    public function __sleep(){
      echo "<br>触发sleep成功<br>";
       return array('name');
    }
}
$stu = new Stu(" "); //实例化对象
$stus = serialize($stu);//进行序列化
echo "<br>".'==============='."<br>";
echo $stus; //打印序列化
echo "<br>".'==============='."<br>";
var_dump(unserialize($stus));//进行反序列化
echo "<br>".'==============='."<br>";
?>

__sleep()输出结果
在这里插入图片描述

(4)__wakeup()

反序列化unserialize()函数会先检查类中是否存在__wakeup(),如果存在,则会先被调用,然后再执行反序列化操作

注意:先触发__wakeup()魔术方法,然后再执行—反序列化操作

__wakeup()代码演示

<meta charset='UTf-8'>
<?php
 class Stu{
    public $name = 'aa';
    public $age = 18;
    public function demo(){
        echo "你好啊";
    }
    public function __construct(){
      echo "<br>触发construct成功<br>";
    }
    public function __wakeup(){
      echo "<br>触发wakeup成功<br>";
    }
}
$stu = new Stu(" "); //实例化对象
$stus = serialize($stu);//进行序列化
echo "<br>".'==============='."<br>";
echo $stus; //打印序列化
echo "<br>".'==============='."<br>";
var_dump(unserialize($stus));//进行反序列化
echo "<br>".'==============='."<br>";
?>

__wakeup()输出结果

在这里插入图片描述

(5)__toString()

表达方式错误,即对象当成字符串使用,导致__toString()触发

__toString()代码演示

<meta charset='UTf-8'>
<?php
 class Stu{
    public $name = 'aa';
    public $age = 18;
    public function demo(){
        echo "你好啊";
    }
    public function __construct(){
      echo "<br>触发construct成功<br>";
    }
    public function __toString(){
      echo "<br>触发toString成功<br>";
    }
}
$stu = new Stu(" "); //实例化对象
var_dump($stu);//打印对象
echo $stu ;//将对象当作字符串来执行,引发报错,触发toString
//引发报错后,后面代码将不再执行
$stus = serialize($stu);//进行序列化
echo "<br>".'==============='."<br>";
echo $stus; //打印序列化
echo "<br>".'==============='."<br>";
var_dump(unserialize($stus));//进行反序列化
echo "<br>".'==============='."<br>";
?>

__toString()输出结果

在这里插入图片描述

(6)__call()

当调用不存在的方法时触发。默认有两个参数, n a m e 表示所调用 ∗ ∗ 不存在的方法的名称 ∗ ∗ , name表示所调用**不存在的方法的名称**, name表示所调用不存在的方法的名称sex表示所调用不存在方法的参数

注意:①__call()在输出不存在方法的参数时,需保证是数组输出且保证是数组第一位开始即:echo “ n a m e , name, name,sex[0]”;

如果是echo “ n a m e , name, name,sex[1]”;则无法输出不存在方法的参数------感觉这地方如果出题目的话,会容易被人遗忘。

__call( n a m e , name, name,sex)代码演示

<meta charset='UTF-8'>
<?php
class system{
  public function __call($name,$sex){
    echo "$name,$sex[0]";
    echo "<br>";
    echo "<br>触发call成功<br>";
  }
}
$System =new system();
$System->test('hello world');
?>

__call( n a m e , name, name,sex)输出结果

在这里插入图片描述

(7)__callStatic()

当静态调用或者调用成员常量是使用的方法不存在时触发。默认有两个参数, n a m e 表示所调用不存在方法的名称, name表示所调用不存在方法的名称, name表示所调用不存在方法的名称,age表示所调用不存在方法的参数

__callStatic()代码演示

<meta charset='UTF-8'>
<?php
class system{
  public static function __callStatic($name,$sex){
    echo "$name,$sex[0]";
    echo "<br>";
    echo "<br>触发callStatic成功<br>";
  }
}
system::test('hello');
?>

__callStatic()输出结果

在这里插入图片描述

(8)__get()

当调用的成员属性不存在时 或 不可访问时触发。默认有一个参数,$name表示不存在的成员属性

__get()代码演示

<meta charset='UTF-8'>
<?php
class Stu{
  public function __get($name){
    echo "$name";
    echo "<br>触发callStatic成功<br>";
  }
}
$stu =new Stu();
$stu -> password;
?>

__get()输出结果

在这里插入图片描述

(9)__set()

当给不存在的成员属性赋值时触发。默认有两个参数, n a m e 表示不存在的成员属性名称, name表示不存在的成员属性名称, name表示不存在的成员属性名称,age表示不存在的成员属性所赋的值

注意:①在第二个参数$age如果用数组接收时,仅仅输出一个字符,这与前面calll()和callStatic()默认两个参数的有所不同,

__set()代码演示

<meta charset='UTF-8'>
<?php
class Stu{
  public function __set($name,$age){
    echo "$name,$age[0]";
    echo "<br>触发set成功<br>";
  }
}
$stu =new Stu();
$stu -> password = '123456';
?>

__set()输出结果

在这里插入图片描述

(10)__isset()

当对不可访问的属性或者不存在的属性使用**isset()或者empty()**时触发。默认有一个参数,$name表示不可访问或者不存在的成员属性名称

__isset()代码演示

<meta charset='UTF-8'>
<?php
class Stu{
  private $username;
  public function __isset($name){
    echo "$name";
    echo "<br>触发isset成功<br>";
  }
}
$stu =new Stu();
isset($stu -> username);
echo "<br>===============================<br>";
empty($stu -> root);
?>

__isset()输出结果

在这里插入图片描述

(11)__unset()

当对不可访问的属性或者不存在的属性使用**unset()**时触发。默认一个参数,$name表示不可访问或者不存在的成员属性的名称

__unset()代码演示

<meta charset='UTF-8'>
<?php
class Stu{
  private $username;
  public function __unset($name){
    echo "$name";
    echo "<br>触发unset成功<br>";
  }
}
$stu =new Stu();
unset($stu -> username);
?>

__unset()结果输出

在这里插入图片描述

(12)__invoke()

格式表达式错误,即把对象当成函数使用,导致__invoke()触发

注意:__toString()是把对象当成字符串使用,这里invoke是对象当成函数使用

__invoke()代码演示

<meta charset='UTF-8'>
<?php
class Stu{
  private $username;
  public function __invoke(){
    echo "<br>触发invoke成功<br>";
  }
}
$stu =new Stu();
echo $stu();//看清楚这里是对象当成函数使用;
?>

__invoke()结果输出

在这里插入图片描述

(13)__set_state()

当使用var_export()导出一个对象时,如果__set_state()存在则会尝试调用该对象的__set_state()方法,并且接受一个数组作为参数,通常用来从数组中重建对象状态。

注意:①__set_state()魔术方法在 PHP 7.4 中引入。②必须是静态方法。

__set_state()代码演示(可能有问题,为什么没有输出echo语句)

<meta charset='UTF-8'>
<?php
class Stu{
  public $username;
  //public $name;
  public static function __set_state($password){
    echo "<br>触发set_state成功<br>";
    $data = new Stu();
    foreach($password as $kay => $value){
      $dara -> $kay = $value;
    }
    return $data;
  }
}
$stu = new Stu();
$stu -> username = 'admin';
var_export($stu);
?>

__set_state()结果输出

在这里插入图片描述

(14)__clone()

当使用clone()拷贝完成一个对象后,新对象会自动调用__clone()

__clone()代码演示

<meta charset='UTF-8'>
<?php
class Stu{
  public $username;
  public function __clone(){
    echo "<br>触发clone成功<br>";
  }
}
$stu = new Stu();
$a = clone($stu);//注意这里是拷贝对象
?>

__clone()结果输出

在这里插入图片描述

(15)__autoload()

当试图使用尚未被定义的类时,会自动调用__autoload()去查找对应类的文件,如果文件存在则加载,如果不存在则程序终止

注意:①当php版本大于7.2时,__autoload()被废弃。

__autoload代码演示(头晕了)

<meta charset='UTF-8'>
<?php
class Stu{
  public $username;
  public $name;
     function __autoload($password){
   echo "<br>触发autoload成功1<br>";
   require $name.'.php';
  }
  public function info(){
    echo '<br>触发autoload成功2<br>';
  }
  public function port(){
    echo '<br>触发autoload成功3<br>';
  }
}
$stu = new Stu();
$stu -> info();
$stu -> port();
?>

__autoload()结果输出

在这里插入图片描述

(16)__debuginfo()

------PHP5.6.0及以上

当使用var_dump()或者print_r()来调试输出时,将调用__dubuginfo()方法来获取该对象的调试信息,这里也是配置返回值。没用定义该方法时,将默认输出对象的公共属性及其值。

注意:①__debugInfo() 方法在 PHP 5.6.0 版本中被引入,因此确保你的 PHP 环境版本至少是 5.6.0 或更高。②这里debuginfo()和sleep()函数相同需要配置返回参数,返回该对象的属性,即键值对

__debuginfo()代码演示

<meta charset='UTF-8'>
<?php
class Stu{
  public $username;
  public $name;
  public function __debuginfo(){
    echo "<br>触发debuginfo成功<br>";
    return ['用户名' => $this->username] ;
  }
}
$stu = new Stu();
$stu -> username = 'admin';
var_dump($stu);
?>

__debuginfo()结果输出

在这里插入图片描述

(17)_show()

魔法函数代码拼凑

请注意魔术方法所使用的版本

<meta charset='UTf-8'>
<?php
 class Stu{
    public $name = 'aa';
    public $age = 18;
    public function demo(){
        echo "你好啊";
    }
    public function __construct(){
      echo "<br>触发construct成功<br>";
    }
    public function __destruct(){
    echo "<br>触发destruct成功<br>";
    }
    public function __sleep(){
      echo "<br>触发sleep成功<br>";
       return array('name','age');
    }
    public function __wakeup(){
      echo "<br>触发wakeup成功<br>";
    }
    public function __toString(){
      echo "<br>触发toString成功<br>";
    }
    public function __call($name,$age){
      echo "$name,$age[0]";
      echo "<br>触发call成功<br>";
    }
    public  static function __callStatic($name,$age){
      echo "$name,$age[0]";
      echo "<br>触发callStatic成功<br>";
    }
    public function __get($name){
      echo "$name";
      echo "<br>触发get成功<br>";
    }
    public function __set($name,$age){
      echo "$name,$age";
      echo "<br>触发set成功<br>";
    }
    public function __isset($name){
      echo "$name";
      echo "<br>触发isset成功<br>";
    }
    public function __unset($name){
      echo "$name";
      echo "<br>触发unset成功<br>";
    }
    public function __invoke(){
      echo "<br>触发invoke成功<br>";
    }
    public function __clone(){
      echo "<br>触发clone成功<br>";
    }
    public function __debuginfo(){
      echo "<br>触发debuginfo成功<br>";
      return ['用户名'=> $this -> name];
    }
    public static function __set_state($password){
      echo "<br>触发set_state成功<br>";
      $data = new Stu();
      foreach($password as $kay => $value){
        $dara -> $kay = $value;
      }
      return $data;
    }
  //  public function __autoload($password){
    //  echo '<br>触发autoload成功<br>';
    //}


}
$stu = new Stu(" "); //实例化对象
//echo "<br>".'==============='."<br>";
//echo $stu;  //使用后触发__toString()
//echo "<br>".'==============='."<br>";
$stu->test('hello world');//使用后触发__call()
Stu::test('hello'); //使用后触发__callStatic()
$stu->password;//使用后触发__get()
$stu ->password = '123456';//使用后触发__set()
isset($stu -> password);//使用后触发__isset()
empty($stu -> root);//使用后触发__isset()
unset($stu -> username);//使用后触发__unset()
echo $stu();//使用后触发__invoke()
$a = clone($stu);//使用后触发__clone(),注意这里是拷贝的对象

$stu ->name ='admin';
var_dump($stu);//使用后触发__debuginfo()
print_r($stu);//使用后触发__debuginfo()

var_export($stu);//使用后触发__set_state()

$stus = serialize($stu);//进行序列化
echo $stus; //打印序列化
echo "<br>".'==============='."<br>";
var_dump(unserialize($stus));//进行反序列化
echo "<br>".'==============='."<br>";
?>

9.PHP异或

------异或绕正则(当ascii 0-127都过滤时,使用)

原理

$a = 1^ 1; //0

$a= 0^ 0; //0

$a= 1^ 0; //1$a= 0^ 1; //1

返回0或者1

以制作免杀马为例:

在制作免杀马的过程,根据php的语言特性对字符进行!运算会将字符类型转为bool类型,而bool类型遇到运算符号时,true会自动转为数字1,false会自动转为数字0,如果将bool类型进行计算,并使用chr()函数转为字符,使用"."进行连接,便可以绕过preg_match匹配。

详情了解php不同于其他语言部分

但是很多的preg_match会过滤掉".“,所以需要使用异或运算进行绕过,很多的免杀马都是这样制作的。php对字符进行异或运算是先将字符转换成ASCII码然后进行异或运算,并且php能直接对一串字符串进行异或运算,例如"123”^"abc"是"1"与"a"进行异或然后"2"与"b"进行异或,以此类推,在异或结束后就获得了想要的字符串。

**注意点:**进行异或运算时要将数字转换成字符形式,如果数字(int)和字符异或的话,结果只会是数字,例如1"a"=1,"a"2=2,将数字转换成字符串可以使用trim()函数。

以GET或POST传入字符绕preg_match为例:

php的eval()函数在执行时如果内部有类似"abc"^"def"的计算式,那么就先进行计算再执行,我们可以利用再创参数来实现更方便的操作,例如传入?a=$_GET[b],由于b不受限制就可以任意传值了,不过

注意1:在测试过程中发现问题,类似phpinfo();的,需要将后面的();放在第个参数的后面,例如url?a={_GET}{b}();&b=phpinfo,也就是?a=KaTeX parse error: Expected '}', got 'EOF' at end of input: …hpinfo,在传入后实际上为{???^???}{?}();但是到了eval()函数内部就会变成${_GET}{?}();成功执行。

注意2:测试中发现,传值时对于要计算的部分不能用括号括起来,因为括号也将被识别为传入的字符串,可以使用{}代替,原因是php的use of undefined constant特性,例如 G E T a 这样的语句 p h p 是不会判为错误的 , 因为 使用来界定变量的 , 这句话就是会 将 G E T 自动看为字符串 , 也就是 {_GET}{a}这样的语句php是不会判为错误的,因为{}使用来界定变量的,这句话就是会将_GET自动看为字符串,也就是 GETa这样的语句php是不会判为错误的,因为使用来界定变量的,这句话就是会GET自动看为字符串,也就是_GET[‘a‘]

例如

preg_match(‘/[\x00- 0-9A-Za-z\‘"\`~_&.,|=[\x7F]+/i‘, $hhh) 
===========================================
\xnn 匹配ASCII代码中十六进制代码为nn的字符

[x00-x7f] 匹配ASCII值从0-127的字符

0-127表示单字节字符,也就是:数字,英文字符,半角符号,以及某些控制字符。
0-127的ascii[0-127]的字符 数字 字母 一些字符等等都被过滤了

异或payload

①url?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=phpinfo

②url?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

③url?_=${%86%86%86%86^%d9%c1%c3%d2}{%d2}();&%d2=phpinfo

a=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo,在传入后实际上为${????^????}{?}();但是到了eval()函数内部就会变成${_GET}{?}();成功执行

异或运算进行绕过,很多的免杀马都是这样制作的。php对字符进行异或运算是先将字符转换成ASCII码然后进行异或运算,并且php能直接对一串字符串进行异或运算,例如"123"^"abc"是"1"与"a"进行异或然后"2"与"b"进行异或,以此类推,在异或结束后就获得了想要的字符串。

**注意点:**进行异或运算时要将数字转换成字符形式,如果数字(int)和字符异或的话,结果只会是数字,例如1"a"=1,"a"2=2,将数字转换成字符串可以使用trim()函数。

以GET或POST传入字符绕preg_match为例:

php的eval()函数在执行时如果内部有类似"abc"^"def"的计算式,那么就先进行计算再执行,我们可以利用再创参数来实现更方便的操作,例如传入?a=$_GET[b],由于b不受限制就可以任意传值了,不过

注意1:在测试过程中发现问题,类似phpinfo();的,需要将后面的();放在第个参数的后面,例如url?a={_GET}{b}();&b=phpinfo,也就是?a=KaTeX parse error: Expected '}', got 'EOF' at end of input: …hpinfo,在传入后实际上为{???^???}{?}();但是到了eval()函数内部就会变成${_GET}{?}();成功执行。

注意2:测试中发现,传值时对于要计算的部分不能用括号括起来,因为括号也将被识别为传入的字符串,可以使用{}代替,原因是php的use of undefined constant特性,例如 G E T a 这样的语句 p h p 是不会判为错误的 , 因为 使用来界定变量的 , 这句话就是会 将 G E T 自动看为字符串 , 也就是 {_GET}{a}这样的语句php是不会判为错误的,因为{}使用来界定变量的,这句话就是会将_GET自动看为字符串,也就是 GETa这样的语句php是不会判为错误的,因为使用来界定变量的,这句话就是会GET自动看为字符串,也就是_GET[‘a‘]

例如

preg_match(‘/[\x00- 0-9A-Za-z\‘"\`~_&.,|=[\x7F]+/i‘, $hhh) 
===========================================
\xnn 匹配ASCII代码中十六进制代码为nn的字符

[x00-x7f] 匹配ASCII值从0-127的字符

0-127表示单字节字符,也就是:数字,英文字符,半角符号,以及某些控制字符。
0-127的ascii[0-127]的字符 数字 字母 一些字符等等都被过滤了

异或payload

①url?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=phpinfo

②url?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

③url?_=${%86%86%86%86^%d9%c1%c3%d2}{%d2}();&%d2=phpinfo

a=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo,在传入后实际上为${????^????}{?}();但是到了eval()函数内部就会变成${_GET}{?}();成功执行

我清风 与诸君共勉,共创辉煌篇章。
(给个赞赞,顺带关注一下,清风持续为帅哥美女们输出优质文章,需要什么知识点的文章,请在评论区留言)
(本次PHP知识点分多个文章进行编写,努力涵盖CTF备赛知识点)

  • 19
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值