FindUncommonShares 快速查找AD域中的共享

FindUncommonShares 快速查找AD域中的共享

FindUncommonShares简介

FindUncommonShares 是一个 Python 脚本,允许在巨大的 Windows 域中快速找到不常见的共享,并通过读/写 访问进行过滤。

脚本FindUncommonShares.py是PowerViewInvoke-ShareFinder.ps1的 Python 等价物

特征

  • 只需要一个低权限的域用户帐户
  • 从域控制器的 LDAP 自动获取所有计算机的列表。
  • 忽略带有 (以 结尾$)的隐藏共享--ignore-hidden-shares
  • 用于发现 SMB 共享的多线程连接。
  • 以 JSON 格式导出结果,其中包含 IP、名称、注释、标志和带有--export-json <file.json>.
  • 以 XLSX 格式导出结果,其中包含 IP、名称、注释、标志和带有--export-xlsx <file.xlsx>.
  • 使用 IP、名称、注释、标志和 UNC 路径将结果导出到 SQLITE3 中--export-sqlite <file.db>
  • 迭代 LDAP 结果页面以获取域中的每台计算机,无论大小。

示范

FindUncommonShares 快速查找AD域中的共享

快速获胜命令

  • 列出当前用户具有写访问权限的所有共享:
./FindUncommonShares.py -u user -p 'Podalirius123!' -d DOMAIN --dc-ip 192.168.1.71 --writable
  • 将域中的共享列表导出到客户端的 Excel 文件:
./FindUncommonShares.py -u user -p 'Podalirius123!' -d DOMAIN --dc-ip 192.168.1.71 --export-xlsx ./examples/results.xlsx
  • 列出当前用户具有访问权限的所有共享:
./FindUncommonShares.py -u user -p 'Podalirius123!' -d DOMAIN --dc-ip 192.168.1.71 --check-user-access

用法

$ ./FindUncommonShares.py -h
FindUncommonShares v3.0 - by @podalirius_

用法: FindUncommonShares.py [-h] [-v] [--use-ldaps] [-q] [--debug] [-no-colors] [-t THREADS] [-l LDAP_QUERY] [-ns NAMESERVER]
                             [--check-user-access] [--readable] [--writable] [-I] [-i IGNORED_SHARES] [-s ACCEPTED_SHARES]
                             [--export-xlsx EXPORT_XLSX] [--export-json EXPORT_JSON] [--export-sqlite EXPORT_SQLITE] --dc-ip ip
                             address [-d DOMAIN] [-u USER] [--no-pass | -p PASSWORD | -H [LMHASH:]NTHASH | --aes-key hex key] [-k]

在远程机器上查找不常见的SMB共享。

