HTB2Trilium fixed
1 parent b03b5d7 commit 3ed3a9a4ebdc5bcffdf9915beb2514e2536f5312
0xRoM authored on 4 Oct
Showing 7 changed files
View
4
ReportToolz/config.php
$vdbPath = "/opt/RossMarks/DirtyScripts/ReportToolz/";
 
// repgen templates
//$template = "/mnt/hgfs/PentestOS/Misc/repgen_test/test_04/blank_template_v2.1.odt";
$template = "templates/odt/blank_template_v3.0.odt";
$template = "/mnt/hgfs/PentestOS/Sapphire2/repgen_template/blank_template_v3.0.odt";
//$template = "templates/odt/blank_template_v2.1.odt";
//$CHECKtemplate = "templates/odt/blank_template_check_v0.4.odt";
$vulnTemplate = "templates/odt/vuln_template_v1.0.xml";
 
?>
View
__pycache__/htb_client.cpython-311.pyc 0 → 100644
Not supported
View
2
■■■
eicar/<img src=x onError=alert(1)>.exe 0 → 100644
test
View
eicar/<img src=x onError=alert(1)>.jpg 100644 → 0
View
215
htb2trilium.py 100644 → 0
'''
########################
# SETUP
########################
> pip3 install trilium-py
> pip3 install pyhackthebox
 
- get trilium token in trilium options->ETAPI and "create new token"
- get trilium_htb_folder click on note to become folder -> "note info". Note ID is there.
- set the folder's owned attributes to:
#label:user=promoted,single,text #label:root=promoted,single,text #label:respect=promoted,single,text #user=0 #root=0 #respect=0
 
- create a note named "HTBMachineTemplate" and set it's "owned attributes" to:
#template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text
 
- get this page's ID and put in "trilium_template_id"
- to get "in progress" todo colour - add to taskmanager/implementation/CSS:
 
span.fancytree-node.inprogress .fancytree-title {
color: orange !important;
}
'''
 
########################
# IGNORE THESE
########################
 
import requests
from bs4 import BeautifulSoup
from datetime import timezone
from hackthebox import HTBClient
from trilium_py.client import ETAPI
 
########################
# EDIT BELOW HERE
########################
 
htb_email = ''
htb_pass = ''
trilium_server_url = ''
trilium_token = ''
trilium_htb_folder = ''
trilium_template_id = ''
 
########################
# LEAVE BELOW HERE
########################
 
def generate_newpage(machine):
no = "<span style=\"color:hsl(0,75%,60%);\">No</span>"
yes = "<span style=\"color:hsl(120,75%,60%);\">Yes</span>"
user_colour = no
if machine.user_owned:
user_colour = yes
root_colour = no
if machine.root_owned:
root_colour = yes
 
status = "Active"
if machine.retired:
status = "Retired"
 
html = """
<figure class="image image-style-align-left image_resized" style="width:12.28%;">
<img src="https://www.hackthebox.com{avatar}">
</figure>
<p>
<strong>OS:</strong> {os}<br>
<strong>Difficulty:</strong> {difficulty}<br>
<strong>Rating:</strong> {rating} / 5<br>
<strong>Points:</strong> {points}<br>
<strong>User / Root: </strong> {user_colour} / {root_colour}<br>
<strong>Released:</strong> {release_date}<br>
<strong>State:</strong> {status}
</p>
<hr>
<h2>Notes</h2>
<p>&nbsp;</p>
""".format(
os=machine.os,
difficulty=machine.difficulty,
rating=machine.stars,
points=machine.points,
release_date=machine.release_date.strftime("%d %B %Y"),
user_colour=user_colour,
root_colour=root_colour,
status = status,
avatar = machine.avatar,
)
 
return html
 
def get_timestamp(machine):
dt = machine.release_date
timestamp = dt.replace(tzinfo=timezone.utc).timestamp()
return timestamp
 
print("[+] connecting to HTB")
client = HTBClient(email=htb_email, password=htb_pass)
print("[+] connecting to trilium")
ea = ETAPI(trilium_server_url, trilium_token)
 
