Init. import

This commit is contained in:
Caplag
2025-12-22 05:19:38 +03:00
commit 39a4c5e8ca
58 changed files with 3063 additions and 0 deletions

433
GAME/osint/gen.py Normal file
View File

@@ -0,0 +1,433 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
forge_repo_staging.py — генерирует публичный репозиторий с 500+ коммитами.
Строка mesh: "OH-21B4" и base64-флаг встречаются РОВНО в одном коммите ветки
`staging/router-mesh`. В main их нет (ветка смёржена через --squash).
Можно сразу запушить: python3 forge_repo_staging.py --push <git-url>
Или задать автора: --author-name "Имя" --author-email you@example.com
"""
import os, sys, json, random, subprocess, shutil, argparse, base64, re
from datetime import datetime, timedelta, timezone
# -------- параметры по умолчанию --------
REPO_NAME = "iot-suite-repo-mega-fixed"
BRANCH_SECRET = "staging/router-mesh"
FEATURE_BRANCHES = [
("feature/ui-polish", 6),
("hotfix/diag-timeout", 3),
("refactor/router-cfg", 5),
]
MAIN_COMMITS = 520 # шумовых коммитов в main
TIME_START = datetime(2023, 3, 1, 10, 0, tzinfo=timezone(timedelta(hours=1)))
STEP_MAIN = timedelta(hours=6)
STEP_BRANCH = timedelta(hours=2)
AUTHORS_DEFAULT = [
("Natalie Orlov", "natalie@orbital.home"),
("Anton Lebedev", "anton@orbital.home"),
("Nina Petrova", "nina@orbital.home"),
("Mark Isaev", "mark@orbital.home"),
("QA Bot", "qa-bot@orbital.home"),
]
NOISE_MESSAGES = [
"docs: update changelog", "chore: reformat", "ci: bump node action",
"router: refactor logger", "kettle: tweak diag", "nas: session cleanup",
"printer: queue cosmetics", "deps: bump ejs", "deps: bump express",
"test: deflake", "build: docker cache", "perf: micro-opt",
"style: trailing space", "docs: badges",
]
FLAG_PLAINTEXT = "caplag{Orb1ta1-h0me-vendor-found-needed-branch-version}"
FLAG_B64 = base64.b64encode(FLAG_PLAINTEXT.encode()).decode()
def sh(args, cwd=None, env=None):
return subprocess.run(args, cwd=cwd, env=env, check=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True).stdout.strip()
def write(path, content, binary=False):
os.makedirs(os.path.dirname(path), exist_ok=True)
mode = "wb" if (binary or isinstance(content, (bytes, bytearray))) else "w"
with open(path, mode, encoding=None if mode=="wb" else "utf-8") as f:
f.write(content)
BASE_README = """# Orbital Home — IoT Suite (public)
Публичная версия кодовой базы для демонстраций: Router, Printer, Kettle, NAS.
Документация и закрытые части вырезаны.
"""
# ---------------- router (публичный минимальный) ----------------
ROUTER_SERVER = """import express from 'express';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = express();
const PORT = process.env.PORT || 8085;
const cfgPath = path.join(__dirname, 'config', 'router.json');
let cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
app.get('/info_connection', (req,res)=>{
res.json({ devices: [
{ name:'Printer', model:'PRN-3130', vendor: cfg.vendor, mesh: cfg.mesh, url: 'http://printer.local' },
{ name:'Kettle', model:'KTL-74C1', vendor: cfg.vendor, mesh: cfg.mesh, url: 'http://kettle.local' },
{ name:'NAS', model:'NAS-220', vendor: cfg.vendor, mesh: cfg.mesh, url: 'http://nas.local' }
]});
});
app.get('/', (req,res)=> res.type('text/plain').send('Router UI (public build)'));
app.listen(PORT, ()=> console.log(`[router-public] listening on ${PORT}`));
"""
ROUTER_PKG = {
"name":"router-public","version":"0.3.0","type":"module",
"scripts":{"start":"node server.js"},
"dependencies":{"express":"^4.19.2"}
}
# ---------------- printer (замаскированный) ----------------
PRINTER_SERVER_PUBLIC = """import express from 'express';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = express();
const PORT = process.env.PORT || 8080;
const QUEUE_DIR = path.join(__dirname, 'data', 'queue');
// <redacted>: private build may include additional endpoints and auth
app.get('/', (req,res)=> res.type('text/plain').send('Printer UI (public build)'));
app.get('/jobs/preview', (req,res)=>{
const name = String(req.query.file||'').replace(/\\\\/g,'/');
if (!name || name.includes('..') || path.isAbsolute(name)) return res.status(400).send('bad path');
const abs = path.join(QUEUE_DIR, path.normalize(name));
if (!abs.startsWith(QUEUE_DIR)) return res.status(400).send('bad path');
if (!fs.existsSync(abs)) return res.status(404).send('not found');
fs.createReadStream(abs).pipe(res);
});
app.get('/go', (req,res)=>{
const u = String(req.query.u||'');
if (/^https?:\\/\\//i.test(u)) return res.redirect(302,u);
res.redirect('/');
});
app.get('/robots.txt', (req,res)=> res.type('text/plain').send('User-agent: *\\nDisallow:\\n'));
app.listen(PORT, ()=> console.log(`[printer-public] listening on ${PORT}`));
"""
PRINTER_PKG_PUBLIC = {
"name":"printer-public","version":"0.3.0","type":"module",
"scripts":{"start":"node server.js"},
"dependencies":{"express":"^4.19.2"}
}
# ---------------- kettle (замаскированный) ----------------
KETTLE_SERVER_PUBLIC = """import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
// import axios from 'axios'; // <redacted>
// import crypto from 'crypto'; // <redacted>
// const PRINTER_PROOF = process.env.PRINTER_PROOF; // <redacted>
// const KETTLE_NONCE = process.env.KETTLE_NONCE; // <redacted>
// const KETTLE_SHARED_KEY = process.env.KETTLE_SHARED_KEY; // <redacted>
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = express();
const PORT = process.env.PORT || 8081;
app.get('/', (req,res)=> res.redirect('/diagnostics'));
app.get('/diagnostics', (req,res)=>{
// In private build this page exposes limited device info and triggers telemetry.
res.type('text/plain').send('Kettle diagnostics (public build)');
});
app.post('/diagnostics/fetch', (req,res)=>{
// <redacted>: route-signature-based fetch via internal gateway
// key = (PRINTER_PROOF + ":" + KETTLE_NONCE).encode()
//mac = hmac.new(key, (url + '\n' + ts).encode(), hashlib.sha256).hexdigest()
res.status(501).type('text/plain').send('disabled in public build');
});
app.get('/assets/badge.svg', (req,res)=>{
res.type('image/svg+xml').send('<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><circle cx="10" cy="10" r="7" fill="#5c7cfa"/></svg>');
});
app.listen(PORT, ()=> console.log(`[kettle-public] listening on ${PORT}`));
"""
KETTLE_PKG_PUBLIC = {
"name":"kettle-public","version":"0.3.0","type":"module",
"scripts":{"start":"node server.js"},
"dependencies":{"express":"^4.19.2"}
}
# ---------------- nas (замаскированный) ----------------
NAS_SERVER_PUBLIC = """import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
// import jwt from 'jsonwebtoken'; // <redacted>
// import crypto from 'crypto'; // <redacted>
// const JWT_SECRET = process.env.JWT_SECRET; // <redacted>
// const KETTLE_SHARED_KEY = process.env.KETTLE_SHARED_KEY; // <redacted>
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = express();
const PORT = process.env.PORT || 5000;
app.use(express.urlencoded({ extended:false }));
app.use(express.json());
app.get('/', (req,res)=> res.redirect('/login'));
app.get('/login', (req,res)=> res.type('text/plain').send('NAS login (public build)'));
app.post('/login', (req,res)=> res.status(501).send('disabled in public build'));
// admin_pass = hashlib.sha1((KETTLE_PROOF + ":" + PRINTER_PROOF).encode()).hexdigest()[:12]
app.get('/internal/hint', (req,res)=>{
// <redacted> internal gateway-only endpoint (requires shared key + route sign)
res.status(403).type('text/plain').send('forbidden (public build)');
});
app.get('/admin/flag', (req,res)=> res.status(404).send('not found'));
const auth = req.headers.authorization || '';
const token = auth.replace(/^Bearer\s+/,'');
try{
const payload = jwt.verify(token, JWT_SECRET, { algorithms:['HS256'] });
if (payload.role !== 'admin') return res.status(403).send('need admin role');
if (payload.kpf !== KETTLE_PROOF) return res.status(403).send('incorrect kpf');
if (payload.aud !== 'nas-admin') return res.status(403).send('bad aud');
const now = Math.floor(Date.now()/1000);
if (!payload.iat || Math.abs(now - payload.iat) > 120) return res.status(403).send('iat too old');
app.listen(PORT, ()=> console.log(`[nas-public] listening on ${PORT}`));
"""
NAS_PKG_PUBLIC = {
"name":"nas-public","version":"0.3.0","type":"module",
"scripts":{"start":"node server.js"},
"dependencies":{"express":"^4.19.2"}
}
def commit(root, message, when, author):
sh(["git","add","-A"], cwd=root)
env = os.environ.copy()
env.update({
"GIT_AUTHOR_NAME": author[0], "GIT_AUTHOR_EMAIL": author[1],
"GIT_COMMITTER_NAME": author[0], "GIT_COMMITTER_EMAIL": author[1],
"GIT_AUTHOR_DATE": when.isoformat(),
"GIT_COMMITTER_DATE": when.isoformat(),
})
sh(["git","commit","-m", message], cwd=root, env=env)
def set_mesh(root, value):
cfgp = os.path.join(root,"router/config/router.json")
with open(cfgp,"r+",encoding="utf-8") as f:
data = json.load(f)
data["mesh"] = value
f.seek(0); json.dump(data, f, indent=2); f.truncate()
def add_vendor_note(root, b64text):
p = os.path.join(root,"router/server.js")
with open(p,"a",encoding="utf-8") as f:
f.write(f"\n// vendor-note: {b64text}\n")
def remove_vendor_note(root):
p = os.path.join(root,"router/server.js")
with open(p,"r",encoding="utf-8") as f:
s = f.read()
s = re.sub(r"\n?//\s*vendor-note:[^\n]*\n?", "\n", s, flags=re.IGNORECASE)
with open(p,"w",encoding="utf-8") as f:
f.write(s)
def add_trailing_space_lines(path):
with open(path, "r", encoding="utf-8") as f:
lines = f.read().splitlines()
with open(path, "w", encoding="utf-8") as f:
for line in lines:
f.write(line.rstrip(" ") + " \n")
def strip_trailing_space_lines(path):
with open(path, "r", encoding="utf-8") as f:
lines = f.read().splitlines()
with open(path, "w", encoding="utf-8") as f:
for line in lines:
f.write(line.rstrip(" ") + "\n")
def snapshot_public_sources(root):
for rel in ("kettle/server.js", "nas/server.js"):
add_trailing_space_lines(os.path.join(root, rel))
def restore_public_sources(root):
for rel in ("kettle/server.js", "nas/server.js"):
strip_trailing_space_lines(os.path.join(root, rel))
def mutate_noise(root, i):
choice = random.choice(["docs","router","printer","kettle","nas"])
if choice == "docs":
p = os.path.join(root,"docs/CHANGELOG.md")
with open(p,"a",encoding="utf-8") as f:
f.write(f"- maintenance {i}\n")
elif choice in ("router","printer","kettle","nas"):
p = os.path.join(root, f"{choice}/server.js") if choice=="router" else os.path.join(root,f"{choice}/server.js")
# лёгкая косметика-комментарий
with open(p,"a",encoding="utf-8") as f:
f.write(f"\n// note: minor tweak {i}\n")
def init_repo(root, primary_author, authors):
if os.path.exists(root):
shutil.rmtree(root)
os.makedirs(root, exist_ok=True)
sh(["git","init","--initial-branch=main"], cwd=root)
write(os.path.join(root,"README.md"), BASE_README)
write(os.path.join(root,"docs/CHANGELOG.md"), "## Changelog (public)\n")
# router
write(os.path.join(root,"router/server.js"), ROUTER_SERVER)
write(os.path.join(root,"router/package.json"), json.dumps(ROUTER_PKG, indent=2, ensure_ascii=False))
write(os.path.join(root,"router/config/router.json"), json.dumps({"vendor":"Orbital Home","mesh":"OH-2194"}, indent=2))
# printer (публичный, безопасный)
write(os.path.join(root,"printer/server.js"), PRINTER_SERVER_PUBLIC)
write(os.path.join(root,"printer/package.json"), json.dumps(PRINTER_PKG_PUBLIC, indent=2, ensure_ascii=False))
write(os.path.join(root,"printer/data/queue/readme.txt"), "public job\n")
# kettle (публичный, урезанный)
write(os.path.join(root,"kettle/server.js"), KETTLE_SERVER_PUBLIC)
write(os.path.join(root,"kettle/package.json"), json.dumps(KETTLE_PKG_PUBLIC, indent=2, ensure_ascii=False))
# nas (публичный, урезанный)
write(os.path.join(root,"nas/server.js"), NAS_SERVER_PUBLIC)
write(os.path.join(root,"nas/package.json"), json.dumps(NAS_PKG_PUBLIC, indent=2, ensure_ascii=False))
commit(root, "init(public): scaffold modules", TIME_START, primary_author)
def build_main(root, authors):
t = TIME_START
for i in range(1, MAIN_COMMITS+1):
t = t + STEP_MAIN + timedelta(minutes=random.randint(0,59))
mutate_noise(root, i)
commit(root, random.choice(NOISE_MESSAGES), t, random.choice(authors))
return t
def merge_noff(root, branch_name, message, when, author):
sh(["git","checkout","main"], cwd=root)
sh(["git","merge","--no-ff", branch_name, "-m", message], cwd=root)
def build_feature_branch(root, name, commits, base_time, authors):
sh(["git","checkout","-b", name], cwd=root)
t = base_time
for i in range(commits):
t = t + STEP_BRANCH + timedelta(minutes=random.randint(0,40))
mutate_noise(root, 5000+i)
commit(root, f"{name}: routine update {i+1}", t, random.choice(authors))
merge_noff(root, name, f"merge: {name}", t + timedelta(minutes=5), random.choice(authors))
sh(["git","branch","-d", name], cwd=root)
def build_secret_branch(root, base_time, authors):
# ответвиться от текущего HEAD main
sh(["git","checkout","-b", BRANCH_SECRET], cwd=root)
t = base_time
# немного шума до секрета
for i in range(2):
t = t + STEP_BRANCH + timedelta(minutes=random.randint(0,30))
mutate_noise(root, 8000+i)
commit(root, random.choice(NOISE_MESSAGES), t, random.choice(authors))
# СЕКРЕТНЫЙ КОММИТ: OH-21B4 + base64-флаг как комментарий в router/server.js
t = t + STEP_BRANCH
set_mesh(root, "OH-21B4")
add_vendor_note(root, FLAG_B64)
snapshot_public_sources(root)
commit(root, "router: set mesh id in config (staging bench)", t, random.choice(authors))
# ещё шум
for i in range(2):
t = t + STEP_BRANCH
mutate_noise(root, 8100+i)
commit(root, random.choice(NOISE_MESSAGES), t, random.choice(authors))
# вернуть прод-значение и удалить комментарий
t = t + STEP_BRANCH
set_mesh(root, "OH-2194")
remove_vendor_note(root)
restore_public_sources(root)
commit(root, "router: revert mesh id to production", t, random.choice(authors))
# финальный шум на ветке
t = t + STEP_BRANCH
mutate_noise(root, 8200)
commit(root, "chore: staging cleanup", t, random.choice(authors))
# squash-merge в main (в main ни OH-21B4, ни vendor-note не попадут)
sh(["git","checkout","main"], cwd=root)
sh(["git","merge","--squash", BRANCH_SECRET], cwd=root)
t = t + STEP_BRANCH
commit(root, f"merge: {BRANCH_SECRET}", t, random.choice(authors))
# Ветку НЕ удаляем — пусть живёт
def tag_releases(root):
sh(["git","tag","-a","v0.2.0","-m","public release 0.2.0"], cwd=root)
sh(["git","tag","-a","v0.3.0","-m","public release 0.3.0"], cwd=root)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--author-name", default=None, help="Primary author name")
parser.add_argument("--author-email", default=None, help="Primary author email")
parser.add_argument("--push", default=None, help="Git remote URL to push (SSH or HTTPS)")
args = parser.parse_args()
primary = (args.author_name or "Your Name", args.author_email or "you@example.com")
authors = [primary] + [a for a in AUTHORS_DEFAULT if a[1] != primary[1]]
random.seed(42)
root = os.path.abspath(REPO_NAME)
init_repo(root, primary, authors)
t_end_main = build_main(root, authors)
# пара симпатичных merge-веток
for name, count in FEATURE_BRANCHES:
build_feature_branch(root, name, count, t_end_main, authors)
t_end_main = t_end_main + timedelta(hours=1)
# секретная ветка
build_secret_branch(root, t_end_main, authors)
tag_releases(root)
print(f"[ok] repo created: {root}")
print("\nПроверка локально:")
print(" cd", REPO_NAME)
print(" git log -S 'OH-21B4' --all --oneline")
print(" git grep -n 'OH-21B4' $(git rev-list --all)")
print(" git show $(git log -S 'OH-21B4' --all -n 1 --pretty=%H) | grep -i 'vendor-note'")
print("\nПуш в GitHub вручную:")
print(" git remote add origin <YOUR_GITHUB_REPO_URL>")
print(" git push -u origin --all --tags")
if args.push:
sh(["git","remote","add","origin", args.push], cwd=root)
sh(["git","push","-u","origin","--all"], cwd=root)
sh(["git","push","origin","--tags"], cwd=root)
print("[ok] pushed to:", args.push)
print("Проверь ветки/теги на GitHub UI.")
if __name__ == "__main__":
main()

92
GAME/osint/solver.py Normal file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import base64
import re
import subprocess
from pathlib import Path
def sh(args, cwd=None):
proc = subprocess.run(
args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
if proc.returncode != 0:
err = proc.stderr.strip() or proc.stdout.strip()
cmd = " ".join(args)
raise SystemExit(f"command failed: {cmd}\n{err}")
return proc.stdout.strip()
def default_clone_dir(repo_url, base_dir):
name = repo_url.rstrip("/").split("/")[-1]
if name.endswith(".git"):
name = name[:-4]
return base_dir / (name or "repo")
def commit_candidates(repo_dir):
patterns = ("vendor-note", "OH-21B4")
for pattern in patterns:
out = sh(["git", "log", "-S", pattern, "--all", "--pretty=%H"], cwd=repo_dir)
commits = [line.strip() for line in out.splitlines() if line.strip()]
if commits:
return commits
return []
def find_vendor_note(repo_dir):
for commit in commit_candidates(repo_dir):
try:
src = sh(["git", "show", f"{commit}:router/server.js"], cwd=repo_dir)
except subprocess.CalledProcessError:
continue
match = re.search(r"vendor-note:\s*([A-Za-z0-9+/=]+)", src)
if not match:
continue
b64_note = match.group(1)
flag = base64.b64decode(b64_note).decode("utf-8", "replace")
return commit, b64_note, flag
raise SystemExit("vendor-note not found in history")
def dump_sources(repo_dir, commit):
paths = [
"router/server.js",
"router/config/router.json",
"printer/server.js",
"kettle/server.js",
"nas/server.js",
]
for rel in paths:
content = sh(["git", "show", f"{commit}:{rel}"], cwd=repo_dir)
print(f"\n--- {rel} @ {commit} ---")
print(content.rstrip("\n"))
def main():
parser = argparse.ArgumentParser()
parser.add_argument("repo", help="Git URL or local path")
parser.add_argument("--dest", default=None, help="Clone directory (default: <script_dir>/<repo>)")
args = parser.parse_args()
base_dir = Path(__file__).resolve().parent
clone_dir = Path(args.dest).resolve() if args.dest else default_clone_dir(args.repo, base_dir)
if clone_dir.exists():
print(f"[i] using existing repo: {clone_dir}")
else:
print(f"[i] cloning into: {clone_dir}")
sh(["git", "clone", args.repo, str(clone_dir)])
sh(["git", "fetch", "--all", "--prune"], cwd=clone_dir)
commit, b64_note, flag = find_vendor_note(clone_dir)
print(f"[ok] clone_dir: {clone_dir}")
print(f"[ok] commit: {commit}")
print(f"[ok] vendor_note_b64: {b64_note}")
print(f"[ok] flag: {flag}")
dump_sources(clone_dir, commit)
if __name__ == "__main__":
main()

60
GAME/osint/writeup.md Normal file
View File

@@ -0,0 +1,60 @@
# Writeup: Orbital Home / OH-21B4
## Вводные из игры
Участники получили две зацепки: название производителя `Orbital Home` и параметр `mesh` со значением `OH-21B4` (в легенде мог быть написан как mash/mesh).
## Поиск репозитория
1) По ключу производителя:
- запросы: `"Orbital Home"`, `orbital.home`, `"Orbital Home" iot`.
- подсказка: в коммитах встречаются e-mailы вида `@orbital.home`, что хорошо ищется по GitHub.
2) По связке с mesh:
- запросы: `"OH-21B4" github`, `"mesh" "Orbital Home"`.
В результате находится публичный репозиторий с IoT-модулями (router/printer/kettle/nas). В README он описан как публичная сборка «Orbital Home IoT Suite».
## Что внутри репозитория
- `router/` — сервис роутера и `config/router.json` с полями `vendor` и `mesh`.
- `printer/`, `kettle/`, `nas/` — публичные версии сервисов (функции урезаны, часть эндпоинтов закомментирована).
- История содержит доп. ветку `staging/router-mesh`, где во время стендовых проверок меняли `mesh`.
## Как извлечь артефакт
1) Клонировать репозиторий и посмотреть все ветки:
```bash
git clone <repo-url>
cd <repo>
git branch -a
```
2) Поискать `OH-21B4` по истории всех веток:
```bash
git log --all -S "OH-21B4" --oneline
# или
git grep -n "OH-21B4" $(git rev-list --all)
```
3) Открыть найденный коммит:
```bash
git show <commit>:router/config/router.json
git show <commit>:router/server.js
```
В `router/server.js` обнаруживается строка вида:
```
// vendor-note: <base64>
```
4) Декодировать base64:
```bash
echo <base64> | base64 -d
```
Получаем флаг:
```
caplag{Orb1ta1-h0me-vendor-found-needed-branch-version}
```