$lifetime,'path'=>'/','secure'=>isset($_SERVER['HTTPS']),'httponly'=>true,'samesite'=>'Strict']); session_start(); // Security Headers if (SEND_SECURITY_HEADERS) { header("X-Content-Type-Options: nosniff"); header("X-Frame-Options: SAMEORIGIN"); header("Referrer-Policy: strict-origin-when-cross-origin"); header("Permissions-Policy: camera=(), microphone=(), geolocation=()"); header("Content-Security-Policy: default-src 'self'; script-src 'self' https://ctaas.de 'unsafe-inline'; style-src 'self' https://ctaas.de 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"); } // Session Handling & Timeout if (isset($_SESSION['user'])) { if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > $lifetime)) { session_destroy(); header('Location: ?'); exit; } $_SESSION['last_activity'] = time(); setcookie(session_name(), session_id(), time() + $lifetime, '/', '', isset($_SERVER['HTTPS']), true); } if (!isset($_SESSION['created'])) $_SESSION['created'] = time(); if (isset($_SESSION['created']) && (time() - $_SESSION['created'] > SESSION_TIMEOUT_MIN * 60)) { session_regenerate_id(true); $_SESSION['created'] = time(); } if (empty($_SESSION['token'])) $_SESSION['token'] = bin2hex(random_bytes(32)); // HTMLPurifier Integration require_once __DIR__ . '/data/htmlpurifier/HTMLPurifier.standalone.php'; $purifierConfig = HTMLPurifier_Config::createDefault(); $purifierConfig->set('HTML.Allowed', 'a[href|title|target],b,i,strong,em,span[style],br,p[style],ul,ol,li[style],h1[style],h2[style],h3[style],h4[style],h5[style],h6[style],hr,img[src|alt],div[style],table[style],thead,tbody,tr,th[style],td[style],style,u,pre,code'); $purifierConfig->set('CSS.AllowedProperties', 'color,background-color,font-family,font-size,text-align'); $cacheDir = __DIR__ . '/data/temp/purifier_cache'; if (!is_dir($cacheDir)) mkdir($cacheDir, 0755, true); $purifierConfig->set('Cache.SerializerPath', $cacheDir); $purifierConfig->set('HTML.TargetBlank', true); $purifier = new HTMLPurifier($purifierConfig); // Pfade & Helfer define('PAGE_DIR', __DIR__ . '/data/pages/'); define('INDEX_FILE', __DIR__ . '/data/index.json'); define('DUMMY_HASH', '$2y$10$dummyhashdummyhashdummyhashdummyhashdummyha'); define('COMPRESS_LEVEL', 9); // User-Array aus PHP-Datei laden $USERS = []; $u_file = __DIR__ . '/data/my_users.php'; if (is_file($u_file)) { require $u_file; $tmp = []; foreach ($USERS as $k => $v) $tmp[strtolower($k)] = $v; $USERS = $tmp; } function user() { return $_SESSION['user'] ?? null; } function role() { return $_SESSION['role'] ?? null; } function is_admin() { return strtolower(role() ?? '') === 'admin'; } function is_logged() { return user() !== null; } function h($s) { return htmlspecialchars($s ?? '', ENT_QUOTES, 'UTF-8'); } // --- TOTP Verifikation & E-Mail Helfer --- function verify_totp($secret, $code, $window = 2) { $code = preg_replace('/\D/', '', $code); if (strlen($code) !== 6) return false; $base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; $secret = strtoupper(preg_replace('/[^A-Z2-7]/', '', $secret)); $secret_bin = ''; $buffer = 0; $bitsLeft = 0; for ($i = 0; $i < strlen($secret); $i++) { $buffer = ($buffer << 5) | strpos($base32chars, $secret[$i]); $bitsLeft += 5; if ($bitsLeft >= 8) { $bitsLeft -= 8; $secret_bin .= chr($buffer >> $bitsLeft); } } if (empty($secret_bin)) return false; $time = floor(time() / 30); for ($offset = -$window; $offset <= $window; $offset++) { $time_bytes = pack('N', 0) . pack('N', $time + $offset); $hmac = hash_hmac('sha1', $time_bytes, $secret_bin, true); $offset_val = ord($hmac[19]) & 0x0F; $otp_int = (((ord($hmac[$offset_val]) & 0x7F) << 24) | (ord($hmac[$offset_val+1]) << 16) | (ord($hmac[$offset_val+2]) << 8) | ord($hmac[$offset_val+3])) % 1000000; if (hash_equals(str_pad($otp_int, 6, '0', STR_PAD_LEFT), $code)) return true; } return false; } function send_tfa_mail($u, $c) { if(!is_file(__DIR__.'/data/PHPMailer/PHPMailer.php')) die('Systemfehler: 2FA-Modul fehlt.'); require __DIR__.'/data/PHPMailer/my_PHPMailer_config.php'; require_once __DIR__.'/data/PHPMailer/Exception.php'; require_once __DIR__.'/data/PHPMailer/PHPMailer.php'; require_once __DIR__.'/data/PHPMailer/SMTP.php'; $m = new PHPMailer\PHPMailer\PHPMailer(true); try { $m->isSMTP(); $m->Host = $MAIL_CONF['host']; $m->SMTPAuth = $MAIL_CONF['auth']; $m->Username = $MAIL_CONF['user']; $m->Password = $MAIL_CONF['pass']; $m->SMTPSecure = $MAIL_CONF['smtpsure']; $m->Port = $MAIL_CONF['port']; $m->setFrom($MAIL_CONF['from'], $MAIL_CONF['fromName']); foreach(explode(';', $u['emails']) as $em) $m->addAddress(trim($em)); $m->Subject = 'Dein CMS Login-Code'; $m->Body = "Dein Sicherheitscode lautet: $c\nDieser Code ist ".(TFA_TIMEOUT_SEC/60)." Minuten gueltig."; $m->send(); } catch (Exception $e) { die('Fehler beim Mailversand!'); } } function update_index() { $idx = []; if (!is_dir(PAGE_DIR)) return; foreach (new DirectoryIterator(PAGE_DIR) as $f) { if ($f->isFile() && $f->getExtension() === 'txt') { [$m] = load_page($f->getPathname()); if ($m) $idx[] = ['path' => $f->getBasename('.txt'), 'title' => $m['title'] ?? $f->getBasename('.txt'), 'pinned' => (int)($m['pinned'] ?? 0), 'reader' => (int)($m['reader'] ?? 0)]; } } $tmp = INDEX_FILE . '.tmp'; if (file_put_contents($tmp, json_encode($idx, JSON_PRETTY_PRINT))) rename($tmp, INDEX_FILE); } function navigation() { $p = is_file(INDEX_FILE) ? json_decode(file_get_contents(INDEX_FILE), true) : null; if (!is_array($p) || (!empty($p) && !is_file(PAGE_DIR . $p[0]['path'] . '.txt'))) { update_index(); $p = json_decode(file_get_contents(INDEX_FILE), true) ?: []; } if (!is_logged()) $p = array_filter($p, fn($i) => (int)($i['reader'] ?? 0) === 0); usort($p, function($a, $b) { if ($a['path'] === '_start') return -1; if ($b['path'] === '_start') return 1; return ($b['pinned'] ?? 0) <=> ($a['pinned'] ?? 0) ?: strnatcasecmp($a['title'], $b['title']); }); return $p; } function slug_from_title($title) { $map = ['ä'=>'ae','ö'=>'oe','ü'=>'ue','Ä'=>'Ae','Ö'=>'Oe','Ü'=>'Ue','ß'=>'ss']; return trim(preg_replace('/_+/', '_', preg_replace('/[^a-zA-Z0-9]/', '_', strtr($title, $map))), '_'); } function generate_filename($title) { $ts = date('ymdHis') . sprintf('%03d', floor(microtime(true) * 1000) % 1000); $slug = slug_from_title($title); $f = $ts . ($slug !== '' ? '_' . $slug : '') . '.txt'; return (strlen($f) <= 64) ? $f : $ts . '_' . substr(str_replace('_', '', $slug), 0, 43) . '.txt'; } // Logik & Routing $page = preg_replace('/[^a-zA-Z0-9_]/', '', $_GET['page'] ?? '_start') ?: '_start'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!isset($_POST['token']) || $_POST['token'] !== $_SESSION['token']) die('Token ungültig!'); // Manueller 2FA-E-Mail Versand (Resend-Button) if (isset($_POST['resend_email']) && isset($_SESSION['tfa_pending']['email_code'])) { send_tfa_mail($_SESSION['tfa_pending']['user'], $_SESSION['tfa_pending']['email_code']); $_SESSION['tfa_pending']['ts'] = time(); header('Location: ?login&2fa&sent=1'); exit; } // 2FA Überprüfungs-Event if (isset($_POST['tfa_check'])) { $tmp = __DIR__ . '/data/temp/'; if (!is_dir($tmp)) mkdir($tmp, 0755, true); $ipf = $tmp . 'limit_tfa_' . hash('sha256', $_SERVER['REMOTE_ADDR'] ?? 'unknown') . '.json'; $ld = is_file($ipf) ? json_decode(file_get_contents($ipf), true) : ['c' => 0, 'l' => 0]; $dly = LOGIN_DELAY_SECONDS[min($ld['c'], count(LOGIN_DELAY_SECONDS) - 1)]; if ($dly > 0 && (time() - $ld['l'] < $dly)) die("Warten!"); $pending = $_SESSION['tfa_pending'] ?? null; if ($pending && (time() - $pending['ts'] < TFA_TIMEOUT_SEC)) { $input_code = trim($_POST['code'] ?? ''); $verified = false; if (!empty($pending['totp_secret']) && verify_totp($pending['totp_secret'], $input_code)) { $verified = true; } elseif (!empty($pending['email_code']) && hash_equals($pending['email_code'], $input_code)) { $verified = true; } if ($verified) { if (is_file($ipf)) @unlink($ipf); $u = $pending['user']; unset($_SESSION['tfa_pending']); session_regenerate_id(true); $_SESSION['user'] = $u['name']; $_SESSION['role'] = $u['role']; $_SESSION['last_activity'] = time(); header('Location: ?page=' . $page); exit; } } $ld['c']++; $ld['l'] = time(); file_put_contents($ipf, json_encode($ld)); sleep(2); die('Code falsch oder abgelaufen! Zurück zum Login'); } if (isset($_POST['login'])) { $tmp = __DIR__ . '/data/temp/'; if (!is_dir($tmp)) mkdir($tmp, 0755, true); $ipf = $tmp . 'limit_' . hash('sha256', $_SERVER['REMOTE_ADDR'] ?? 'unknown') . '.json'; $ld = is_file($ipf) ? json_decode(file_get_contents($ipf), true) : ['c' => 0, 'l' => 0]; $dly = LOGIN_DELAY_SECONDS[min($ld['c'], count(LOGIN_DELAY_SECONDS) - 1)]; if ($dly > 0 && (time() - $ld['l'] < $dly)) die("Warten!"); $u_input = trim($_POST['user'] ?? ''); $u_lower = strtolower($u_input); $p = $_POST['pass'] ?? ''; $hash = $USERS[$u_lower]['hash'] ?? DUMMY_HASH; if (password_verify($p, $hash) && isset($USERS[$u_lower])) { if (is_file($ipf)) @unlink($ipf); $user_data = $USERS[$u_lower]; $has_totp = !empty($user_data['totp_secret']); $has_email = !empty($user_data['emails']); if ($has_totp || $has_email) { $email_code = ''; if ($has_email) { $email_code = (string)random_int(100000, 999999); if (!$has_totp) send_tfa_mail($user_data, $email_code); } $_SESSION['tfa_pending'] = ['user' => $user_data, 'ts' => time(), 'totp_secret' => $has_totp ? $user_data['totp_secret'] : '', 'email_code' => $email_code]; header('Location: ?login&2fa'); exit; } session_regenerate_id(true); $_SESSION['user'] = $user_data['name']; $_SESSION['role'] = $user_data['role']; $_SESSION['last_activity'] = time(); header('Location: ?page=' . $page); exit; } $ld['c']++; $ld['l'] = time(); file_put_contents($ipf, json_encode($ld)); usleep(500000); header('Location: ?login&error=1'); exit; } if (isset($_POST['delete']) && is_admin() && $page !== '_start') { @unlink(PAGE_DIR . $page . '.txt'); update_index(); header('Location: ?'); exit; } if (isset($_POST['save']) && is_admin()) { $title = trim($_POST['title'] ?? ''); $old_n = preg_replace('/[^a-zA-Z0-9_ -]/', '', $_POST['old_page_name'] ?? ''); $new_f = ($old_n === '_start') ? '_start.txt' : generate_filename($title); if ($old_n !== '_new' && $old_n !== '_start' && $old_n . '.txt' !== $new_f && is_file(PAGE_DIR . $old_n . '.txt')) rename(PAGE_DIR . $old_n . '.txt', PAGE_DIR . $new_f); $m = ['title' => $title ?: basename($new_f, '.txt'), 'reader' => isset($_POST['reader']) ? 1 : 0, 'gzbase64' => isset($_POST['gzbase64']) ? 1 : 0, 'pinned' => isset($_POST['pinned']) ? 1 : 0, 'webcrypto' => isset($_POST['webcrypto']) ? 1 : 0, 'crypto_meta' => $_POST['crypto_meta'] ?? '', 'lastedit' => date('Y-m-d H:i') . '/' . user()]; if (empty($m['webcrypto'])) $m['crypto_meta'] = ''; if (basename($new_f, '.txt') === '_start') { $m['reader'] = 0; $m['pinned'] = 0; $m['webcrypto'] = 0; } if ($m['webcrypto'] && $m['gzbase64']) $m['gzbase64'] = 0; $cleanContent = $purifier->purify($_POST['content']); save_page(PAGE_DIR . $new_f, $m, $cleanContent); update_index(); header("Location: ?page=" . basename($new_f, '.txt') . "&edit"); exit; } } if (isset($_GET['logout'])) { session_destroy(); setcookie(session_name(), '', time() - 3600, '/'); header('Location: ?'); exit; } function load_page($f) { if (!is_file($f)) return [null, null]; $fp = fopen($f, 'rb'); flock($fp, LOCK_SH); $r = stream_get_contents($fp); flock($fp, LOCK_UN); fclose($fp); preg_match('/---META---(.*?)---DATA---/s', $r, $m); $meta = []; foreach (explode("\n", trim($m[1] ?? '')) as $l) if (str_contains($l, '=')) { [$k, $v] = explode('=', $l, 2); $meta[trim($k)] = trim($v); } $d = substr($r, strpos($r, '---DATA---') + 10); if (($meta['gzbase64'] ?? 0) == 1) $d = @gzuncompress(base64_decode($d)); return [$meta, $d]; } function save_page($f, $m, $d) { if (($m['gzbase64'] ?? 0) == 1) $d = base64_encode(gzcompress($d, COMPRESS_LEVEL)); $content = "---META---\n"; foreach ($m as $k => $v) $content .= "$k=$v\n"; $content .= "---DATA---\n" . $d; $fp = fopen($f, 'wb'); flock($fp, LOCK_EX); fwrite($fp, $content); flock($fp, LOCK_UN); fclose($fp); return true; } $new = isset($_GET['new']) && is_admin(); $edit = isset($_GET['edit']) && is_admin(); if ($new) { $meta = ['title' => '', 'reader' => 0, 'pinned' => 0, 'gzbase64' => 0, 'webcrypto' => 0, 'crypto_meta' => '']; $content = ''; } else { [$meta, $content] = load_page(PAGE_DIR . $page . '.txt'); if (!$meta && !isset($_GET['login']) && !isset($_GET['search'])) { http_response_code(404); echo '404404 Error – Seite nicht gefunden!
zurück'; exit; } if (isset($meta['title']) && $meta['title'] === $page) $meta['title'] = ''; } $searchData = []; if (isset($_GET['search'])) { $pages = navigation(); foreach ($pages as $p) { $file = PAGE_DIR . $p['path'] . '.txt'; if (!is_file($file)) continue; [$m, $d] = load_page($file); if (!$m) continue; $item = ['path' => $p['path'], 'title' => $m['title'] ?? $p['title'] ?? $p['path'], 'lastedit' => $m['lastedit'] ?? '', 'gzbase64' => isset($m['gzbase64']) ? (int)$m['gzbase64'] : 0, 'webcrypto' => isset($m['webcrypto']) ? (int)$m['webcrypto'] : 0, 'crypto_meta' => $m['crypto_meta'] ?? '']; $item['data'] = $d; $searchData[] = $item; } } ?> <?= h($meta['title'] ?? 'CMS') ?>

🛡️ WebCrypto Schutz

Passwort festlegen:




🛡️ Schutz-Stufe: (> k)

2FA Bestätigung

E-Mail wurde gesendet!

'; ?>



zurück zum Passwortlogin

Login





🛡️ WebCrypto-verschlüsselt


Falsch!

Volltextsuche

Verschlüsselte Datei gefunden:



Seitentitel ↕Letzte Änderung ↕

Gib einen Suchbegriff ein, um Seiten zu durchsuchen.

🔒 Privat.
-