选项:
  -h, --help            显示此帮助消息并退出.
  -v, --verbose         详细的模式。(默认值:False)。
  --use-ldaps           使用LDAPS代替LDAP。
  -q, --quiet           不显示任何信息。
  --debug               调试模式。(默认值:False)。
  -no-colors            禁用彩色输出模式。
  -t THREADS, --threads THREADS
                        线程数(默认:20)。
  -l LDAP_QUERY, --ldap-query LDAP_QUERY
                        用于从域中提取计算机的LDAP查询。
  -ns NAMESERVER, --nameserver NAMESERVER
                        要使用的DNS服务器的IP,而不是--dc-ip。
  --check-user-access   检查当前用户是否可以访问共享。
  --readable            仅列出当前用户具有读访问权限的共享。
  --writable            仅列出当前用户具有WRITE访问权限的共享。
  -I, --ignore-hidden-shares
                        忽略隐藏的共享(以$结尾的共享)
  -i IGNORED_SHARES, --ignore-share IGNORED_SHARES
                        指定要显式忽略的共享。(例如,--ignore-share 'C$' --ignore-share 'Backup)
  -s ACCEPTED_SHARES, --show-share ACCEPTED_SHARES
                        S (例如指定要显式显示的共享。., --show-share 'C$' --show-share 'Backup')

输出文件:
  --export-xlsx EXPORT_XLSX
                        输出用于存储结果的XLSX文件。
  --export-json EXPORT_JSON
                        用于存储结果的输出JSON文件。
  --export-sqlite EXPORT_SQLITE
                        输出SQLITE3文件以存储结果。

认证与连接:
  --dc-ip ip address    Kerberos的域控制器或KDC (Key Distribution Center)的IP地址。如果省略,它将使用identity参数中指定的域部分(FQDN)
  -d DOMAIN, --domain DOMAIN
                        需要认证的(FQDN)域
  -u USER, --user USER  要进行身份验证的用户

凭证:
  --no-pass             不要询问密码(对于-k很有用)
  -p PASSWORD, --password PASSWORD
                        进行身份验证的密码
  -H [LMHASH:]NTHASH, --hashes [LMHASH:]NTHASH
                        NT/LM哈希值,格式为 LMhash:NThash
  --aes-key hex key     用于Kerberos身份验证的AES密钥(128或256位)
  -k, --kerberos        使用Kerberos身份验证。根据目标参数从.ccache文件(KRB5CCNAME)中获取凭据。如果找不到有效的凭证,它将使用命令行中指定的凭证.

导出结果

每个 JSON 条目如下所示:

{
    "computer": {
        "fqdn": "TDC01.DOMAIN.local",
        "ip": "192.168.1.71"
    },
    "share": {
        "name": "IPC$",
        "comment": "Remote IPC",
        "hidden": true,
        "uncpath": "\\\\192.168.1.71\\IPC$\\",
        "type": {
            "stype_value": 2147483651,
            "stype_flags": [
                "STYPE_IPC",
                "STYPE_TEMPORARY"
            ]
        },
        "access_rights": {
            "readable": true,
            "writable": false
        }
    }
}

导出结果示例

results.json

FindUncommonShares 快速查找AD域中的共享
{
    "TDC01.DOMAIN.local": [
        {
            "computer": {
                "fqdn": "TDC01.DOMAIN.local",
                "ip": "192.168.1.71"
            },
            "share": {
                "name": "ADMIN$",
                "comment": "Remote Admin",
                "hidden": true,
                "uncpath": "\\\\192.168.1.71\\ADMIN$\\",
                "type": {
                    "stype_value": 2147483648,
                    "stype_flags": [
                        "STYPE_DISKTREE",
                        "STYPE_TEMPORARY"
                    ]
                },
                "access_rights": {
                    "readable": false,
                    "writable": false
                }
            }
        },
        {
            "computer": {
                "fqdn": "TDC01.DOMAIN.local",
                "ip": "192.168.1.71"
            },
            "share": {
                "name": "C$",
                "comment": "Default share",
                "hidden": true,
                "uncpath": "\\\\192.168.1.71\\C$\\",
                "type": {
                    "stype_value": 2147483648,
                    "stype_flags": [
                        "STYPE_DISKTREE",
                        "STYPE_TEMPORARY"
                    ]
                },
                "access_rights": {
                    "readable": false,
                    "writable": false
                }
            }
        },
        {
            "computer": {
                "fqdn": "TDC01.DOMAIN.local",
                "ip": "192.168.1.71"
            },
            "share": {
                "name": "IPC$",
                "comment": "Remote IPC",
                "hidden": true,
                "uncpath": "\\\\192.168.1.71\\IPC$\\",
                "type": {
                    "stype_value": 2147483651,
                    "stype_flags": [
                        "STYPE_IPC",
                        "STYPE_TEMPORARY"
                    ]
                },
                "access_rights": {
                    "readable": true,
                    "writable": false
                }
            }
        },
        {
            "computer": {
                "fqdn": "TDC01.DOMAIN.local",
                "ip": "192.168.1.71"
            },
            "share": {
                "name": "NETLOGON",
                "comment": "Logon server share ",
                "hidden": false,
                "uncpath": "\\\\192.168.1.71\\NETLOGON\\",
                "type": {
                    "stype_value": 0,
                    "stype_flags": [
                        "STYPE_DISKTREE",
                        "STYPE_SPECIAL",
                        "STYPE_TEMPORARY"
                    ]
                },
                "access_rights": {
                    "readable": true,
                    "writable": false
                }
            }
        },
        {
            "computer": {
                "fqdn": "TDC01.DOMAIN.local",
                "ip": "192.168.1.71"
            },
            "share": {
                "name": "SYSVOL",
                "comment": "Logon server share ",
                "hidden": false,
                "uncpath": "\\\\192.168.1.71\\SYSVOL\\",
                "type": {
                    "stype_value": 0,
                    "stype_flags": [
                        "STYPE_DISKTREE",
                        "STYPE_SPECIAL",
                        "STYPE_TEMPORARY"
                    ]
                },
                "access_rights": {
                    "readable": true,
                    "writable": false
                }
            }
        },
        {
            "computer": {
                "fqdn": "TDC01.DOMAIN.local",
                "ip": "192.168.1.71"
            },
            "share": {
                "name": "Users",
                "comment": "",
                "hidden": false,
                "uncpath": "\\\\192.168.1.71\\Users\\",
                "type": {
                    "stype_value": 0,
                    "stype_flags": [
                        "STYPE_DISKTREE",
                        "STYPE_SPECIAL",
                        "STYPE_TEMPORARY"
                    ]
                },
                "access_rights": {
                    "readable": true,
                    "writable": false
                }
            }
        }
    ]
}

results.sqlite3

FindUncommonShares 快速查找AD域中的共享

results.xlsx

FindUncommonShares 快速查找AD域中的共享

工具下载

FindUncommonShares.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File name          : FindUncommonShares.py
# Author             : Podalirius (@podalirius_)
# Date created       : 30 Jan 2022


from concurrent.futures import ThreadPoolExecutor
from enum import Enum
from impacket import version
from impacket.smbconnection import SMBConnection, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB_DIALECT, SessionError
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech
from sectools.windows.ldap import raw_ldap_query, init_ldap_session
from sectools.windows.crypto import nt_hash, parse_lm_nt_hashes
import argparse
import binascii
import dns.resolver
import dns.exception
import json
import ldap3
import logging
import ntpath
import os
import random
import re
import sqlite3
import socket
import ssl
import sys
import threading
import time
import traceback
import xlsxwriter

VERSION = "3.0"

COMMON_SHARES = [
    "C$",
    "ADMIN$", "IPC$",
    "PRINT$", "print$",
    "fax$", "FAX$",
    "SYSVOL", "NETLOGON"
]


class MicrosoftDNS(object):
    """
    Documentation for class MicrosoftDNS
    """

    __wildcard_dns_cache = {}

    def __init__(self, dnsserver, auth_domain, auth_username, auth_password, auth_dc_ip, auth_lm_hash, auth_nt_hash, verbose=False):
        super(MicrosoftDNS, self).__init__()
        self.dnsserver = dnsserver
        self.verbose = verbose
        self.auth_domain = auth_domain
        self.auth_username = auth_username
        self.auth_password = auth_password
        self.auth_dc_ip = auth_dc_ip
        self.auth_lm_hash = auth_lm_hash
        self.auth_nt_hash = auth_nt_hash

    def resolve(self, target_name):
        target_ips = []
        for rdtype in ["A", "AAAA"]:
            dns_answer = self.get_record(value=target_name, rdtype=rdtype)
            if dns_answer is not None:
                for record in dns_answer:
                    target_ips.append(record.address)
        if self.verbose and len(target_ips) == 0:
            print("[debug] No records found for %s." % target_name)
        return target_ips

    def get_record(self, rdtype, value):
        dns_resolver = dns.resolver.Resolver()
        dns_resolver.nameservers = [self.dnsserver]
        dns_answer = None
        # Try UDP
        try:
            dns_answer = dns_resolver.resolve(value, rdtype=rdtype, tcp=False)
        except dns.resolver.NXDOMAIN:
            # the domain does not exist so dns resolutions remain empty
            pass
        except dns.resolver.NoAnswer as e:
            # domains existing but not having AAAA records is common
            pass
        except dns.resolver.NoNameservers as e:
            pass
        except dns.exception.DNSException as e:
            pass

        if dns_answer is None:
            # Try TCP
            try:
                dns_answer = dns_resolver.resolve(value, rdtype=rdtype, tcp=True)
            except dns.resolver.NXDOMAIN:
                # the domain does not exist so dns resolutions remain empty
                pass
            except dns.resolver.NoAnswer as e:
                # domains existing but not having AAAA records is common
                pass
            except dns.resolver.NoNameservers as e:
                pass
            except dns.exception.DNSException as e:
                pass

        if self.verbose and dns_answer is not None:
            for record in dns_answer:
                print("[debug] '%s' record found for %s: %s" % (rdtype, value, record.address))

        return dns_answer

    def check_wildcard_dns(self):
        ldap_server, ldap_session = init_ldap_session(
            auth_domain=self.auth_domain,
            auth_dc_ip=self.auth_dc_ip,
            auth_username=self.auth_username,
            auth_password=self.auth_password,
            auth_lm_hash=self.auth_lm_hash,
            auth_nt_hash=self.auth_nt_hash,
            use_ldaps=False
        )

        target_dn = "CN=MicrosoftDNS,DC=DomainDnsZones," + ldap_server.info.other["rootDomainNamingContext"][0]

        ldapresults = list(ldap_session.extend.standard.paged_search(target_dn, "(&(objectClass=dnsNode)(dc=\\2A))", attributes=["distinguishedName", "dNSTombstoned"]))

        results = {}
        for entry in ldapresults:
            if entry['type'] != 'searchResEntry':
                continue
            results[entry['dn']] = entry["attributes"]

        if len(results.keys()) != 0:
            print("[!] WARNING! Wildcard DNS entries found, dns resolution will not be consistent.")
            for dn, data in results.items():
                fqdn = re.sub(',CN=MicrosoftDNS,DC=DomainDnsZones,DC=DOMAIN,DC=local$', '', dn)
                fqdn = '.'.join([dc.split('=')[1] for dc in fqdn.split(',')])

                ips = self.resolve(fqdn)

                if data["dNSTombstoned"]:
                    print("  | %s ──> %s (set to be removed)" % (dn, ips))
                else:
                    print("  | %s ──> %s" % (dn, ips))

                # Cache found wildcard dns
                for ip in ips:
                    if fqdn not in self.__wildcard_dns_cache.keys():
                        self.__wildcard_dns_cache[fqdn] = {}
                    if ip not in self.__wildcard_dns_cache[fqdn].keys():
                        self.__wildcard_dns_cache[fqdn][ip] = []
                    self.__wildcard_dns_cache[fqdn][ip].append(data)
            print()
        return results


def STYPE_MASK(stype_value):
    known_flags = {
        ## One of the following values may be specified. You can isolate these values by using the STYPE_MASK value.
        # Disk drive.
        "STYPE_DISKTREE": 0x0,

        # Print queue.
        "STYPE_PRINTQ": 0x1,

        # Communication device.
        "STYPE_DEVICE": 0x2,

        # Interprocess communication (IPC).
        "STYPE_IPC": 0x3,

        ## In addition, one or both of the following values may be specified.
        # Special share reserved for interprocess communication (IPC$) or remote administration of the server (ADMIN$).
        # Can also refer to administrative shares such as C$, D$, E$, and so forth. For more information, see Network Share Functions.
        "STYPE_SPECIAL": 0x80000000,

        # A temporary share.
        "STYPE_TEMPORARY": 0x40000000
    }
    flags = []
    if (stype_value & 0b11) == known_flags["STYPE_DISKTREE"]:
        flags.append("STYPE_DISKTREE")
    elif (stype_value & 0b11) == known_flags["STYPE_PRINTQ"]:
        flags.append("STYPE_PRINTQ")
    elif (stype_value & 0b11) == known_flags["STYPE_DEVICE"]:
        flags.append("STYPE_DEVICE")
    elif (stype_value & 0b11) == known_flags["STYPE_IPC"]:
        flags.append("STYPE_IPC")
    if (stype_value & known_flags["STYPE_SPECIAL"]) == 0:
        flags.append("STYPE_SPECIAL")
    if (stype_value & known_flags["STYPE_TEMPORARY"]) == 0:
        flags.append("STYPE_TEMPORARY")
    return flags


def export_json(options, results):
    print("[>] Exporting results to %s ... " % options.export_json, end="")
    sys.stdout.flush()
    basepath = os.path.dirname(options.export_json)
    filename = os.path.basename(options.export_json)
    if basepath not in [".", ""]:
        if not os.path.exists(basepath):
            os.makedirs(basepath)
        path_to_file = basepath + os.path.sep + filename
    else:
        path_to_file = filename
    f = open(path_to_file, "w")
    f.write(json.dumps(results, indent=4) + "\n")
    f.close()
    print("done.")


def export_xlsx(options, results):
    print("[>] Exporting results to %s ... " % options.export_xlsx, end="")
    sys.stdout.flush()
    basepath = os.path.dirname(options.export_xlsx)
    filename = os.path.basename(options.export_xlsx)
    if basepath not in [".", ""]:
        if not os.path.exists(basepath):
            os.makedirs(basepath)
        path_to_file = basepath + os.path.sep + filename
    else:
        path_to_file = filename
    workbook = xlsxwriter.Workbook(path_to_file)
    worksheet = workbook.add_worksheet()

    header_format = workbook.add_format({'bold': 1})
    header_fields = ["Computer FQDN", "Computer IP", "Share name", "Share comment", "Is hidden"]
    for k in range(len(header_fields)):
        worksheet.set_column(k, k + 1, len(header_fields[k]) + 3)
    worksheet.set_row(0, 20, header_format)
    worksheet.write_row(0, 0, header_fields)

    row_id = 1
    for computername in results.keys():
        computer = results[computername]
        for share in computer:
            data = [share["computer"]["fqdn"], share["computer"]["ip"], share["share"]["name"], share["share"]["comment"], share["share"]["hidden"]]
            worksheet.write_row(row_id, 0, data)
            row_id += 1
    worksheet.autofilter(0, 0, row_id, len(header_fields) - 1)
    workbook.close()
    print("done.")


def export_sqlite(options, results):
    print("[>] Exporting results to %s ... " % options.export_sqlite, end="")
    sys.stdout.flush()
    basepath = os.path.dirname(options.export_sqlite)
    filename = os.path.basename(options.export_sqlite)
    if basepath not in [".", ""]:
        if not os.path.exists(basepath):
            os.makedirs(basepath)
        path_to_file = basepath + os.path.sep + filename
    else:
        path_to_file = filename

    conn = sqlite3.connect(path_to_file)
    cursor = conn.cursor()
    cursor.execute("CREATE TABLE IF NOT EXISTS shares(fqdn VARCHAR(255), ip VARCHAR(255), shi1_netname VARCHAR(255), shi1_remark VARCHAR(255), shi1_type INTEGER, hidden INTEGER);")
    for computername in results.keys():
        for share in results[computername]:
            cursor.execute("INSERT INTO shares VALUES (?, ?, ?, ?, ?, ?)", (
                    share["computer"]["fqdn"],
                    share["computer"]["ip"],
                    share["share"]["name"],
                    share["share"]["comment"],
                    share["share"]["type"]["stype_value"],
                    share["share"]["hidden"]
                )
            )
    conn.commit()
    conn.close()
    print("done.")


def is_port_open(target, port):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.settimeout(1)
        # Non-existant domains cause a lot of errors, added error handling
        try:
            return s.connect_ex((target, port)) == 0
        except Exception as e:
            return False


def dns_resolve(options, target_name):
    dns_resolver = dns.resolver.Resolver()
    if options.nameserver is not None:
        dns_resolver.nameservers = [options.nameserver]
    else:
        dns_resolver.nameservers = [options.dc_ip]
    dns_answer = None

    # Try UDP
    try:
        dns_answer = dns_resolver.resolve(target_name, rdtype="A", tcp=False)
    except dns.resolver.NXDOMAIN:
        # the domain does not exist so dns resolutions remain empty
        pass
    except dns.resolver.NoAnswer as e:
        # domains existing but not having AAAA records is common
        pass
    except dns.resolver.NoNameservers as e:
        pass
    except dns.exception.DNSException as e:
        pass

    if dns_answer is None:
        # Try TCP
        try:
            dns_answer = dns_resolver.resolve(target_name, rdtype="A", tcp=True)
        except dns.resolver.NXDOMAIN:
            # the domain does not exist so dns resolutions remain empty
            pass
        except dns.resolver.NoAnswer as e:
            # domains existing but not having AAAA records is common
            pass
        except dns.resolver.NoNameservers as e:
            pass
        except dns.exception.DNSException as e:
            pass

    target_ip = []
    if dns_answer is not None:
        target_ip = [ip.address for ip in dns_answer]

    if len(target_ip) != 0:
        return target_ip[0]
    else:
        return None


def parse_args():
    print("FindUncommonShares v%s - by @podalirius_\n" % VERSION)

    parser = argparse.ArgumentParser(add_help=True, description='Find uncommon SMB shares on remote machines.')

    parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode. (default: False).")

    parser.add_argument("--use-ldaps", default=False, action="store_true", help="Use LDAPS instead of LDAP.")
    parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", default=False, help="Show no information at all.")
    parser.add_argument("--debug", dest="debug", action="store_true", default=False, help="Debug mode. (default: False).")
    parser.add_argument("-no-colors", dest="colors", action="store_false", default=True, help="Disables colored output mode.")
    parser.add_argument("-t", "--threads", dest="threads", action="store", type=int, default=20, required=False, help="Number of threads (default: 20).")
    parser.add_argument("-l", "--ldap-query", dest="ldap_query", type=str, default="(objectCategory=computer)", required=False, help="LDAP query to use to extract computers from the domain.")
    parser.add_argument("-ns", "--nameserver", dest="nameserver", default=None, required=False, help="IP of the DNS server to use, instead of the --dc-ip.")

    # Shares
    parser.add_argument("--check-user-access", default=False, action="store_true", help="Check if current user can access the share.")
    parser.add_argument("--readable", default=False, action="store_true", help="Only list shares that current user has READ access to.")
    parser.add_argument("--writable", default=False, action="store_true", help="Only list shares that current user has WRITE access to.")
    parser.add_argument("-I", "--ignore-hidden-shares", dest="ignore_hidden_shares", action="store_true", default=False, help="Ignores hidden shares (shares ending with $)")
    parser.add_argument("-i", "--ignore-share", default=[], dest="ignored_shares", action="append", required=False, help="Specify shares to ignore explicitly. (e.g., --ignore-share 'C$' --ignore-share 'Backup')")
    parser.add_argument("-s", "--show-share", default=[], dest="accepted_shares", action="append", required=False, help="Specify shares to show explicitly. (e.g., --show-share 'C$' --show-share 'Backup')")

    output = parser.add_argument_group('Output files')
    output.add_argument("--export-xlsx", dest="export_xlsx", type=str, default=None, required=False, help="Output XLSX file to store the results in.")
    output.add_argument("--export-json", dest="export_json", type=str, default=None, required=False, help="Output JSON file to store the results in.")
    output.add_argument("--export-sqlite", dest="export_sqlite", type=str, default=None, required=False, help="Output SQLITE3 file to store the results in.")

    authconn = parser.add_argument_group('Authentication & connection')
    authconn.add_argument('--dc-ip', required=True, action='store', metavar="ip address", help='IP Address of the domain controller or KDC (Key Distribution Center) for Kerberos. If omitted it will use the domain part (FQDN) specified in the identity parameter')
    authconn.add_argument("-d", "--domain", dest="auth_domain", metavar="DOMAIN", action="store", default="", help="(FQDN) domain to authenticate to")
    authconn.add_argument("-u", "--user", dest="auth_username", metavar="USER", action="store", default="", help="user to authenticate with")

    secret = parser.add_argument_group("Credentials")
    cred = secret.add_mutually_exclusive_group()
    cred.add_argument("--no-pass", default=False, action="store_true", help="Don't ask for password (useful for -k)")
    cred.add_argument("-p", "--password", dest="auth_password", metavar="PASSWORD", action="store", default="", help="Password to authenticate with")
    cred.add_argument("-H", "--hashes", dest="auth_hashes", action="store", metavar="[LMHASH:]NTHASH", help='NT/LM hashes, format is LMhash:NThash')
    cred.add_argument("--aes-key", dest="auth_key", action="store", metavar="hex key", help='AES key to use for Kerberos Authentication (128 or 256 bits)')
    secret.add_argument("-k", "--kerberos", dest="use_kerberos", action="store_true", help='Use Kerberos authentication. Grabs credentials from .ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line')

    if len(sys.argv) == 1:
        parser.print_help()
        sys.exit(1)

    options = parser.parse_args()

    if options.auth_password is None and options.no_pass == False:
        from getpass import getpass
        options.auth_password = getpass("Password:")

    if options.readable == True or options.writable == True:
        options.check_user_access = True

    return options


def print_results(options, sharename, address, sharecomment, access_rights):
    # Share is not a common share

    str_access_readable, str_colored_access_readable = "", ""
    str_access_writable, str_colored_access_writable = "", ""
    str_access, str_colored_access = "", ""
    if options.check_user_access:
        if access_rights["readable"] == True:
            str_access_readable = "READ"
            str_colored_access_readable = "\x1b[1;92mREAD\x1b[0m"
        if access_rights["writable"] == True:
            str_access_writable = "WRITE"
            str_colored_access_writable = "\x1b[1;92mWRITE\x1b[0m"
        if access_rights["readable"] == False and access_rights["writable"] == False:
            str_access = "access: DENIED"
            str_colored_access = "access: \x1b[1;91mDENIED\x1b[0m"
        elif access_rights["readable"] == True and access_rights["writable"] == True:
            str_access = "access: %s, %s" % (str_access_readable, str_access_writable)
            str_colored_access = "access: %s, %s" % (str_colored_access_readable, str_colored_access_writable)
        elif access_rights["readable"] == False and access_rights["writable"] == True:
            str_access = "access: %s" % str_access_writable
            str_colored_access = "access: %s" % str_colored_access_writable
        elif access_rights["readable"] == True and access_rights["writable"] == False:
            str_access = "access: %s" % str_access_readable
            str_colored_access = "access: %s" % str_colored_access_readable

    # Specific use cases
    do_print_results = False
    # Print all results
    if options.readable == False and options.writable == False:
        do_print_results = True
    # print results for readable shares
    if options.readable == True:
        if access_rights["readable"] == True:
            do_print_results = True
        else:
            do_print_results = False
    # print results for writable shares
    if options.writable == True:
        if access_rights["writable"] == True:
            do_print_results = True
        else:
            do_print_results = False

    if not do_print_results:
        return

    if ((sharename not in COMMON_SHARES) or (sharename in options.accepted_shares)) and (sharename not in options.ignored_shares):
        if not options.quiet:
            if len(sharecomment) != 0:
                if options.colors:
                    # Hidden share
                    if sharename.endswith('$') and not options.ignore_hidden_shares:
                        print("[>] Found '\x1b[94m%s\x1b[0m' on '\x1b[96m%s\x1b[0m' (comment: '\x1b[95m%s\x1b[0m') %s" % (sharename, address, sharecomment, str_colored_access))
                    # Not hidden share
                    else:
                        print("[>] Found '\x1b[93m%s\x1b[0m' on '\x1b[96m%s\x1b[0m' (comment: '\x1b[95m%s\x1b[0m') %s" % (sharename, address, sharecomment, str_colored_access))
                else:
                    print("[>] Found '%s' on '%s' (comment: '%s') %s" % (sharename, address, sharecomment, str_access))
            else:
                if options.colors:
                    # Hidden share
                    if sharename.endswith('$') and not options.ignore_hidden_shares:
                        print("[>] Found '\x1b[94m%s\x1b[0m' on '\x1b[96m%s\x1b[0m' %s" % (sharename, address, str_colored_access))
                    # Not hidden share
                    else:
                        print("[>] Found '\x1b[93m%s\x1b[0m' on '\x1b[96m%s\x1b[0m' %s" % (sharename, address, str_colored_access))
                else:
                    # Hidden share
                    if sharename.endswith('$') and not options.ignore_hidden_shares:
                        print("[>] Found '%s' on '%s' %s" % (sharename, address, str_access))
                    # Not hidden share
                    else:
                        print("[>] Found '%s' on '%s' %s" % (sharename, address, str_access))
        else:
            # Quiet mode, do not print anything
            pass
    # Debug mode in case of a common share
    elif options.debug and not options.quiet:

        # Share has a comment
        if len(sharecomment) != 0:

            # Colored output
            if options.colors:
                # Hidden share
                if sharename.endswith('$') and not options.ignore_hidden_shares:
                    print("[>] Skipping common share '\x1b[94m%s\x1b[0m' on '\x1b[96m%s\x1b[0m' (comment: '\x1b[95m%s\x1b[0m') %s" % (sharename, address, sharecomment, str_colored_access))
                # Not hidden share
                else:
                    print("[>] Skipping common share '\x1b[93m%s\x1b[0m' on '\x1b[96m%s\x1b[0m' (comment: '\x1b[95m%s\x1b[0m') %s" % (sharename, address, sharecomment, str_colored_access))

            # Not colored output
            else:
                print("[>] Skipping common share '%s' on '%s' (comment: '%s') %s" % (sharename, address, sharecomment, str_access))

        # Share has no comment
        else:
            # Colored output
            if options.colors:
                # Hidden share
                if sharename.endswith('$') and not options.ignore_hidden_shares:
                    print("[>] Skipping common share '\x1b[94m%s\x1b[0m' on '\x1b[96m%s\x1b[0m' %s" % (sharename, address, str_colored_access))
                # Not hidden share
                else:
                    print("[>] Skipping common share '\x1b[93m%s\x1b[0m' on '\x1b[96m%s\x1b[0m' %s" % (sharename, address, str_colored_access))

            # Not colored output
            else:
                # Hidden share
                if sharename.endswith('$'):
                    if not options.ignore_hidden_shares:
                        print("[>] Skipping common share '%s' on '%s' %s" % (sharename, address, str_access))
                # Not hidden share
                else:
                    print("[>] Skipping common share '%s' on '%s' %s" % (sharename, address, str_access))


def get_machine_name(options, domain):
    if options.dc_ip is not None:
        s = SMBConnection(options.dc_ip, options.dc_ip)
    else:
        s = SMBConnection(domain, domain)
    try:
        s.login('', '')
    except Exception:
        if s.getServerName() == '':
            raise Exception('Error while anonymous logging into %s' % domain)
    else:
        s.logoff()
    return s.getServerName()


def get_access_rights(smbclient, sharename):
    access_rights = {"readable": False, "writable": False}
    try:
        smbclient.listPath(sharename, '*', password=None)
        access_rights["readable"] = True
    except SessionError as e:
        access_rights["readable"] = False

    try:
        temp_dir = ntpath.normpath("\\" + ''.join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ0123456759") for k in range(16)]))
        smbclient.createDirectory(sharename, temp_dir)
        smbclient.deleteDirectory(sharename, temp_dir)
        access_rights["writable"] = True
    except SessionError as e:
        access_rights["writable"] = False

    return access_rights


def init_smb_session(options, target_ip, domain, username, password, address, lmhash, nthash, port=445, debug=False):
    smbClient = SMBConnection(address, target_ip, sess_port=int(port))
    dialect = smbClient.getDialect()
    if dialect == SMB_DIALECT:
        if debug:
            print("[debug] SMBv1 dialect used")
    elif dialect == SMB2_DIALECT_002:
        if debug:
            print("[debug] SMBv2.0 dialect used")
    elif dialect == SMB2_DIALECT_21:
        if debug:
            print("[debug] SMBv2.1 dialect used")
    else:
        if debug:
            print("[debug] SMBv3.0 dialect used")
    if options.use_kerberos is True:
        smbClient.kerberosLogin(username, password, domain, lmhash, nthash, options.aesKey, options.dc_ip)
    else:
        smbClient.login(username, password, domain, lmhash, nthash)
    if smbClient.isGuestSession() > 0:
        if debug:
            print("[debug] GUEST Session Granted")
    else:
        if debug:
            print("[debug] USER Session Granted")
    return smbClient


def worker(options, target_name, domain, username, password, address, lmhash, nthash, results, lock):
    target_ip = dns_resolve(options, target_name)
    if target_ip is not None:
        if is_port_open(target_ip, 445):
            try:
                smbClient = init_smb_session(options, target_ip, domain, username, password, address, lmhash, nthash)
                resp = smbClient.listShares()
                for share in resp:
                    # SHARE_INFO_1 structure (lmshare.h)
                    # https://docs.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
                    sharename = share['shi1_netname'][:-1]
                    sharecomment = share['shi1_remark'][:-1]
                    sharetype = share['shi1_type']

                    access_rights = {}
                    if options.check_user_access:
                        access_rights = get_access_rights(smbClient, sharename)

                    lock.acquire()
                    if target_name not in results.keys():
                        results[target_name] = []
                    results[target_name].append(
                        {
                            "computer": {
                                "fqdn": target_name,
                                "ip": target_ip
                            },
                            "share": {
                                "name": sharename,
                                "comment": sharecomment,
                                "hidden": (True if sharename.endswith('$') else False),
                                "uncpath": "\\".join(['', '', target_ip, sharename, '']),
                                "type": {
                                    "stype_value": sharetype,
                                    "stype_flags": STYPE_MASK(sharetype)
                                },
                                "access_rights": access_rights
                            }
                        }
                    )

                    print_results(options, sharename, address, sharecomment, access_rights)

                    lock.release()

            except Exception as err:
                if options.debug:
                    lock.acquire()
                    print(err)
                    lock.release()

    # DNS Resolution failed
    else:
        if options.debug:
            lock.acquire()
            print("[!] Could not resolve")
            lock.release()


if __name__ == '__main__':
    options = parse_args()
    auth_lm_hash, auth_nt_hash = parse_lm_nt_hashes(options.auth_hashes)

    mdns = MicrosoftDNS(
        dnsserver=options.dc_ip,
        auth_domain=options.auth_domain,
        auth_username=options.auth_username,
        auth_password=options.auth_password,
        auth_dc_ip=options.dc_ip,
        auth_lm_hash=auth_lm_hash,
        auth_nt_hash=auth_nt_hash,
        verbose=options.verbose
    )
    mdns.check_wildcard_dns()

    if not options.quiet:
        print("[>] Extracting all computers ...")

    computers = raw_ldap_query(
        auth_domain=options.auth_domain,
        auth_dc_ip=options.dc_ip,
        auth_username=options.auth_username,
        auth_password=options.auth_password,
        auth_hashes=options.auth_hashes,
        query=options.ldap_query,
        attributes=["dNSHostName", "sAMAccountName"]
    )

    if not options.quiet:
        print("[+] Found %d computers in the domain. \n" % len(computers.keys()))
        print("[>] Enumerating shares ...")

    results = {}

    if len(computers.keys()) != 0:
        # Setup thread lock to properly write in the file
        lock = threading.Lock()
        # Waits for all the threads to be completed
        with ThreadPoolExecutor(max_workers=min(options.threads, len(computers.keys()))) as tp:
            for ck in computers.keys():
                computer = computers[ck]
                tp.submit(
                    worker,
                    options,
                    computer['dNSHostName'],
                    options.auth_domain,
                    options.auth_username,
                    options.auth_password,
                    computer['dNSHostName'],
                    auth_lm_hash,
                    auth_nt_hash,
                    results,
                    lock
                )

        if options.export_json is not None:
            export_json(options, results)

        if options.export_xlsx is not None:
            export_xlsx(options, results)

        if options.export_sqlite is not None:
            export_sqlite(options, results)
    else:
        print("[!] No computers in the domain found matching filter '%s'" % options.ldap_query)
    print("[+] Bye Bye!")

requirements.txt

impacket
xlsxwriter
sectools

项目地址:

GitHub:
github.com/p0dalirius/FindUncommonShares

转载请注明出处及链接

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用*标注