print("[i] HTB User:",client.user.id, "-", client.user.name)
print("[i] user owns:", client.user.user_owns, "| Root owns:", client.user.root_owns, "| Respect:", client.user.respects)
 
master_folder = ea.get_note(trilium_htb_folder)
for attribute in master_folder['attributes']:
if attribute['name'] == "user":
if attribute['value'] != str(client.user.user_owns):
print("[+] updating user owns (folder attribute)")
ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user.user_owns))
if attribute['name'] == "root":
if attribute['value'] != str(client.user.root_owns):
print("[+] updating root owns (folder attribute)")
ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user.root_owns))
if attribute['name'] == "respect":
if attribute['value'] != str(client.user.respects):
print("[+] updating respect (folder attribute)")
ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user.respects))
 
print("[+] gathering machines info")
 
machines = client.get_machines()
machines += client.get_machines(None, True)
machines.sort(key=get_timestamp)
# starts with oldest, ends with newest...
 
machine_count = 0
completed_count = 0
for machine in machines:
machine_count += 1
print('processing: ',machine_count, "/", len(machines), "("+machine.name+") " , end='\r')
res = ea.search_note(
search="\""+machine.name+"\"",
ancestorNoteId=trilium_htb_folder,
ancestorDepth='eq1',
limit=1,
fastSearch=True,
)
if res['results'] and machine.name == res['results'][0]['title']:
# page exists - lets check if the details have changed
 
current_html = ea.get_note_content(noteId=res['results'][0]['noteId'])
current_soup = BeautifulSoup(current_html, 'html.parser')
current_paragraph = current_soup.find_all('p')[0].text
 
new_html = generate_newpage(machine)
new_soup = BeautifulSoup(new_html, 'html.parser')
new_paragraph = new_soup.find_all('p')[0].text
 
# current page contains first paragraph of "blank" (useful for when it doesnt create or find the note properly.. shouldnt get here)
if current_paragraph == "blank":
ea.update_note_content(noteId=res['results'][0]['noteId'], content=new_html)
ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="relation", name="template", value=trilium_template_id)
if machine.user_owned and machine.root_owned:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="done")
else:
if machine.user_owned or machine.root_owned:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="inprogress")
else:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="todo")
# re-get the current content
current_html = ea.get_note_content(noteId=res['results'][0]['noteId'])
current_soup = BeautifulSoup(current_html, 'html.parser')
 
if current_paragraph != new_paragraph:
 
# details have updated!
print("[+] updating page:",machine.name, "-> "+res['results'][0]['title']+" ")
replacement = current_soup.find('p')
replacement.replace_with( new_soup.find_all('p')[0] )
ea.update_note_content(noteId=res['results'][0]['noteId'], content=current_soup)
 
# now to update the label
for attribute in res['results'][0]['attributes']:
if attribute['name'] == "cssClass":
if machine.user_owned and machine.root_owned:
ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
else:
if machine.user_owned or machine.root_owned:
ea.patch_attribute(attributeId=attribute['attributeId'], value="inprogress")
else:
ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
else:
# title does not exist - create the note
html = generate_newpage(machine)
new_note = ea.create_note(
parentNoteId=trilium_htb_folder,
type="text",
title=machine.name,
content=html,
)
print("[+] created note:", machine.name, " ")
 
ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_template_id)
if machine.user_owned and machine.root_owned:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
else:
if machine.user_owned or machine.root_owned:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="inprogress")
else:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
 
if machine.user_owned and machine.root_owned:
completed_count += 1
 
print("[+] updating folder name ")
ea.patch_note(noteId=trilium_htb_folder,title="Machines - "+str(completed_count)+" / "+str(len(machines)))
 
print("[=] processed", machine_count)
 
