Last active
November 13, 2021 18:30
-
-
Save jartigag/3984755268cc2780fa4fa8b588155267 to your computer and use it in GitHub Desktop.
hackmd2writeup.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# | |
# this script adapts a hackmd note **including images** to the Scavenger's writeup style: | |
# https://scavengersecurity.com/ | |
# usage and output: | |
# $ ./hackmd2writeup.py test.md | |
# OR | |
# $ ./hackmd2writeup.py hb5VSfHnQNKbdHbOCftmCA | |
# [+] __mandatory fields__: | |
# date: 2021-09-12 | |
# event: CSAW | |
# title: Gotta Decrypt Them All | |
# discipline: crypto | |
# author: jartigag, blast_o | |
# | |
# [?] the post 2021-09-12-csaw-gottadecryptthemall will be created. is this format correct? [Y/n] | |
# | |
# [+] writeup created in jekyll/_posts/2021-09-12-csaw-gottadecryptthemall/2021-09-12-csaw-gottadecryptthemall.md | |
# example: | |
# $ head hackmd_draft_note.md | |
# --- | |
# event: CSAW | |
# date: 2021-09-12 | |
# title: Gotta Decrypt Them All | |
# discipline: crypto | |
# difficulty: easy - 235 solves (175 points) | |
# tags: morse, base64, rsa, rot13 | |
# usemathjax: true | |
# author: jartigag, blast_o | |
# --- | |
# $ head jekyll/_posts/2021-09-12-csaw-gottadecryptthemall/2021-09-12-csaw-gottadecryptthemall.md | |
# --- | |
# author: jartigag, blast_o | |
# categories: ctf | |
# date: 2021-09-12 | |
# layout: post | |
# tags: | |
# - jartigag | |
# - blast_o | |
# - morse | |
# - base64 | |
# - rsa | |
# - rot13 | |
# title: CSAW - Gotta Decrypt Them All [Crypto] | |
# usemathjax: true | |
# --- | |
# additional setup instructions: | |
# | |
# ___ how to setup a hackmd template: | |
# $ cp default-hackmd-template.md $HACKMD_DIR/public/default.md | |
# | |
# ___ how to export from a hackmd note to a markdown file: | |
# | |
# # 1. get the cli: | |
# $ wget https://raw.githubusercontent.com/hedgedoc/cli/\ | |
# 8b13b8836cf330921856907d905421d34a1e645c/bin/hedgedoc -O hackmd && chmod +x hackmd | |
# | |
# # 2. set config via environment variables: | |
# $ export HEDGEDOC_SERVER='https://hackmd.scavengersecurity.com' | |
# | |
# # 3. export by note_id: | |
# $ ./hackmd export --md G2wBk0sAQQqqsO2jFZrgrA note.md | |
__author__ = "@jartigag" | |
__version__ = '1.4' | |
# CHANGELOG: | |
# v1.4 (20211102): | |
# - fix empty tags bug (by @00xc1) | |
# | |
# v1.3 (20211102): | |
# - post title standardization (by @00xc1) | |
# | |
# v1.2 (20211024): | |
# - ctrl+c signal handled | |
# - improvements in hackmd_cli error handling | |
# | |
# v1.1 (20211024): | |
# - export to md from note_id | |
import sys, os | |
from pathlib import Path | |
import shutil | |
import yaml | |
from datetime import datetime | |
import signal | |
HACKMD_UPLOAD_DIR = Path('/opt/docker/hackmd/upload-data/') | |
JEKYLL_DIR = Path('/var/jekyll/scavenger/') | |
HACKMD_CLI = '/home/jartigag/hackmd' | |
HEDGEDOC_SERVER="https://hackmd.scavengersecurity.com" | |
os.putenv('HEDGEDOC_SERVER',HEDGEDOC_SERVER) | |
def bold(text): return f"\033[1m{text}\033[0m" | |
def get_yaml_header(f): | |
"""extracted from https://stackoverflow.com/q/57168926""" | |
pointer = f.tell() | |
if f.readline() != '---\n': | |
f.seek(pointer) | |
return '' | |
readline = iter(f.readline, '') | |
readline = iter(readline.__next__, '---\n') | |
return ''.join(readline) | |
def make_it_a_list(d,k): | |
"""example: k='author' -> get 'author'/'authors' tag as a list, | |
being that a string or a list""" | |
author_list = d.get(k,d.get(f"{k}s",'')) | |
if isinstance(author_list,str): | |
return [author for author in author_list.split(", ") if author] | |
else: | |
return author_list | |
def draft2writeup_header(d): | |
return dict({ | |
'layout': 'post', | |
'title': f"{d['event']} - {d['title']} [{d['discipline'].capitalize()}]", | |
'categories': 'ctf', | |
'tags': make_it_a_list(d,'author')+make_it_a_list(d,'tag'), | |
'date': d['date'], | |
'author': ", ".join(make_it_a_list(d,'author')) | |
}, **{k: d[k] for k in d.keys()-['title', 'event','discipline','tag','tags','difficulty','author','authors']}) | |
#TODO: use 'difficulty' tag for something | |
def sigint_handler(signal, frame): | |
print("\n[!] ERROR: process cancelled") | |
sys.exit(0) | |
if __name__ == '__main__': | |
signal.signal(signal.SIGINT, sigint_handler) | |
if len(sys.argv)==2 or (len(sys.argv)==3 and sys.argv[2]=="-y"): | |
draft_file = sys.argv[1] | |
if not '.md' in draft_file: | |
# so draft_file is a note_id: | |
note_id = draft_file | |
os.environ['HEDGEDOC_SERVER'] = HEDGEDOC_SERVER | |
exit_code = os.system(f"{HACKMD_CLI} export --md {note_id} note.md 1>/dev/null") | |
if exit_code!=0: | |
sys.exit(f"[!] ERROR: 'export HEDGEDOC_SERVER={HEDGEDOC_SERVER}; {HACKMD_CLI} export {note_id} note.md") | |
if open('note.md').readlines()[0]=='<!DOCTYPE html>\n': | |
os.system('rm note.md') | |
sys.exit(f"[!] ERROR: '{draft_file}' is invalid as a file (must be .md) and as a note_id, since is not found in {HEDGEDOC_SERVER} ( {HEDGEDOC_SERVER}/{note_id} )") | |
print(f"[+] note_id {note_id} exported as note.md") | |
draft_file = 'note.md' | |
draft_header_str = get_yaml_header(open(draft_file)) | |
y = yaml.safe_load(draft_header_str) | |
# 0. CHECK MANDATORY FIELDS: date, event, title, discipline, author/authors | |
print('[+] __mandatory fields__:') | |
all_mandatory_fields = True | |
for f in ['date', 'event', 'title', 'discipline']: | |
print(f"{f}: {y.get(f,'')}") | |
if f not in y: | |
all_mandatory_fields = False | |
print(f"author: {', '.join(make_it_a_list(y,'author'))}") | |
if not make_it_a_list(y,'author'): | |
all_mandatory_fields = False | |
if not all_mandatory_fields: | |
sys.exit('[!] ERROR: mandatory field(s) missing') | |
# 1. GET DIR/POST NAME AND CREATE DIR: | |
lcase_event = y['event'].lower().replace(' ','') | |
lcase_title = y['title'].lower().replace(' ','') | |
post_dirname = f"{y['date']}-{lcase_event}-{lcase_title}" | |
if len(sys.argv)==2: | |
answ = input(f"\n[?] the post {bold(post_dirname)} will be created. is this format correct? [Y/n] ") | |
else: | |
if sys.argv[2]=="-y": | |
answ="y" | |
if answ=="": answ="y" | |
if answ[0].isnumeric() or answ[0].lower()!="y": | |
post_dirname = '' | |
while len(post_dirname.split('-'))!=5: | |
post_dirname = input("\n[?] please correct the post info, in YYY-MM-DD-event-title format: ") | |
y['date'] = datetime.strptime("-".join(post_dirname.split('-')[0:3]),"%Y-%m-%d").date() | |
y['event'] = post_dirname.split('-')[3] | |
y['title'] = post_dirname.split('-')[4] | |
for k in ('event','title'): | |
answ = input(f"\n[?] the {k} will be {bold(y[k])}. press intro if it's correct or rewrite it here: ") | |
if answ!="": y[k]=answ | |
lcase_event = y['event'].lower().replace(' ','') | |
lcase_title = y['title'].lower().replace(' ','') | |
post_dirname = f"{y['date']}-{lcase_event}-{lcase_title}" | |
post_dir = Path(JEKYLL_DIR,"_posts",post_dirname) | |
post_dir.mkdir(exist_ok=True) | |
# 2. GENERATE NEW YAML METADATA: | |
new_y = draft2writeup_header(y) | |
writeup_header_str = yaml.dump(new_y) | |
# 3. COPY PNGS AND DUMP THE WRITEUP CONTENT: | |
draft_str = open(draft_file).read() | |
for png in HACKMD_UPLOAD_DIR.iterdir(): | |
exit_code = os.system(f"grep {png.name} {draft_file}") | |
if exit_code==0: | |
shutil.copyfile(png,post_dir.joinpath(png.name)) | |
draft_str = draft_str.replace(f"/uploads/{png.name}",png.name) | |
writeup_str = draft_str.replace(draft_header_str, writeup_header_str) | |
with open(post_dir.joinpath(f"{post_dirname}.md"),'w') as writeup_file: | |
writeup_file.write(writeup_str) | |
print(f"\n[+] writeup created in {bold(post_dir.joinpath(post_dirname+'.md'))}") | |
else: | |
script_name = sys.argv[0].strip('./') | |
print(f"\n[!] usage: ./{script_name} draft_file.md") | |
print(f"OR ./{script_name} note_id") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment