1. <?php
    2. include("flag.php");
    3. highlight_file(__FILE__);
    4. class FileHandler {
    5. protected $op;
    6. protected $filename;
    7. protected $content;
    8. function __construct() {
    9. $op = "1";
    10. $filename = "/tmp/tmpfile";
    11. $content = "Hello World!";
    12. $this->process();
    13. }
    14. public function process() {
    15. if($this->op == "1") {
    16. $this->write();
    17. } else if($this->op == "2") {
    18. $res = $this->read();
    19. $this->output($res);
    20. } else {
    21. $this->output("Bad Hacker!");
    22. }
    23. }
    24. private function write() {
    25. if(isset($this->filename) && isset($this->content)) {
    26. if(strlen((string)$this->content) > 100) {
    27. $this->output("Too long!");
    28. die();
    29. }
    30. $res = file_put_contents($this->filename, $this->content);
    31. if($res) $this->output("Successful!");
    32. else $this->output("Failed!");
    33. } else {
    34. $this->output("Failed!");
    35. }
    36. }
    37. private function read() {
    38. $res = "";
    39. if(isset($this->filename)) {
    40. $res = file_get_contents($this->filename);
    41. }
    42. return $res;
    43. }
    44. private function output($s) {
    45. echo "[Result]: <br>";
    46. echo $s;
    47. }
    48. function __destruct() {
    49. if($this->op === "2")
    50. $this->op = "1";
    51. $this->content = "";
    52. $this->process();
    53. }
    54. }
    55. function is_valid($s) {
    56. for($i = 0; $i < strlen($s); $i++)
    57. if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
    58. return false;
    59. return true;
    60. }
    61. if(isset($_GET{'str'})) {
    62. $str = (string)$_GET['str'];
    63. if(is_valid($str)) {
    64. $obj = unserialize($str);
    65. }
    66. }

    一道反序列化题目,接收一个字符串将其反序列化。
    先看__destruct方法,如果op为2,则将其赋值为1,content设为空,调用process方法。
    process方法中,根据op的值决定调用read方法或write方法。write方法可以向文件中写入内容,但是content无法控制,再看read方法,使用file_get_contents方法来获取内容,而filename是可以控制的,那么只需要想办法让程序调转到read方法即可,也就是需要控制op的值。
    可以注意到,__destruct方法中的判断是强类型判断,而上面的process中是弱类型判断,那么只需要传入数字2即可绕过__destruct中的判断。
    payload:

    1. O:11:"FileHandler":3:{s:5:"*op";i:2;s:11:"*filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:10:"*content";N;}

    但是由于类型是protected的原因,每个变量前面会有%00*%00的标志,绕过方法有两个:

    1. 将protected类型改为public,php7.1+版本对属性类型不敏感
    2. 将s改为大写,原理下面附上图。

    [网鼎杯 2020 青龙组]AreUSerialz - 图1
    最后读取到flag.php的内容,解码得到flag。