View
224
htb2trilium2.py 0 → 100644
'''
########################
# SETUP
########################
> pip3 install trilium-py
 
- get trilium token in trilium options->ETAPI and "create new token"
- get trilium_htb_folder click on note to become folder -> "note info". Note ID is there.
- set the folder's owned attributes to:
#label:user=promoted,single,text #label:root=promoted,single,text #label:respect=promoted,single,text #user=0 #root=0 #respect=0
 
- create a note named "HTBMachineTemplate" and set it's "owned attributes" to:
#template #label:User=promoted,single,text #label:Root=promoted,single,text #label:Tags=promoted,single,text
 
- get this page's ID and put in "trilium_template_id"
- to get "in progress" todo colour - add to trilium demo/scripting/taskmanager/implementation/CSS:
 
span.fancytree-node.inprogress .fancytree-title {
color: orange !important;
}
'''
 
########################
# IGNORE THESE
########################
 
import requests
from bs4 import BeautifulSoup
from datetime import datetime, timezone
from htb_client import HTBClient
from trilium_py.client import ETAPI
 
########################
# EDIT BELOW HERE
########################
 
htb_code = ''
trilium_server_url = 'https://notes.place.com'
trilium_token = ''
trilium_htb_folder = ''
trilium_template_id = ''
 
########################
# LEAVE BELOW HERE
########################
 
def generate_newpage(machine):
no = "<span style=\"color:hsl(0,75%,60%);\">No</span>"
yes = "<span style=\"color:hsl(120,75%,60%);\">Yes</span>"
user_colour = no
if machine['authUserInUserOwns']:
user_colour = yes
root_colour = no
if machine['authUserInRootOwns']:
root_colour = yes
 
status = "Retired"
if machine['active']:
status = "Active"
 
release_str = machine['release']
release_date = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
formatted_release_date = release_date.strftime("%d %B %Y")
 
html = """
<figure class="image image-style-align-left image_resized" style="width:12.28%;">
<img src="https://www.hackthebox.com{avatar}">
</figure>
<p>
<strong>OS:</strong> {os}<br>
<strong>Difficulty:</strong> {difficultyText} <br>
<strong>Rating:</strong> {rating} / 5<br>
<strong>Points:</strong> {points}<br>
<strong>User / Root: </strong> {user_colour} / {root_colour}<br>
<strong>Released:</strong> {release_date}<br>
<strong>State:</strong> {status}
</p>
<hr>
<h2>Notes</h2>
<p>&nbsp;</p>
""".format(
os=machine['os'],
difficultyText=machine['difficultyText'],
rating=machine['star'],
points=machine['points'],
release_date=formatted_release_date,
user_colour=user_colour,
root_colour=root_colour,
status = status,
avatar = machine['avatar'],
)
 
return html
 
def get_timestamp(machine):
# Parse the release date string into a datetime object
release_str = machine['release']
dt = datetime.strptime(release_str, "%Y-%m-%dT%H:%M:%S.%fZ")
# Set the timezone to UTC
dt = dt.replace(tzinfo=timezone.utc)
# Get the timestamp
timestamp = dt.timestamp()
return timestamp
 
print("[+] connecting to HTB")
client = HTBClient(password=htb_code)
print("[+] connecting to trilium")
ea = ETAPI(trilium_server_url, trilium_token)
print("[i] version: ", ea.app_info()['appVersion'])
 
print("[i] HTB User:", client.user['id'], "-", client.user['name'])
print("[i] user owns:", client.user['user_owns'], "| Root owns:", client.user['root_owns'], "| Respect:", client.user['respects'])
 
master_folder = ea.get_note(trilium_htb_folder)
for attribute in master_folder['attributes']:
if attribute['name'] == "user":
if attribute['value'] != str(client.user['user_owns']):
print("[+] updating user owns (folder attribute)")
ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['user_owns']))
if attribute['name'] == "root":
if attribute['value'] != str(client.user['root_owns']):
print("[+] updating root owns (folder attribute)")
ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['root_owns']))
if attribute['name'] == "respect":
if attribute['value'] != str(client.user['respects']):
print("[+] updating respect (folder attribute)")
ea.patch_attribute(attributeId=attribute['attributeId'], value=str(client.user['respects']))
 
print("[+] gathering machines info")
machines = client.get_all_machines()
machines.sort(key=get_timestamp)
 
print(f"[i] Retrieved {len(machines)} machines")
#for machine in machines:
# print(f" - ID: {machine['id']}, Name: {machine['name']}, OS: {machine['os']}, Difficulty: {machine['difficultyText']}")
 
machine_count = 0
completed_count = 0
for machine in machines:
machine_count += 1
print('processing: ',machine_count, "/", len(machines), "("+machine['name']+") " , end='\r')
 
res = ea.search_note(
search="\""+machine['name']+"\"",
ancestorNoteId=trilium_htb_folder,
ancestorDepth='eq1',
limit=1,
fastSearch=True,
)
 
if res['results'] and machine['name'] == res['results'][0]['title']:
# page exists - lets check if the details have changed
 
current_html = ea.get_note_content(noteId=res['results'][0]['noteId'])
current_soup = BeautifulSoup(current_html, 'html.parser')
current_paragraph = current_soup.find_all('p')[0].text
 
new_html = generate_newpage(machine)
new_soup = BeautifulSoup(new_html, 'html.parser')
new_paragraph = new_soup.find_all('p')[0].text
 
# current page contains first paragraph of "blank" (useful for when it doesnt create or find the note properly.. shouldnt get here)
if current_paragraph == "blank":
ea.update_note_content(noteId=res['results'][0]['noteId'], content=new_html)
ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="relation", name="template", value=trilium_template_id)
if machine['authUserInUserOwns'] and machine['authUserInRootOwns']:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="done")
else:
if machine['authUserInUserOwns'] or machine['authUserInRootOwns']:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="inprogress")
else:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=res['results'][0]['noteId'], type="label", name="cssClass", value="todo")
# re-get the current content
current_html = ea.get_note_content(noteId=res['results'][0]['noteId'])
current_soup = BeautifulSoup(current_html, 'html.parser')
 
if current_paragraph != new_paragraph:
 
# details have updated!
print("[+] updating page:",machine['name'], "-> "+res['results'][0]['title']+" ")
replacement = current_soup.find('p')
replacement.replace_with( new_soup.find_all('p')[0] )
ea.update_note_content(noteId=res['results'][0]['noteId'], content=current_soup)
 
# now to update the label
for attribute in res['results'][0]['attributes']:
if attribute['name'] == "cssClass":
if machine['authUserInUserOwns'] and machine['authUserInRootOwns']:
ea.patch_attribute(attributeId=attribute['attributeId'], value="done")
else:
if machine['authUserInUserOwns'] or machine['authUserInRootOwns']:
ea.patch_attribute(attributeId=attribute['attributeId'], value="inprogress")
else:
ea.patch_attribute(attributeId=attribute['attributeId'], value="todo")
else:
# title does not exist - create the note
html = generate_newpage(machine)
new_note = ea.create_note(
parentNoteId=trilium_htb_folder,
type="text",
title=machine['name'],
content=html,
)
print("[+] created note:", machine['name'], " ")
 
ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="relation", name="template", value=trilium_template_id)
if machine['authUserInUserOwns'] and machine['authUserInRootOwns']:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="done")
else:
if machine['authUserInUserOwns'] or machine['authUserInRootOwns']:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="inprogress")
else:
ea.create_attribute(attributeId=None, isInheritable=False, noteId=new_note['note']['noteId'], type="label", name="cssClass", value="todo")
 
if machine['authUserInUserOwns'] and machine['authUserInRootOwns']:
completed_count += 1
 
print("[+] updating folder name ")
ea.patch_note(noteId=trilium_htb_folder,title="Machines - "+str(completed_count)+" / "+str(len(machines)))
 
print("[=] processed", machine_count)
View
146
htb_client.py 0 → 100644
import requests
import warnings
 
warnings.filterwarnings("ignore")
 
