mirror of
https://github.com/NohamR/LetCTF.git
synced 2026-05-25 19:59:23 +00:00
imaginaryctf support
This commit is contained in:
12
main.py
12
main.py
@@ -3,6 +3,7 @@ from src.platforms.theblackside import TheBlackSidePlatform
|
|||||||
from src.platforms.crackmes import CrackmesPlatform
|
from src.platforms.crackmes import CrackmesPlatform
|
||||||
from src.platforms.crackmy import CrackmyPlatform
|
from src.platforms.crackmy import CrackmyPlatform
|
||||||
from src.platforms.cattheflag import CatTheFlagPlatform
|
from src.platforms.cattheflag import CatTheFlagPlatform
|
||||||
|
from src.platforms.imaginaryctf import ImaginaryCTFPlatform
|
||||||
from src.generator import WriteupGenerator
|
from src.generator import WriteupGenerator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -52,9 +53,20 @@ def cattheflag():
|
|||||||
print(generator.challenges)
|
print(generator.challenges)
|
||||||
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
||||||
|
|
||||||
|
def imaginaryctf():
|
||||||
|
challenge_name = "Prime Cuts"
|
||||||
|
challenge_url = challenge_name.lower().replace(' ', '-')
|
||||||
|
platform = ImaginaryCTFPlatform()
|
||||||
|
|
||||||
|
generator = WriteupGenerator(platform, Path("./writeups"))
|
||||||
|
generator.fetch_challenges()
|
||||||
|
generator.fetch_challenge(challenge_url=challenge_url)
|
||||||
|
print(generator.challenges)
|
||||||
|
generator.generate_writeup_structure(hugo_header=True, translated=True)
|
||||||
|
|
||||||
# theblackside()
|
# theblackside()
|
||||||
# hackropole()
|
# hackropole()
|
||||||
# crackmes()
|
# crackmes()
|
||||||
# crackmy()
|
# crackmy()
|
||||||
# cattheflag()
|
# cattheflag()
|
||||||
|
# imaginaryctf()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List, Dict
|
from typing import List, Dict, Optional
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Challenge:
|
class Challenge:
|
||||||
@@ -10,13 +10,13 @@ class Challenge:
|
|||||||
author: str
|
author: str
|
||||||
category: str
|
category: str
|
||||||
description: str
|
description: str
|
||||||
difficulty: str
|
|
||||||
points: int
|
|
||||||
files: List[str]
|
files: List[str]
|
||||||
|
difficulty: Optional[int] = None
|
||||||
|
points: Optional[int] = None
|
||||||
additional_info: Dict = None
|
additional_info: Dict = None
|
||||||
template: str = None
|
template: str = None
|
||||||
template_translated: str = None
|
template_translated: str = None
|
||||||
solved_number: int = 0
|
solved_number: Optional[int] = 0
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class File:
|
class File:
|
||||||
|
|||||||
263
src/platforms/imaginaryctf.py
Normal file
263
src/platforms/imaginaryctf.py
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
from typing import List, Dict
|
||||||
|
from pathlib import Path
|
||||||
|
from .base import CTFPlatform
|
||||||
|
from ..models import Challenge, File
|
||||||
|
from ..utils.config_handler import load_config
|
||||||
|
from ..utils.challenge_handler import download_files
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from datetime import datetime
|
||||||
|
import textwrap
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
|
||||||
|
class ImaginaryCTFPlatform(CTFPlatform):
|
||||||
|
def __init__(self, url: str = "https://imaginaryctf.org/"):
|
||||||
|
super().__init__(url)
|
||||||
|
self.url = url
|
||||||
|
self.headers = {
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||||
|
'Accept-Language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
'Sec-Fetch-Dest': 'document',
|
||||||
|
'Sec-Fetch-Mode': 'navigate',
|
||||||
|
'Sec-Fetch-Site': 'none',
|
||||||
|
'Sec-Fetch-User': '?1',
|
||||||
|
'Upgrade-Insecure-Requests': '1',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
|
||||||
|
'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-platform': '"macOS"',
|
||||||
|
}
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Not implementing login since we don't need it :D"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_challenges(self) -> List[Challenge]:
|
||||||
|
"""Get all challenges from platform"""
|
||||||
|
try:
|
||||||
|
response = self.session.get('https://imaginaryctf.org/Challenges', headers=self.headers)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(f"Error fetching challenges: {response.status_code}")
|
||||||
|
except requests.RequestException as e:
|
||||||
|
raise Exception(f"Error fetching challenges: {e}")
|
||||||
|
|
||||||
|
response.encoding = "utf-8"
|
||||||
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
challenges = {}
|
||||||
|
|
||||||
|
for category_header in soup.find_all('h3', class_='text-start'):
|
||||||
|
category = category_header.text.strip()
|
||||||
|
|
||||||
|
current_element = category_header.find_next('div', class_='card challenge')
|
||||||
|
|
||||||
|
while current_element and not current_element.find_parent('h3'):
|
||||||
|
header = current_element.find('div', class_='challenge-header')
|
||||||
|
if not header:
|
||||||
|
continue
|
||||||
|
|
||||||
|
header_text = header.text.strip()
|
||||||
|
name = header_text.split('(')[0].strip()
|
||||||
|
points = int(header_text.split('(')[1].split('pts')[0].strip())
|
||||||
|
|
||||||
|
modal_id = current_element.find('a')['data-bs-target'].replace('#', '')
|
||||||
|
modal = soup.find('div', id=modal_id)
|
||||||
|
|
||||||
|
if modal:
|
||||||
|
modal_title = modal.find('h5', class_='modal-title')
|
||||||
|
author = modal_title.find('small', class_='text-muted').text.replace('by', '').strip()
|
||||||
|
solve_count = int(modal_title.find('span').text.split('solves')[0].strip('- '))
|
||||||
|
|
||||||
|
description = modal.find('p').text.strip()
|
||||||
|
|
||||||
|
files = []
|
||||||
|
attachments_section = modal.find('b', text='Attachments')
|
||||||
|
if attachments_section:
|
||||||
|
files = [a['href'] for a in attachments_section.find_next('p').find_all('a')]
|
||||||
|
|
||||||
|
challenge_id = name.lower().replace(' ', '-')
|
||||||
|
|
||||||
|
challenge = Challenge(
|
||||||
|
id=challenge_id,
|
||||||
|
url='https://imaginaryctf.org/Challenges',
|
||||||
|
platform="ImaginaryCTF",
|
||||||
|
name=name,
|
||||||
|
author=author,
|
||||||
|
category=category,
|
||||||
|
description=description,
|
||||||
|
points=points,
|
||||||
|
files=files,
|
||||||
|
solved_number=solve_count
|
||||||
|
)
|
||||||
|
|
||||||
|
challenges[challenge_id] = challenge
|
||||||
|
|
||||||
|
current_element = current_element.find_next('div', class_='card challenge')
|
||||||
|
|
||||||
|
self.challenges = challenges
|
||||||
|
|
||||||
|
def resolve_challenge_files(self, file_url: Challenge):
|
||||||
|
"""Resolve file URL to get direct download link it"""
|
||||||
|
print(f"Resolving file URL: {file_url}")
|
||||||
|
api_url = "https://cybersharing.net/api/containers/" + file_url.split('/')[-1]
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/json, text/plain, */*',
|
||||||
|
'Accept-Language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Origin': 'https://cybersharing.net',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
'Sec-Fetch-Dest': 'empty',
|
||||||
|
'Sec-Fetch-Mode': 'cors',
|
||||||
|
'Sec-Fetch-Site': 'same-origin',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
|
||||||
|
'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-platform': '"macOS"',
|
||||||
|
}
|
||||||
|
data = {"password":None}
|
||||||
|
response = self.session.post(api_url, headers=headers, json=data)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(f"Error fetching file: {response.status_code}")
|
||||||
|
response = response.json()
|
||||||
|
folder_id = response['id']
|
||||||
|
signature = response['signature']
|
||||||
|
files = []
|
||||||
|
for upload in response['uploads']:
|
||||||
|
name = upload['fileName']
|
||||||
|
file_url = f"https://cybersharing.net/api/download/file/{folder_id}/{upload['id']}/{signature}/{name}"
|
||||||
|
hash = None
|
||||||
|
file = File(name=name, url=file_url, hash=hash)
|
||||||
|
files.append(file)
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
def get_challenge(self, challenge_url: str) -> Challenge:
|
||||||
|
"""Get a specific challenge by URL"""
|
||||||
|
|
||||||
|
challenge = self.challenges.get(challenge_url)
|
||||||
|
not_resolved_files = challenge.files
|
||||||
|
allfiles = []
|
||||||
|
for file_url in not_resolved_files:
|
||||||
|
files = self.resolve_challenge_files(file_url)
|
||||||
|
allfiles = allfiles + files
|
||||||
|
|
||||||
|
challenge.files = allfiles
|
||||||
|
return challenge
|
||||||
|
|
||||||
|
def download_challenge_files(self, challenge: Challenge, output_dir: Path):
|
||||||
|
download_files(self, challenge, output_dir)
|
||||||
|
|
||||||
|
def generate_tags(self, challenge):
|
||||||
|
tags = []
|
||||||
|
tags.append(challenge.category)
|
||||||
|
tags.append("ImaginaryCTF")
|
||||||
|
tags = list(set(filter(None, tags)))
|
||||||
|
tags_str = '", "'.join(tags)
|
||||||
|
return tags_str
|
||||||
|
|
||||||
|
def generate_template(self, challenge: Challenge, hugo_header: bool = False, translated: bool = False):
|
||||||
|
"""Generate writeup template for challenge"""
|
||||||
|
|
||||||
|
tags_str = self.generate_tags(challenge)
|
||||||
|
|
||||||
|
hugo_header_template = textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
---
|
||||||
|
title: "{challenge.name}"
|
||||||
|
date: "{datetime.now().isoformat()}"
|
||||||
|
tags: ["{tags_str}"]
|
||||||
|
author: "Noham"
|
||||||
|
summary: "Writeup for {challenge.name} from {challenge.platform}. A {challenge.points} points {challenge.category.lower()} challenge with {challenge.solved_number} solves."
|
||||||
|
showToc: false
|
||||||
|
TocOpen: false
|
||||||
|
draft: false
|
||||||
|
hidemeta: false
|
||||||
|
comments: true
|
||||||
|
disableHLJS: false
|
||||||
|
disableShare: false
|
||||||
|
hideSummary: false
|
||||||
|
searchHidden: false
|
||||||
|
ShowReadingTime: true
|
||||||
|
ShowBreadCrumbs: true
|
||||||
|
searchHidden: true
|
||||||
|
ShowPostNavLinks: true
|
||||||
|
ShowWordCount: true
|
||||||
|
ShowRssButtonInSectionTermList: true
|
||||||
|
UseHugoToc: true
|
||||||
|
---
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
hugo_header_template_fr = textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
---
|
||||||
|
title: "{challenge.name}"
|
||||||
|
date: "{datetime.now().isoformat()}"
|
||||||
|
tags: ["{tags_str}"]
|
||||||
|
author: "Noham"
|
||||||
|
summary: "Writeup du challenge {challenge.name} de {challenge.platform}. Un challenge de {challenge.category.lower()} de {challenge.points} points avec {challenge.solved_number} résolutions."
|
||||||
|
showToc: false
|
||||||
|
TocOpen: false
|
||||||
|
draft: false
|
||||||
|
hidemeta: false
|
||||||
|
comments: true
|
||||||
|
disableHLJS: false
|
||||||
|
disableShare: false
|
||||||
|
hideSummary: false
|
||||||
|
searchHidden: false
|
||||||
|
ShowReadingTime: true
|
||||||
|
ShowBreadCrumbs: true
|
||||||
|
searchHidden: true
|
||||||
|
ShowPostNavLinks: true
|
||||||
|
ShowWordCount: true
|
||||||
|
ShowRssButtonInSectionTermList: true
|
||||||
|
UseHugoToc: true
|
||||||
|
---
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
stars = "⭐" * min(5, max(1, round(challenge.points / 40)))
|
||||||
|
|
||||||
|
files_section = ""
|
||||||
|
for file in challenge.files:
|
||||||
|
hash_text = f" *(SHA256: {file.hash})*" if file.hash else ""
|
||||||
|
files_section += f"[{file.name}]({file.url}){hash_text}, "
|
||||||
|
files_section = files_section.rstrip(", ")
|
||||||
|
|
||||||
|
main_content = textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
- Challenge URL: [{challenge.name} - {challenge.platform}]({challenge.url})
|
||||||
|
- Author: {challenge.author}
|
||||||
|
- Category: {challenge.category}
|
||||||
|
- Challenge description: {challenge.description}
|
||||||
|
- Difficulty: {stars} ({challenge.points} points, {challenge.solved_number} solves)
|
||||||
|
- Files provided: {files_section}
|
||||||
|
|
||||||
|
## Writeup
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
main_content_fr = textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
- URL du challenge: [{challenge.name} - {challenge.platform}]({challenge.url})
|
||||||
|
- Auteur: {challenge.author}
|
||||||
|
- Catégorie: {challenge.category}
|
||||||
|
- Description du challenge: {challenge.description}
|
||||||
|
- Difficulté: {stars} ({challenge.points} points, {challenge.solved_number} résolutions)
|
||||||
|
- Fichiers fournis: {files_section}
|
||||||
|
|
||||||
|
## Writeup
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
if hugo_header:
|
||||||
|
challenge.template = hugo_header_template + main_content
|
||||||
|
if translated:
|
||||||
|
challenge.template_translated = hugo_header_template_fr + main_content_fr
|
||||||
|
else:
|
||||||
|
challenge.template = main_content
|
||||||
|
if translated:
|
||||||
|
challenge.template_translated = main_content_fr
|
||||||
Reference in New Issue
Block a user