class HTBClient:
def __init__(self, password):
self.password = password
self.base_url = 'https://labs.hackthebox.com/api/v4'
self.proxies = {
#"http": "http://127.0.0.1:8080", # Burp proxy for HTTP traffic
#"https": "http://127.0.0.1:8080" # Burp proxy for HTTPS traffic
}
# Fetch user info
self.user = self.get_user_info()
# Fetch user stats and store them in self.user
user_owns, root_owns, respects = self.get_user_stats(self.user['id'])
self.user['user_owns'] = user_owns
self.user['root_owns'] = root_owns
self.user['respects'] = respects
 
def get_user_info(self):
headers = {
"Authorization": f"Bearer {self.password}",
"User-Agent": None # Explicitly remove User-Agent
}
response = requests.get(
f'{self.base_url}/user/info',
headers=headers,
proxies=self.proxies,
verify=False # Disable SSL verification
)
if response.status_code != 200:
raise Exception(f"Error fetching {self.base_url}/user/info user info: {response.status_code}, {response.text}")
# Return the user info as a dictionary
data = response.json().get('info')
return {'id': data['id'], 'name': data['name'], 'email': data['email']}
 
def get_user_stats(self, user_id):
headers = {
"Authorization": f"Bearer {self.password}",
"User-Agent": None # Explicitly remove User-Agent
}
response = requests.get(
f'{self.base_url}/user/profile/basic/{user_id}',
headers=headers,
proxies=self.proxies,
verify=False # Disable SSL verification
)
if response.status_code != 200:
raise Exception(f"Error fetching user stats: {response.status_code}, {response.text}")
# Extract user statistics from the response
data = response.json().get('profile')
user_owns = data['user_owns']
root_owns = data['system_owns']
respects = data['respects']
return user_owns, root_owns, respects
 
def get_active_machines(self):
machines = []
seen_ids = set() # Track unique machine IDs
seen_names = set() # Track unique machine names
page = 1
 
while True:
response = requests.get(
f'{self.base_url}/machine/paginated?per_page=100&page={page}',
headers={
"Authorization": f"Bearer {self.password}",
"User-Agent": None # Explicitly remove User-Agent
},
proxies=self.proxies,
verify=False # Disable SSL verification
)
 
if response.status_code != 200:
raise Exception(f"Error fetching active machines: {response.status_code}, {response.text}")
 
data = response.json()
for machine in data['data']:
if machine['id'] not in seen_ids and machine['name'] not in seen_names:
machines.append(machine)
seen_ids.add(machine['id'])
seen_names.add(machine['name'])
 
# Check for pagination
if page >= data['meta']['last_page']:
break
page += 1
return machines
 
def get_retired_machines(self):
machines = []
seen_ids = set() # Track unique machine IDs
seen_names = set() # Track unique machine names
page = 1
 
while True:
response = requests.get(
f'{self.base_url}/machine/list/retired/paginated?per_page=100&page={page}',
headers={
"Authorization": f"Bearer {self.password}",
"User-Agent": None # Explicitly remove User-Agent
},
proxies=self.proxies,
verify=False # Disable SSL verification
)
 
if response.status_code != 200:
raise Exception(f"Error fetching retired machines: {response.status_code}, {response.text}")
 
data = response.json()
for machine in data['data']:
if machine['id'] not in seen_ids and machine['name'] not in seen_names:
machines.append(machine)
seen_ids.add(machine['id'])
seen_names.add(machine['name'])
 
# Check for pagination
if page >= data['meta']['last_page']:
break
page += 1
return machines
 
def get_all_machines(self):
# Combine active and retired machines, ensuring no duplicates
active_machines = self.get_active_machines()
retired_machines = self.get_retired_machines()
 
all_machines = active_machines + retired_machines
seen_ids = set() # Track unique machine IDs
seen_names = set() # Track unique machine names
unique_machines = []
 
for machine in all_machines:
if machine['id'] not in seen_ids and machine['name'] not in seen_names:
unique_machines.append(machine)
seen_ids.add(machine['id'])
seen_names.add(machine['name'])
 
return unique_machines
Buy Me A Coffee