Seite 5 von 8

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 08:55
von Volker
Ich werde den mal testweise raus nehmen und mir mal Gedanken machen warum das nicht auf jedem Server klappt. Aber eigentlich war der auch schon die der Version drin, die bei dir lief :confused:

Ist echt nicht einfach, zumal ich hier bei mir auf dem Server keinerlei Probleme mit den Formularen habe.

Melde mich dann wieder...

Wäre Klasse wenn ein paar andere User hier das auch mal testen könnten.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 09:00
von Volker
Was sagen denn deine error logs ? Kannst du auf die zugreifen ?

Mal mit einem anderen browser testen
Browser-Konsole prüfen:
Network-Tab: Schauen welche Requests fehlschlagen


Ich hab mit allen gängigen Browsern bis auf firerfox getestet. Chrome, Opera, Edge

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 09:04
von Tommy Herrmann
ChatGPT bestätig meinen Verdacht. Das Cookie wird nie gesetzt - also kann das doch auch nicht funktionieren:

ChatGPT hat geschrieben:Hi Tommy! 👋

Der Fehler kommt von hier:

Code: Alles auswählen

if (!isset($_POST["csrf_token"]) || $_POST["csrf_token"] !== ($_SESSION["csrf_token"] ?? null)) {
    http_response_code(400);
    echo json_encode(["success" => false, "message" => "Sicherheitsfehler: Ungültiges Token."]);
    exit;
}

Du prüfst ein CSRF-Token – setzt es aber nirgends. Damit ist jede Anfrage „ungültig“ und der Ablauf endet, bevor überhaupt irgendetwas (inkl. CAPTCHA) geprüft oder erzeugt wird. Zusätzlich wird ein CAPTCHA-Code erst nach einem Fehlversuch generiert – beim ersten Laden existiert also keiner. Beides zusammen erklärt Dein Verhalten.

Unten findest Du kompakte Fixes, die Du 1:1 übernehmen kannst.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 09:08
von Volker
OK und warum geht das bei mir ?

https://www.niederastroth.de/formgenera ... /index.php

https://www.niederastroth.de/test123/
cookie.png
Ich glaube eher es liegt an deinem Server das da keine gesetzt werden
cookie tommy.png
Warum bei mir und bei dir nicht ? Frag das mal die KI

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 09:16
von Tommy Herrmann
Volker hat geschrieben: Fr 22. Aug 2025, 09:08 Ich glaube eher es liegt an deinem Server das da keine gesetzt werden

... nee :D das hat doch ChatGPT sogleich erkannt, das Token wird nie gespeichert. Wie es bei Dir gespeichert wurde weiß ich nicht. Das liegt natürlich nicht an meinem Server, denn nach der Überarbeitung geht es ja jetzt.


Hier von ChatGPT das überarbeitete Script "send.php" (ich teste es nachher). Nein, das schließende ?> PHP-Tag fehlt hier nicht, das schreibt man heute nicht mehr an reine PHP-Skripte:

Code: Alles auswählen

<?php
/**
 * send.php – Kontaktformular-Handler (AJAX/POST)
 * Voraussetzungen:
 *   - In der Formularseite: session_start(), $_SESSION['csrf_token'] setzen, Hidden-Field "csrf_token" mitsenden
 *   - CAPTCHA-Bild über captcha.php einbinden, das $_SESSION['captcha_code'] setzt
 *   - PHP-GD für captcha.php, Schreibrechte für $uploads_dir
 */

declare(strict_types=1);
session_start();

// Immer JSON antworten
header('Content-Type: application/json; charset=UTF-8');

// ------------------------------------------------------------
// PHPMailer laden – EINE Variante verwenden (Composer ODER manuell)
// ------------------------------------------------------------

// Variante A: via Composer
// require_once __DIR__ . '/vendor/autoload.php';

// Variante B: manuell
require_once __DIR__ . '/PHPMailer/src/Exception.php';
require_once __DIR__ . '/PHPMailer/src/PHPMailer.php';
require_once __DIR__ . '/PHPMailer/src/SMTP.php';

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

// ------------------------------------------------------------
// KONFIGURATION
// ------------------------------------------------------------

$recipient_email    = 'mail@example.com';
$default_from_email = 'no-reply@' . ($_SERVER['SERVER_NAME'] ?? 'localhost'); // sinnvoller Default (DMARC)
$sender_name        = 'Kontaktformular';
$success_message    = 'Vielen Dank für Ihre Nachricht!';

// SMTP (optional) – ausfüllen, wenn SMTP genutzt werden soll:
$smtp_host       = '';   // z.B. smtp.gmail.com
$smtp_port       = 587;  // 587 für TLS, 465 für SSL
$smtp_secure     = 'tls';// 'tls' oder 'ssl'
$smtp_user       = '';   // SMTP-Benutzername
$smtp_pass       = '';   // SMTP-Passwort/App-Passwort
$smtp_from_name  = 'Kontaktformular';

// Automatische Antwort (optional)
$enable_auto_reply           = false;
$auto_reply_subject          = 'Ihre Nachricht wurde erhalten';
$auto_reply_message          = "Hallo,\n\nvielen Dank für Ihre Nachricht. Wir melden uns schnellstmöglich.\n\nMit freundlichen Grüßen\nIhr Team";
$auto_reply_include_original = true;
$auto_reply_html_format      = false;
$auto_reply_sender_name      = 'Support-Team';

// Uploads
$allowed_extensions = ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx'];
$max_filesize_mb    = 5;
$uploads_dir        = __DIR__ . '/uploads/';

// Pflichtfelder (Namen wie im Formular)
$required_fields = ['name', 'email', 'Betreff', 'Nachricht', 'Datenschutz']; // case-sensitiv auf deine Felder anpassen

// ------------------------------------------------------------
// HILFSFUNKTIONEN
// ------------------------------------------------------------

function json_fail(int $status, string $message, array $extra = []): never {
    http_response_code($status);
    echo json_encode(array_merge(['success' => false, 'message' => $message], $extra), JSON_UNESCAPED_UNICODE);
    exit;
}

function json_ok(string $message, array $extra = []): never {
    echo json_encode(array_merge(['success' => true, 'message' => $message], $extra), JSON_UNESCAPED_UNICODE);
    exit;
}

function sanitize_input(string $data): string {
    $data = trim($data);
    $data = stripslashes($data);
    return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}

function validateSmtpConfig(): bool {
    global $smtp_host, $smtp_user, $smtp_pass, $smtp_port, $smtp_secure;

    if (empty($smtp_host) || empty($smtp_user) || empty($smtp_pass)) {
        return false;
    }
    if (!is_int($smtp_port) && !ctype_digit((string)$smtp_port)) {
        return false;
    }
    $p = (int)$smtp_port;
    if ($p < 1 || $p > 65535) {
        return false;
    }
    if (!in_array($smtp_secure, ['tls', 'ssl'], true)) {
        return false;
    }
    return true;
}

function configurePHPMailer(PHPMailer $mail, string $fallback_from_email, string $from_name): string {
    global $smtp_host, $smtp_user, $smtp_pass, $smtp_port, $smtp_secure, $smtp_from_name;

    try {
        if (validateSmtpConfig()) {
            $mail->isSMTP();
            $mail->Host       = $smtp_host;
            $mail->SMTPAuth   = true;
            $mail->Username   = $smtp_user;
            $mail->Password   = $smtp_pass;
            $mail->Timeout    = 30;
            $mail->SMTPKeepAlive = false;

            if ($smtp_secure === 'ssl') {
                $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
                $mail->Port       = (int)($smtp_port ?: 465);
            } else {
                $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
                $mail->Port       = (int)($smtp_port ?: 587);
            }

            // Optional während Entwicklung:
            // $mail->SMTPDebug = SMTP::DEBUG_SERVER;

            // SSL-Optionen (je nach Umgebung anpassen)
            $mail->SMTPOptions = [
                'ssl' => [
                    'verify_peer'       => true,
                    'verify_peer_name'  => true,
                    'allow_self_signed' => false,
                ],
            ];

            $mail->setFrom($smtp_user, $smtp_from_name ?: $from_name);
            return 'smtp';
        }

        // Fallback: PHP mail()
        $mail->isMail();
        $mail->setFrom($fallback_from_email, $from_name);
        return 'mail';

    } catch (Exception $e) {
        error_log('SMTP Configuration Error: ' . $e->getMessage());
        $mail->isMail();
        $mail->setFrom($fallback_from_email, $from_name);
        return 'fallback';
    }
}

// ------------------------------------------------------------
// REQUEST-PRÜFUNG
// ------------------------------------------------------------

if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    json_fail(403, 'Direkter Zugriff nicht erlaubt.');
}

// CSRF
if (empty($_POST['csrf_token']) || empty($_SESSION['csrf_token']) || !hash_equals((string)$_SESSION['csrf_token'], (string)$_POST['csrf_token'])) {
    json_fail(400, 'Sicherheitsfehler: Ungültiges Token.');
}

// Honeypot (unsichtbares Feld "name_hp")
if (!empty($_POST['name_hp'])) {
    json_fail(400, 'Honeypot-Feld ausgefüllt, dies ist ein Spam-Versuch.');
}

// Mind. 3 Sekunden zwischen Formularstart und Absenden
$start = (int)($_SESSION['form_start_time'] ?? 0);
if ($start === 0 || (time() - $start) < 3) {
    json_fail(400, 'Formular zu schnell ausgefüllt.');
}

// ------------------------------------------------------------
// FELDER VALIDIEREN
// ------------------------------------------------------------

$errors = [];

// Pflichtfelder vorhanden?
foreach ($required_fields as $fname) {
    $hasUpload = isset($_FILES[$fname]) && ($_FILES[$fname]['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK;
    $val       = trim((string)($_POST[$fname] ?? ''));
    if ($val === '' && !$hasUpload) {
        $errors[] = $fname;
    }
}

// E-Mail validieren (suche das erste Feld, das wie "email" heißt)
$email_field_name = '';
foreach ($_POST as $key => $value) {
    $k = strtolower((string)$key);
    if (str_contains($k, 'email') || str_contains($k, 'mail')) {
        $email_field_name = $key;
        break;
    }
}
$sender_email = '';
if ($email_field_name !== '') {
    $raw = (string)($_POST[$email_field_name] ?? '');
    $san = filter_var(sanitize_input($raw), FILTER_SANITIZE_EMAIL);
    if (!filter_var($san, FILTER_VALIDATE_EMAIL)) {
        $errors[] = $email_field_name;
    } else {
        $sender_email = $san;
    }
}

// CAPTCHA prüfen (Code wurde durch captcha.php in $_SESSION gesetzt)
$postedCaptcha  = strtoupper(trim((string)($_POST['captcha'] ?? '')));
$sessionCaptcha = strtoupper((string)($_SESSION['captcha_code'] ?? ''));
if ($postedCaptcha === '' || $sessionCaptcha === '' || $postedCaptcha !== $sessionCaptcha) {
    $errors[] = 'captcha';

    // defensiv: neuen Code generieren (falls captcha.php nicht neu geladen wird)
    $characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    $new = '';
    for ($i = 0; $i < 5; $i++) {
        $new .= $characters[random_int(0, strlen($characters) - 1)];
    }
    $_SESSION['captcha_code'] = $new;
}

if (!empty($errors)) {
    json_fail(400, 'Bitte füllen Sie alle Pflichtfelder korrekt aus.', ['errors' => $errors]);
}

// ------------------------------------------------------------
// UPLOADS
// ------------------------------------------------------------

$uploaded_files = [];
if (!is_dir($uploads_dir)) {
    if (!@mkdir($uploads_dir, 0775, true) && !is_dir($uploads_dir)) {
        json_fail(500, 'Upload-Ordner konnte nicht erstellt werden.');
    }
}
if (!is_writable($uploads_dir)) {
    json_fail(500, 'Upload-Ordner nicht beschreibbar.');
}

if (!empty($_FILES)) {
    foreach ($_FILES as $field_name => $file) {
        if (!is_array($file) || !isset($file['error'])) {
            continue;
        }

        // Mehrfach-Uploads (name[], tmp_name[] ...) abfangen
        $isMulti = is_array($file['name']);

        $names = $isMulti ? $file['name'] : [$file['name']];
        $tmps  = $isMulti ? $file['tmp_name'] : [$file['tmp_name']];
        $errs  = $isMulti ? $file['error'] : [$file['error']];
        $sizes = $isMulti ? $file['size'] : [$file['size']];

        foreach ($names as $idx => $name) {
            $err = (int)($errs[$idx] ?? UPLOAD_ERR_NO_FILE);
            if ($err === UPLOAD_ERR_NO_FILE) {
                continue;
            }
            if ($err !== UPLOAD_ERR_OK) {
                json_fail(400, 'Fehler beim Hochladen der Datei.');
            }

            $orig_name = sanitize_input(basename((string)$name));
            $tmp_path  = (string)($tmps[$idx] ?? '');
            $size      = (int)($sizes[$idx] ?? 0);

            $ext = strtolower(pathinfo($orig_name, PATHINFO_EXTENSION));
            if (!in_array($ext, $allowed_extensions, true)) {
                json_fail(400, "Fehler: Dateityp .$ext ist nicht erlaubt.");
            }
            if ($size > $max_filesize_mb * 1024 * 1024) {
                json_fail(400, "Fehler: Maximale Dateigröße von {$max_filesize_mb}MB überschritten.");
            }

            $unique = uniqid('', true) . '-' . $orig_name;
            $dest   = rtrim($uploads_dir, '/\\') . DIRECTORY_SEPARATOR . $unique;

            if (!@move_uploaded_file($tmp_path, $dest)) {
                json_fail(500, 'Fehler beim Speichern der hochgeladenen Datei.');
            }

            $uploaded_files[] = [
                'name' => $orig_name,
                'path' => $dest,
                'size' => $size,
            ];
        }
    }
}

// ------------------------------------------------------------
// MAIL-INHALTE AUFBAUEN
// ------------------------------------------------------------

// Betreff automatisch aus den ersten zwei *sichtbaren* Werten
$skip_keys       = ['csrf_token', 'name_hp', 'captcha', 'send_copy_to_sender'];
$visible_values  = [];
foreach ($_POST as $key => $value) {
    if (in_array($key, $skip_keys, true)) continue;
    if (is_array($value)) continue;
    $val = trim((string)$value);
    if ($val === '') continue;
    $visible_values[] = $val;
}
$subject = 'Neue Nachricht aus dem Formular';
if (!empty($visible_values)) {
    $subject = 'Nachricht von ' . sanitize_input($visible_values[0]);
    if (!empty($visible_values[1]) && $visible_values[1] !== $visible_values[0]) {
        $subject .= ' (' . sanitize_input($visible_values[1]) . ')';
    }
}

// HTML/Text
$mail_content_html = '<div style="font-family:Arial,sans-serif;max-width:600px;margin:0 auto;">'
    . '<h2 style="color:#333;border-bottom:2px solid #007bff;padding-bottom:10px;">Neue Nachricht aus dem Formular</h2>'
    . '<p>Hallo,</p><p>Sie haben eine neue Nachricht aus dem Kontaktformular erhalten:</p>'
    . '<table style="border-collapse:collapse;width:100%;border:1px solid #ddd;margin:20px 0;"><tbody>';

$mail_content_text = "Neue Nachricht aus dem Formular:\n\n";

foreach ($_POST as $key => $value) {
    if (in_array($key, $skip_keys, true)) continue;

    $label = ucfirst(str_replace(['_', '-'], ' ', (string)$key));
    if (is_array($value)) {
        $value_str  = implode(', ', array_map('htmlspecialchars', array_map('strval', $value)));
        $value_text = implode(', ', array_map('strval', $value));
    } else {
        $value_str  = sanitize_input((string)$value);
        $value_text = (string)$value;
    }

    $mail_content_html .= '<tr>'
        . '<td style="padding:12px;border:1px solid #ddd;background:#f8f9fa;width:30%;font-weight:bold;vertical-align:top;">' . $label . '</td>'
        . '<td style="padding:12px;border:1px solid #ddd;vertical-align:top;">' . nl2br($value_str) . '</td>'
        . '</tr>';

    $mail_content_text .= $label . ': ' . $value_text . "\n";
}

$mail_content_html .= '</tbody></table>';
if (!empty($uploaded_files)) {
    foreach ($uploaded_files as $f) {
        $kb = number_format($f['size'] / 1024, 1, ',', '.');
        $mail_content_html .= '<p style="margin-top:10px;"><strong>Anhang:</strong> ' . htmlspecialchars($f['name']) . ' (' . $kb . ' KB)</p>';
        $mail_content_text .= "\nAnhang: " . $f['name'] . " ({$kb} KB)\n";
    }
}
$mail_content_html .= '</div>';

// Kopie an Absender?
$send_copy_to_sender = isset($_POST['send_copy_to_sender']) && (string)$_POST['send_copy_to_sender'] === '1';

// ------------------------------------------------------------
// HAUPTMAIL SENDEN
// ------------------------------------------------------------

$main_mail_sent  = false;
$mail_method_used = '';

try {
    $mail = new PHPMailer(true);
    $mail_method_used = configurePHPMailer($mail, $default_from_email, $sender_name);

    $mail->addAddress($recipient_email);

    if ($sender_email !== '') {
        $mail->addReplyTo($sender_email);
    }

    foreach ($uploaded_files as $f) {
        if (is_file($f['path'])) {
            $mail->addAttachment($f['path'], $f['name']);
        }
    }

    $mail->isHTML(true);
    $mail->Subject = $subject;
    $mail->Body    = $mail_content_html;
    $mail->AltBody = strip_tags($mail_content_text);
    $mail->CharSet = 'UTF-8';
    $mail->Encoding = 'base64';

    // Retry-Mechanismus
    $max_retries = 2;
    $attempt     = 0;
    do {
        try {
            $mail->send();
            $main_mail_sent = true;
            break;
        } catch (Exception $ex) {
            $attempt++;
            if ($attempt > $max_retries) {
                throw $ex;
            }
            sleep(1);
        }
    } while ($attempt <= $max_retries);

} catch (Exception $e) {
    $msg = 'Fehler beim Senden der E-Mail';
    $em  = $e->getMessage();
    if (stripos($em, 'SMTP') !== false) {
        if (stripos($em, 'Authentication') !== false) {
            $msg = 'SMTP-Authentifizierung fehlgeschlagen. Bitte prüfen Sie Benutzername und Passwort.';
        } elseif (stripos($em, 'connect') !== false) {
            $msg = 'Verbindung zum SMTP-Server fehlgeschlagen. Bitte Host und Port prüfen.';
        } elseif (stripos($em, 'SSL') !== false || stripos($em, 'TLS') !== false) {
            $msg = 'SSL/TLS-Verschlüsselungsfehler. Bitte Verschlüsselungseinstellungen prüfen.';
        } else {
            $msg = 'SMTP-Fehler: ' . $em;
        }
    }
    error_log("Mail sending failed (Method: {$mail_method_used}): " . $em);
    json_fail(500, $msg);
}

// ------------------------------------------------------------
// KOPIE AN ABSENDER (optional)
// ------------------------------------------------------------

if ($main_mail_sent && $send_copy_to_sender && $sender_email !== '') {
    try {
        $copy_mail = new PHPMailer(true);
        configurePHPMailer($copy_mail, $default_from_email, $sender_name);

        $copy_mail->addAddress($sender_email);

        foreach ($uploaded_files as $f) {
            if (is_file($f['path'])) {
                $copy_mail->addAttachment($f['path'], $f['name']);
            }
        }

        $copy_subject = 'Kopie Ihrer Nachricht: ' . $subject;

        $copy_html = '<div style="font-family:Arial,sans-serif;max-width:600px;margin:0 auto;">'
            . '<h2 style="color:#333;border-bottom:2px solid #007bff;padding-bottom:10px;">Kopie Ihrer gesendeten Nachricht</h2>'
            . '<p>Hallo,</p><p>vielen Dank für Ihre Nachricht. Hier ist eine Kopie der von Ihnen gesendeten Daten:</p>'
            . '<table style="border-collapse:collapse;width:100%;border:1px solid #ddd;margin:20px 0;"><tbody>';
        $copy_txt = "Kopie Ihrer gesendeten Nachricht:\n\n";

        foreach ($_POST as $key => $value) {
            if (in_array($key, $skip_keys, true)) continue;
            $label = ucfirst(str_replace(['_', '-'], ' ', (string)$key));
            if (is_array($value)) {
                $value_str  = implode(', ', array_map('htmlspecialchars', array_map('strval', $value)));
                $value_text = implode(', ', array_map('strval', $value));
            } else {
                $value_str  = sanitize_input((string)$value);
                $value_text = (string)$value;
            }
            $copy_html .= '<tr>'
                . '<td style="padding:12px;border:1px solid #ddd;background:#f8f9fa;width:30%;font-weight:bold;vertical-align:top;">' . $label . '</td>'
                . '<td style="padding:12px;border:1px solid #ddd;vertical-align:top;">' . nl2br($value_str) . '</td>'
                . '</tr>';
            $copy_txt .= $label . ': ' . $value_text . "\n";
        }
        $copy_html .= '</tbody></table>'
            . '<p style="margin-top:30px;color:#666;font-size:.9em;border-top:1px solid #ddd;padding-top:20px;">'
            . 'Diese E-Mail wurde automatisch generiert, da Sie eine Kopie Ihrer Nachricht angefordert haben.'
            . '</p></div>';

        $copy_mail->isHTML(true);
        $copy_mail->Subject = $copy_subject;
        $copy_mail->Body    = $copy_html;
        $copy_mail->AltBody = strip_tags($copy_txt);
        $copy_mail->CharSet = 'UTF-8';
        $copy_mail->Encoding = 'base64';

        $copy_mail->send();

    } catch (Exception $e) {
        error_log('Copy Mail Error: ' . $e->getMessage());
        // kein Abbruch
    }
}

// ------------------------------------------------------------
// AUTO-REPLY (optional)
// ------------------------------------------------------------

if ($main_mail_sent && $enable_auto_reply && $sender_email !== '') {
    try {
        $auto_mail = new PHPMailer(true);
        configurePHPMailer($auto_mail, $default_from_email, $auto_reply_sender_name);

        $auto_mail->addAddress($sender_email);
        $auto_mail->isHTML($auto_reply_html_format);
        $auto_mail->Subject = $auto_reply_subject;

        $auto_body_txt  = $auto_reply_message;
        $auto_body_html = nl2br(htmlspecialchars($auto_reply_message, ENT_QUOTES, 'UTF-8'));

        if ($auto_reply_include_original) {
            $auto_body_txt .= "\n\n--------------------------------------------------\nIhre ursprüngliche Nachricht:\n\n";
            $auto_body_html .= '<hr style="margin:30px 0;"><h3>Ihre ursprüngliche Nachricht:</h3>';

            foreach ($_POST as $key => $value) {
                if (in_array($key, $skip_keys, true)) continue;
                $label = ucfirst(str_replace(['_', '-'], ' ', (string)$key));
                $display = is_array($value) ? implode(', ', array_map('strval', $value)) : (string)$value;

                $auto_body_txt  .= $label . ': ' . $display . "\n";
                $auto_body_html .= '<p><strong>' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . ':</strong> ' . htmlspecialchars($display, ENT_QUOTES, 'UTF-8') . '</p>';
            }
        }

        $auto_mail->Body    = $auto_body_html;
        $auto_mail->AltBody = $auto_body_txt;
        $auto_mail->CharSet = 'UTF-8';
        $auto_mail->Encoding = 'base64';

        $auto_mail->send();

    } catch (Exception $e) {
        error_log('Auto-Reply Error: ' . $e->getMessage());
    }
}

// ------------------------------------------------------------
// AUFRÄUMEN & ANTWORT
// ------------------------------------------------------------

// Nach erfolgreicher Übermittlung CAPTCHA verwerfen – neues Bild erforderlich
unset($_SESSION['captcha_code']);

json_ok($success_message);



Wichtig für die Einbindung:

In Deiner Formularseite vor dem <form>:

Code: Alles auswählen

<?php
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$_SESSION['form_start_time'] = time();
?>

Im Formular:

Code: Alles auswählen

<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES) ?>">
<img src="/captcha.php?_=<?= time() ?>" alt="CAPTCHA" width="160" height="50">
<input type="text" name="captcha" required autocomplete="off">


Bei fetch/AJAX: credentials: 'same-origin' setzen, damit die Session (und damit CSRF/CAPTCHA) zusammenpassen.

Wenn Du willst, schreibe ich Dir direkt die passende captcha.php nochmal kurz & knackig dazu.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 09:41
von Tommy Herrmann
Ich teste noch.

Die fertigen Skripte kommen gleich als ZIP an Dich.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 09:48
von Tommy Herrmann
Hier kannst Du schon mal gucken und testen:

https://www.mobirise-tutorials.com/Test ... atGPT-Neu/


Ich zippe Dir jetzt diese neuen Dateien - als Grundlage bleiben dabei Deine Scripte erhalten:

> index.php
> send.php
> captcha.php

Alles andere bleibt gleich.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 10:12
von Tommy Herrmann
Habe Dir die drei Dateien via "PN" als ZIP-Archiv gesendet.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 14:24
von Tommy Herrmann
Hier noch die Erklärung zum Token:


ChatGPT hat geschrieben:
Erklärung der PHP-Zeile:

Code: Alles auswählen

    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
Diese Zeile erzeugt ein sogenanntes **CSRF-Token** (Cross-Site Request Forgery Token) und speichert es
für die Dauer der aktuellen Sitzung in der PHP-Session. Es handelt sich um einen wichtigen Sicherheitsmechanismus.

------------------------------------------------------------
1. Hintergrund: Wozu dient das Token?
------------------------------------------------------------
Das Token schützt vor sogenannten CSRF-Angriffen (Cross-Site Request Forgery). Dabei versucht
ein Angreifer, ein fremdes Formular im Namen des Nutzers abzuschicken, ohne dass dieser es merkt.
Das Token sorgt dafür, dass nur Formulare akzeptiert werden, die von der eigenen Seite stammen.

------------------------------------------------------------
2. Zerlegung der Zeile
------------------------------------------------------------

a) random_bytes(32)
- Erzeugt 32 zufällige Bytes (= 256 Bit).
- Diese Funktion verwendet eine **kryptographisch sichere Zufallsquelle**.
- Vorteil: Nicht vorhersehbar und extrem schwer zu erraten.

Beispielausgabe: (Binärdaten, nicht lesbar)
\xf7\xa3\x9e\x1b\xa4\x22\xdf\x81\xb5...

b) bin2hex(...)
- Da Binärdaten nicht direkt über HTML-Formulare übertragen werden können, wandeln wir sie
in einen lesbaren Hexadezimal-String um.
- Jedes Byte wird zu zwei Hex-Zeichen → 32 Bytes = 64 Hex-Zeichen.

Beispielausgabe:
f7a39e1ba422df81b59f2d0c4f1b8f76b13421a3b89ce4550f1b9f2a441bc334

c) $_SESSION['csrf_token'] = ...
- Der so erzeugte String wird in der Session gespeichert.
- Die Session gehört nur dem aktuellen Nutzer und läuft automatisch ab, wenn der Browser
geschlossen wird oder nach Ablauf der Session-Zeit.
- Der Token ist damit **serverseitig** gesichert und nicht erratbar.

------------------------------------------------------------
3. Einsatz im Formular
------------------------------------------------------------

Im HTML-Formular fügen wir den Token in ein verstecktes Feld ein:

Code: Alles auswählen

    <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
Beim Absenden sendet der Browser den Token zusammen mit den restlichen Formulardaten an den Server.

------------------------------------------------------------
4. Prüfung im Server-Skript
------------------------------------------------------------

Im `send.php` vergleichen wir den mitgesendeten Token mit dem in der Session gespeicherten:

Code: Alles auswählen

    if (!isset($_POST['csrf_token']) ||
        $_POST['csrf_token'] !== ($_SESSION['csrf_token'] ?? null)) {
        http_response_code(400);
        echo json_encode([
            'success' => false,
            'message' => 'Sicherheitsfehler: Ungültiges Token.'
        ]);
        exit;
    }
Nur wenn der Token korrekt ist, wird das Formular akzeptiert. Alles andere wird blockiert.

------------------------------------------------------------
5. Warum 32 Bytes?
------------------------------------------------------------
- 32 Bytes = 256 Bit = extrem sicher.
- Ein Angreifer müsste 2^256 Möglichkeiten ausprobieren, um das Token zu erraten.
- Weniger Bytes würden funktionieren, aber 32 sind quasi „unknackbar“.


------------------------------------------------------------
6. Vergleich zu unsicheren Methoden
------------------------------------------------------------
Früher haben Entwickler häufig sowas benutzt:

$_SESSION['csrf_token'] = md5(uniqid());

Problem:
- `uniqid()` basiert auf Mikrosekunden + Prozess-ID → vorhersehbar.
- `md5()` ist kryptografisch nicht sicher.
- Mit `random_bytes()` + `bin2hex()` haben wir die aktuell empfohlene Methode.

------------------------------------------------------------
7. Zusammenfassung
------------------------------------------------------------
- `random_bytes(32)` erzeugt 32 zufällige Bytes.
- `bin2hex()` macht daraus einen lesbaren Hex-String.
- Dieser 64-stellige String wird in der Session gespeichert.
- Das Token wird ins Formular eingebettet.
- Beim Absenden wird das Token verglichen.
- Ist es falsch oder fehlt es, wird der Request blockiert.
- Damit können fremde Formulare oder Bots das Formular **nicht missbrauchen**.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 19:19
von Volker
Tommy,
ich bin dir echt dankbar für deine Tests und deine Ausdauer, aber sei mir nicht böse wenn ich der KI mal widerspreche ;

Der Token wird ja bei mir einwandfrei gesetzt ( siehe Screenshot in meinem Beitrag )

Ich habe jetzt mal den Inhalt des Zip auf einen kostenlosen Webspace geladen (https://www.infinityfree.com/)

Auch da wird der Token, den mein Script generiert einwandfrei gesetzt und auch das Captcha wird dort sofort angezeigt. Wenn das nun bei mir läuft und auf irgend einem 0 Euro Webserver, dann kann die Annahme das der Token nicht gesetzt wird nicht stimmen. auch wenn die KI das so sieht. Hier der Screenshot des 0 Euro Webspace:
0euroweb.png

Session und Token: https://volker.infy.uk/
Natürlich klappt das senden des Formulars da jetzt nicht - logisch. Aber es geht ja auch um das fehlende Captcha bei Dir Tommy. Das wird nicht angezeigt weil der Token nicht geschrieben wird. Warum das nur bei Dir so ist und nicht auf meinem Server und auch nicht auf dem Gratis Server, ist mir noch ein Rätsel, liegt aber mit Sicherheit nicht am Script.

Ich wäre echt dankbar wenn ein User hier das mal testweise auf seinem webspace probieren würde. Wenn dort auch kein Token geschrieben wird gehe ich noch mal ans Script, wenn doch, dann liegt der Fehler nicht bei meinem Script. Also Liebe Leute Dieter, Frank oder wer auch immer, testet das doch mal bitte.

Diese Aussage der KI ist Falsch !
Du prüfst ein CSRF-Token – setzt es aber nirgends. Damit ist jede Anfrage „ungültig“ und der Ablauf endet, bevor überhaupt irgendetwas (inkl. CAPTCHA) geprüft oder erzeugt wird. Zusätzlich wird ein CAPTCHA-Code erst nach einem Fehlversuch generiert – beim ersten Laden existiert also keiner. Beides zusammen erklärt Dein Verhalten.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 20:10
von Volker
Ich hab auch mal die KI gefragt. claude.ai
bitte prüfe mal ob die generate.php die formular daten, captcha usw. richtig erstellt

Ich habe die generate.php Datei sorgfältig geprüft und kann bestätigen, dass sie die Formulardaten, CAPTCHA und andere Funktionen korrekt erstellt. Hier ist meine detaillierte Analyse:

✅ Korrekt implementierte Funktionen:
Formularfelder-Generierung:
Standard-Inputs: text, email, tel, password, date, time, number, range, file, hidden
Textarea: Mehrzeilige Textfelder
Select: Dropdown-Menüs mit Optionen
Checkbox/Radio: Multiple Choice Felder
Responsive Layout: Bootstrap Grid-System mit konfigurierbarer Spaltenbreite
Validierung: Required-Attribute und Client-seitige Validierung
CAPTCHA-Implementierung:
Einfaches CAPTCHA: Mathematische Aufgaben (Addition zweier Zufallszahlen)
Bild-CAPTCHA: 5-stelliger alphanumerischer Code mit:
Störlinien und Störpunkten
Anti-Aliasing für bessere Qualität
Refresh-Funktionalität
Session-basierte Speicherung
Sicherheitsfeatures:
CSRF-Token: Schutz vor Cross-Site Request Forgery
Honeypot: Verstecktes Feld für Bot-Erkennung
Zeitbasierte Prüfung: Mindest-Ausfüllzeit von 3 Sekunden
Input-Sanitization: htmlspecialchars() für alle Ausgaben
Erweiterte Funktionen:
Automatische Kopie-Checkbox: Immer verfügbar für Absender
File-Upload: Mit Größen- und Typen-Validierung
Responsive Design: Mobile-optimiert mit Bootstrap 5
AJAX-Formular: Asynchrone Übertragung ohne Page-Reload
⚠️ Potenzielle Verbesserungen:

Verbesserungen für generate.php
Code
Fazit:
Die generate.php ist grundsätzlich korrekt implementiert und erstellt vollständig funktionierende Formulare. Die wichtigsten Punkte:

✅ Was gut funktioniert:
Alle Formularfeld-Typen werden korrekt generiert
CAPTCHA (beide Varianten) funktioniert ordnungsgemäß
Sicherheitsfeatures sind implementiert
Responsive Design mit Bootstrap
AJAX-Funktionalität arbeitet korrekt
🔧 Empfohlene Optimierungen:
Erweiterte Eingabe-Validierung vor der Generierung
Verbesserte CAPTCHA-Implementierung mit weniger verwechslungsanfälligen Zeichen
Stärkere Client-seitige Validierung für bessere UX
Robustere Datei-Upload-Sicherheit mit MIME-Type-Prüfung
Bessere Fehlerbehandlung für Edge-Cases
Die Datei erfüllt alle Anforderungen und generiert sichere, funktionale Formulare. Die vorgeschlagenen Verbesserungen würden die Robustheit und Sicherheit noch weiter erhöhen, sind aber nicht zwingend erforderlich für die Grundfunktionalität.




Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 20:15
von Volker
Habe noch mal gefragt:

auf dem server eines freundes wird ihm kein captcha angezeit, er sagt der token wird nicht erstellt

Das ist ein häufiges Problem bei CAPTCHA-Implementierungen. Hier sind die wahrscheinlichsten Ursachen und Lösungen:


CAPTCHA Debug und Fix
Code
Sofortige Diagnose-Schritte:
1. Session-Problem prüfen
Ihr Freund soll diese Zeilen ganz oben in die index.php einfügen (vor allem anderen Code):

php
<?php
// DEBUG: Session-Status prüfen
error_reporting(E_ALL);
ini_set('display_errors', 1);

session_start();
echo "Session ID: " . session_id() . "<br>";
echo "Session Status: " . session_status() . "<br>";
echo "Session Data: <pre>" . print_r($_SESSION, true) . "</pre>";
?>
2. Häufigste Ursachen:
🔴 Session-Probleme:

Server hat keine Schreibrechte im Session-Verzeichnis
Sessions sind deaktiviert
Cookie-Probleme
🔴 CAPTCHA-spezifisch:

GD-Extension fehlt
Memory-Limit zu niedrig
Falsche Dateipfade
3. Schnelle Fixes:
Session-Verzeichnis prüfen:
bash
# Auf dem Server ausführen:
ls -la /tmp/
# oder
php -r "echo session_save_path();"
GD-Extension prüfen:
php
<?php
echo extension_loaded('gd') ? 'GD verfügbar' : 'GD FEHLT';
phpinfo(); // Zeigt alle Extensions
?>
Einfaches CAPTCHA als Fallback: In der generate.php das Setting auf 'simple' ändern statt 'image':
php
$settings['useCaptcha'] = 'simple'; // Statt 'image'
4. Sofort-Lösung:
Falls nichts hilft, diese minimale captcha.php erstellen:

php
<?php
session_start();
$_SESSION["captcha_code"] = "TEST123";

header("Content-Type: image/png");
$img = imagecreate(150, 50);
$bg = imagecolorallocate($img, 255, 255, 255);
$text = imagecolorallocate($img, 0, 0, 0);
imagestring($img, 5, 30, 15, "TEST123", $text);
imagepng($img);
imagedestroy($img);
?>
Bitte lassen Sie Ihren Freund:

Die Debug-Version von oben testen
Die Session-Diagnose durchführen
phpinfo() aufrufen und nach GD-Extension suchen
Mit den Debug-Ausgaben können wir das Problem genau identifizieren!

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 20:25
von Volker
Zum Thema Chat GTP:

Der hab ich mal die send.php zu lesen gegeben... beachte mal bitte was die so macht und wenn man sie auf ihre Fehler anspricht ihre reaktion: in ROT hervorgehoben
kannst du mal diese send.php prüfen?
ChatGPT:

Ich habe mir den Anfang deiner send.php angeschaut. Sie sieht auf den ersten Blick ordentlich strukturiert aus, mit klaren Bereichen für Konfiguration, SMTP-Optionen, Auto-Reply und Datei-Uploads. 👍

Einige Punkte, die mir bisher auffallen:

✅ Positiv:

PHPMailer wird korrekt eingebunden (Exception.php, PHPMailer.php, SMTP.php).

SMTP-Optionen sind vorbereitet, aber auskommentiert → flexibel nutzbar.

Auto-Reply ist konfigurierbar und sauber abgeschaltet ($enable_auto_reply = false;).

Datei-Upload: Einschränkungen für erlaubte Endungen und Dateigröße sind gesetzt.

⚠️ Punkte, auf die du achten solltest:

Upload-Verzeichnis (uploads/)

Ist das Verzeichnis vorhanden und beschreibbar?

Am besten per .htaccess oder nginx-Regel vor direktem Zugriff schützen (sonst können hochgeladene Dateien direkt im Browser aufgerufen werden → Sicherheitsrisiko).

Validierung von Eingaben

Ich müsste den kompletten Code sehen, aber aktuell erkenne ich noch nicht, ob du filter_var() oder htmlspecialchars() für Formulardaten nutzt. Das ist wichtig, um XSS/Injection zu vermeiden.

Fehlerbehandlung

Prüfe, ob Fehlermeldungen sauber abgefangen werden, damit User nicht interne Infos sehen (z. B. SMTP-Logs).

CSRF-Schutz

Wenn du Sessions nutzt, wäre ein Token im Formular sinnvoll, um Spam/Bot-Angriffe zu verhindern.

👉 Soll ich dir die Datei komplett analysieren und eine Sicherheits-/Funktionsprüfung Zeile für Zeile machen, oder reicht dir ein allgemeiner Sicherheitscheck mit Verbesserungsvorschlägen?

Du:
token wird doch gesetzt
ChatGPT:


Ja, du hast recht 👍 – in deiner send.php wird tatsächlich ein CSRF-Token gesetzt und überprüft.

Auszug aus deinem Code:

// Anti-Spam: CSRF-Token-Überprüfung
if (!isset($_POST["csrf_token"]) || $_POST["csrf_token"] !== ($_SESSION["csrf_token"] ?? null)) {
echo json_encode(["success" => false, "message" => "Sicherheitsfehler: Ungültiges Token."]);


➡️ Das bedeutet:

Im Formular wird ein csrf_token als Hidden-Field mitgegeben.

Auf dem Server prüfst du, ob dieses Token mit dem in der Session gespeicherten übereinstimmt.

Bei Abweichung oder fehlendem Token wird die Anfrage sofort blockiert.

Das ist ein solider CSRF-Schutz und gleichzeitig ein Anti-Spam-Mechanismus.

👉 Willst du, dass ich mir die ganze Datei jetzt komplett durchsehe und dir eine Sicherheits- und Funktionsbewertung Abschnitt für Abschnitt schreibe?

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 20:32
von Volker
Hier die Empfehlung von Chat GPT nach Durchsicht der kompletten send.php:

👉 Willst du, dass ich mir die ganze Datei jetzt komplett durchsehe und dir eine Sicherheits- und Funktionsbewertung Abschnitt für Abschnitt schreibe?

Du:
ja bitte
ChatGPT:
Nachgedacht für 2m 57s

Alles klar—ich hab deine send.php komplett durchleuchtet. Kurzes Fazit vorweg: sehr solide Basis mit CSRF-Token, Honeypot, Zeitbremse, Captcha, sauberem PHPMailer-Setup (kein User-spoofing bei From), Retry-Mechanismus und ordentlichen HTML/Text-Bodies. Für Produktion gibt’s aber ein paar sinnvolle Härtungen und kleine Stolpersteine.

Was passt 👍

CSRF: Prüfung gegen Session-Token und Abbruch mit 400 – korrekt.

Anti-Spam: Honeypot + Mindestzeit (≥ 3 s) + Captcha inkl. Regeneration bei Fehlversuch – gut.

PHPMailer:

Bei SMTP wird setFrom($smtp_user, …) genutzt → kein User-From-Spoofing.

Ohne SMTP fallback via isMail() – ok als Fallback.

Input-Sanitizing: sanitize_input() (trim/stripslashes/htmlspecialchars) und zusätzlich FILTER_VALIDATE_EMAIL bei E-Mailfeldern.

Attachments: Einzigartige Dateinamen via uniqid(), Größenlimit, erlaubte Endungen, addAttachment() – funktioniert.

Antwort-Codes & JSON: Konsequentes http_response_code(...) + json_encode(...).

Auto-Reply (optional): sauber gekapselt, nur bei gültiger Absenderadresse.

Was du verbessern solltest (Security/Robustheit) 🛡️

JSON Content-Type setzen
Aktuell fehlt ein expliziter Header (Browser/JS freut sich darüber).

header('Content-Type: application/json; charset=utf-8');


→ direkt am Anfang der POST-Verarbeitung, bevor echo json_encode(...) aufgerufen wird.

MIME-Type prüfen (nicht nur Dateiendung)
Du prüfst die Endung, aber nicht den wirklichen Typ. Ergänze finfo:

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);

$allowed_mimes = [
'image/jpeg', 'image/png',
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
];
if (!in_array($mime, $allowed_mimes, true)) {
http_response_code(400);
echo json_encode(["success" => false, "message" => "Ungültiger Dateityp."]);
exit;
}


(Optional: zusätzlich Magic-Bytes prüfen.)

Uploads-Verzeichnis absichern

Lege außerhalb des Webroots ab, z. B. ../data/uploads/ (und passe $uploads_dir an).

Wenn im Webroot, dann wenigstens .htaccess mit Deny from all oder Nginx-Location-Block.

Prüfe/erzeuge Ordner bei Bedarf:

if (!is_dir($uploads_dir)) { mkdir($uploads_dir, 0750, true); }


Maximale Anzahl Attachments begrenzen
Verhindert Ressourcenmissbrauch:

$max_files = 3;
$count_ok = 0;
foreach ($_FILES as $file) {
if ($file['error'] === UPLOAD_ERR_OK) {
$count_ok++;
if ($count_ok > $max_files) {
http_response_code(400);
echo json_encode(["success" => false, "message" => "Zu viele Dateien."]);
exit;
}
}
}


Hochgeladene Dateien nach Versand aufräumen (falls nicht benötigt)
Minimiert Risiko & Speicherverbrauch:

// nach $mail->send():
foreach ($uploaded_files as $file) {
if (is_file($file['path'])) { @unlink($file['path']); }
}


SMTP TLS-Validierung nicht global abschalten
In SMTPOptions steht aktuell:

"verify_peer" => false,
"verify_peer_name" => false,
"allow_self_signed" => true


→ Das bitte nur testweise. In Produktion entfernen oder auf true setzen und gültige CA nutzen.

From im Fallback verbessern
Im isMail()-Zweig setzt du setFrom($recipient_email, $sender_name). Besser: eine interne Adresse deiner Domain, z. B. no-reply@deinedomain.tld, um DMARC/SPF-Probleme zu vermeiden:

$mail->setFrom('no-reply@deinedomain.tld', $sender_name);
$mail->addAddress($recipient_email);
if ($sender_email) { $mail->addReplyTo($sender_email); }


Rate-Limiting pro IP / Session
Einfache Bremse gegen Spam-Wellen:

$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$_SESSION['contact_attempts'] = $_SESSION['contact_attempts'] ?? [];
$_SESSION['contact_attempts'] = array_filter(
$_SESSION['contact_attempts'],
fn($ts) => $ts > time() - 60 // nur letzte 60s
);
if (count($_SESSION['contact_attempts']) >= 5) {
http_response_code(429);
echo json_encode(["success" => false, "message" => "Zu viele Anfragen. Bitte später erneut versuchen."]);
exit;
}
$_SESSION['contact_attempts'][] = time();


Pflichtfelder-Liste synchron halten
Du nutzt:

$required_fields = ["name", "email", "Betreff", "Nachricht", "Datenschutz"];


→ Achte auf exakte Feldnamen/Schreibweise im Frontend (Case-Sensitivity). Tipp: zentral konfigurieren oder aus dem Formular generieren.

„Kopie an Absender“ implementieren (optional)
Du liest send_copy_to_sender, aber ich sehe keinen separaten Versand als Kopie. Falls gewünscht:

if ($send_copy_to_sender && $sender_email) {
try {
$copy = clone $mail;
$copy->clearAllRecipients();
$copy->addAddress($sender_email);
$copy->Subject = "[Kopie] " . $subject;
$copy->send();
} catch (Exception $e) {
error_log("Copy send failed: " . $e->getMessage());
}
}


HTML-Escaping konsequent
In den HTML-Tabellen nutzt du bereits htmlspecialchars()/nl2br(). Behalte das für alle Werte/Labels bei (auch bei Arrays via array_map('htmlspecialchars', ...)). Scheint überall korrekt, nur „auf Lücken achten“.

Fehlertexte / Leaks
Du loggst Ausnahmen via error_log() und gibst dem Nutzer generische Messages – top. Achte darauf, niemals SMTP-User/Pass im Klartext auszugeben (tust du nicht).

CSRF/Captcha-Lebenszyklus
Das Token wird geprüft – die Erzeugung passiert vermutlich in der Formularseite. Gute Praxis: Token pro Render neu generieren (One-time-Token). In send.php ist das ok, nur Hinweis für die Gegenstelle. Captcha wird bei Fehler neu generiert – super.

Mini-Diffs (direkt einbaubar)

(a) JSON Header & MIME-Check & Ordner)

// Nach session_start();
header('Content-Type: application/json; charset=utf-8');

// Vor Upload-Verarbeitung:
if (!is_dir($uploads_dir)) { mkdir($uploads_dir, 0750, true); }
$finfo = finfo_open(FILEINFO_MIME_TYPE);
...
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, $allowed_mimes, true)) { ... }


(b) Nach Versand aufräumen)

if ($sent) {
foreach ($uploaded_files as $file) {
if (is_file($file['path'])) { @unlink($file['path']); }
}
}


(c) SMTPOptions strenger)

// Entfernen oder hart absichern:
$mail->SMTPOptions = [
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
'allow_self_signed' => false,
],
];


(d) Fallback-From verbessern)

$mail->isMail();
$mail->setFrom('no-reply@deinedomain.tld', $sender_name);


Wenn du willst, setze ich dir die Änderungen gern zielgenau in deiner Datei (mit sauber kommentierten Blöcken), oder ich gebe dir eine bereinigte Variante deiner send.php mit allen oben genannten Härtungen direkt fertig aus.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 20:39
von Volker
Also abschließend kann ich sagen das mein Script so wie es ist, Lauffähig, Sicher und Anwendbar ist. Geprüft durch 2 unterschiedliche KI´s. Ich werde den Code jetzt erstmal so lassen, bis hier mal jemand den selben Fehler bei sich findet, den Tommy mit dem Script hat. Keine Anzeige des Captchas und Probleme beim Senden durch fehlenden Token.

Auch nochmal auf einer anderen Domain von mir geprüft: https://sound-dj.de/formtest/
Ist zwar der selbe Sever aber ganz andere Domain mit anderen Einstellungen ;)

ach ja, die index.php hab ich auch noch von chatgpt checken lassen :
Ich habe deinen Code geprüft – er funktioniert grundsätzlich fehlerfrei. Meine Empfehlungen betreffen vor allem Sicherheit (Session-Regeneration nach Login, zusätzliche Validierung der E-Mail) und kleine Verbesserungen für Layout und Benutzerfreundlichkeit.

Du:
was genau ?
ChatGPT:

Ganz konkret meine ich Folgendes:

Session-Fixation verhindern
Nach erfolgreichem Login solltest du die Session-ID neu generieren. Direkt bevor du $_SESSION['user_id'] und $_SESSION['rolle'] setzt, füge ein:

session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['rolle'] = $user['rolle'];


➝ Damit wird verhindert, dass jemand eine gestohlene Session-ID weiternutzt.

E-Mail-Eingabe validieren
Bevor du die E-Mail in dein Prepared Statement einsetzt, kannst du prüfen, ob sie auch wirklich ein valides Format hat:

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error = "Bitte eine gültige E-Mail-Adresse eingeben.";
} else {
// Datenbankabfrage...
}


➝ So vermeidest du unnötige Datenbankabfragen mit falschen Eingaben.

(Optional) Kleine Layout-Verbesserung
Statt container mt-5 kannst du auch container-fluid d-flex justify-content-center align-items-center vh-100 nutzen. Damit sitzt das Login-Formular immer mittig im Bildschirm:

<div class="container-fluid d-flex justify-content-center align-items-center vh-100">
<div class="col-md-4">
...
</div>
</div>


Möchtest du, dass ich dir gleich den überarbeiteten Code mit diesen Anpassungen zeige?
Da alle scripte von meinem Backend generiert werden, je nach Formular Inhalten die der User im Frontend eingibt, nutzen mir die fertigen Scripte von dir Tommy leider nichts. Die send.php, index.php und captcha.php wird ausschließlich generiert und sind keine fertigen Dateien, die ich dem Zip beilege.

Re: Neuer Formulargenerator

Verfasst: Fr 22. Aug 2025, 21:08
von Volker
Ich weiß nicht ob du noch Bock hast Tommy :D

Aber wenn ja, dann mach doch bitte mal folgenden Code am Anfang in die index.php die im Zip ist und schreib mir mal die Ergebnisse hier:

Code: Alles auswählen

<?php
// DEBUG: Session-Status prüfen
error_reporting(E_ALL);
ini_set('display_errors', 1);

session_start();
echo "Session ID: " . session_id() . "<br>";
echo "Session Status: " . session_status() . "<br>";
echo "Session Data: <pre>" . print_r($_SESSION, true) . "</pre>";
?>
hab ich hier mal eingebaut und hier wird auch der Token angezeigt: https://sound-dj.de/formtest/
token3.png
Ich hab den cookie First bei dir ja schwer in Verdacht Tommy ;)
Nein, ein Cookie-First Consent Management Platform (CMP) wie CookieFirst verhindert nicht direkt Tokens. Stattdessen verwaltet sie die Einwilligung des Benutzers für die Nutzung von Cookies und anderen Online-Tracking-Technologien gemäß Datenschutzgesetzen wie der DSGVO. Sie kann Cookies auf die Zustimmung des Nutzers beschränken, was wichtig ist, wenn Tokens in Cookies gespeichert werden, und sie ist entscheidend für die Einhaltung von Vorschriften im Zusammenhang mit der Verwendung von Tokens in Webanwendungen.
Wie CookieFirst Tokens beeinflusst
Einwilligungsmanagement für Tokens in Cookies:
Wenn Tokens in HTTP-Cookies gespeichert werden, ist die Zustimmung des Nutzers zur Nutzung dieser Cookies wichtig. CookieFirst stellt sicher, dass die notwendigen Cookies nur dann gesetzt werden, wenn der Nutzer seine Zustimmung gibt, was indirekt die Verwendung von Tokens in Cookies steuert.
Einhaltung von Datenschutzgesetzen:
Durch das Management der Cookie-Zustimmung hilft CookieFirst, die Einhaltung der DSGVO zu gewährleisten, welche die Datenerfassung und -nutzung regelt. Dies ist besonders relevant für tokens, die zur Authentifizierung oder zur Speicherung von Nutzerdaten verwendet werden, und für die Einhaltung der neuen «allow-same-site-none-cookies»-Richtlinie.
Sicherheitspraktiken:
Eine korrekte Cookie-Verwaltung ist eine der Säulen der Sicherheit im Web. Indem CookieFirst dafür sorgt, dass nur notwendige Cookies gesetzt werden, unterstützt es sichere Praktiken, wie die sichere Speicherung von Tokens in HTTP-Only-Cookies.
Zusammenfassend lässt sich sagen: CookieFirst verhindert nicht direkt Tokens, sondern verwaltet die Zustimmung des Nutzers für die Verwendung von Cookies. Dies ist ein wichtiger Schritt, um den Zugriff auf sensible Daten zu kontrollieren, einschließlich der Sicherheit von Tokens, die in Cookies gespeichert sind.
Chat GPT gibt mir Recht :D
cookie first verhindert csrf token, kann das sein ?
ChatGPT:

Das kann tatsächlich passieren – je nach Einstellungen von CookieFirst und der Art, wie dein CSRF-Token gehandhabt wird.

Ein paar typische Ursachen:

Blockierte Cookies

Wenn CookieFirst die Cookies deiner Anwendung (z. B. PHPSESSID, XSRF-TOKEN, csrftoken) blockiert, bevor der Nutzer zustimmt, fehlt der Session- oder CSRF-Cookie → das Token kann nicht gesetzt oder geprüft werden.

Besonders kritisch, wenn dein CSRF-Mechanismus auf ein Cookie-basiertes Token angewiesen ist.

CSP / Script Blocking

Wenn CookieFirst Skripte blockiert, kann es sein, dass die Logik zur Generierung oder Einbindung des CSRF-Tokens im Formular nicht ausgeführt wird. Dann kommt beim POST kein oder ein ungültiges Token an.

Third-Party Requests

Manche CSRF-Mechanismen setzen ein SameSite-Cookie oder verlangen bestimmte Header. CookieFirst kann Third-Party-Cookies blocken → dann geht das CSRF-Cookie nicht durch.

🔧 Mögliche Lösungen:

CSRF-Token nicht nur im Cookie speichern, sondern auch serverseitig in der Session → und als Hidden Field im Formular einbauen. Dann hängt es nicht an der Cookie-Zustimmung.

Falls CookieFirst unbedingt Session-Cookies blockt: diese Cookies in die "Essentiell"-Kategorie packen, sodass sie nie blockiert werden (meistens möglich).

Prüfen, ob dein CSRF-Mechanismus über HTTP-Header (z. B. X-CSRF-TOKEN) läuft und ob CookieFirst hier reinfunkt.

👉 In 90 % der Fälle liegt das Problem daran, dass CookieFirst dein Session-Cookie oder CSRF-Cookie als "nicht essentiell" einstuft und blockiert.

Soll ich dir ein Beispiel zeigen, wie man ein CSRF-Token unabhängig vom CookieFirst-Consent über ein Hidden Input Feld + Session robust umsetzen kann?

Re: Neuer Formulargenerator

Verfasst: Sa 23. Aug 2025, 04:59
von Tommy Herrmann
Moin,

ich bewundere ja Deinen Kampfgeist :)

Warum änderst du nicht einfach die Dateien, so wie von ChatGPT erstellt, und dann läuft doch alles? ChatGPT hat dein Formular ja auch in wenigen Sekunden wunderbar zum Laufen gebracht. Hier der Beweis:

https://www.mobirise-tutorials.com/Test ... atGPT-Neu/

… und nein - bei mir ist "CookieFirst" überhaupt nicht aktiv auf dieser Seite - wie kommst Du denn darauf? CookieFirst muss auf jeder einzelnen Seite verlinkt sein, damit es Anwendung findet. Diese Anwendung von Dir läuft doch in einem ganz eigenen Umgebung auf meinem Server ohne irgendeinen Einfluss von anderen Skripten. Dazu müsste ich in Deiner index.php einen Verweis zu den Skripten von CookieFirst einbinden und das habe ich natürlich nicht getan.

Wenn ich die Anzeige für die Fehlermeldungen hinzufüge, steht es ja auch genau so da wie von ChatGPT analysiert - es wurde keine SESSION Variable gefunden:

https://www.mobirise-tutorials.com/Test ... lker/Test/


Fehlende SESSION-ID.jpg


Mir fällt der Unterschied in der index.php im Formular auf - aber eigentlich macht es keinen Unterschied in welcher Form fdas geschrieben ist.

Du schreibst:

Code: Alles auswählen

<input type="hidden" name="csrf_token" value="<?php echo $_SESSION["csrf_token"]; ?>">
ChatGPT schreibt:

Code: Alles auswählen

<input type="hidden" name="csrf_token" value="<?= e($_SESSION['csrf_token']) ?>">

Ich habe noch folgenden Hinweis erhalten. Wird da vielleicht irgendwo das www bei meiner Domain verschluckt? Bei Dir hat die Domain kein www bei mir schon. Das ist jedenfalls sicher ein Unterschied zwischen Deiner und meiner Domain.

ChatGPT hat geschrieben:
Unterschiedliche Domains oder Subdomains

Sessions sind Domain-gebunden.

Beispiel:

> Formularseite: https://www.meineseite.de

> send.php: https://meineseite.de

→ Ergebnis: Session geht verloren → Token in send.php ist leer.

Lösung: Immer dieselbe Domain verwenden oder session.cookie_domain in der php.ini setzen.


Hast Du denn niemand anderen, der das auch mal zusätzlich prüfen kann. Schreibe doch mal "Stobi" an, könnte aber sein, dass er gerade verreist ist.

Re: Neuer Formulargenerator

Verfasst: Sa 23. Aug 2025, 06:31
von Tommy Herrmann
Eigentlich weißt du am besten, was du seit der neuen Version mit dem Erstellen der Kopie an den Absender geändert hast.

Denn bei mir hat davor noch alles funktioniert – einschließlich der Session, des Tokens und des Captchas:

https://www.mobirise-tutorials.com/Test ... /SMTPMail/

Wir suchen den Fehler vielleicht an einer ganz falschen Stelle. Ergo - der Fehler muss an Deiner letzten Änderung liegen, denn zuvor ging doch bei mir auch alles.

Re: Neuer Formulargenerator

Verfasst: Sa 23. Aug 2025, 07:25
von Volker
Tommy Du bist echt gut :D

Da muss noch ein Fehler sein. Seit dem ich die Kopie Box eingebaut habe läuft es nicht mehr.

Also wieder zurück (erstmal) habe die jetzt wieder raus.
Ich muss es ja auch aller verstehen :D

Kannst du nochmal testen ob die jetzt erstellten formulare gehen ?

https://www.niederastroth.de/formgenerator/

Die Versuche mit Deinem Code werde ich hier testen dann: https://www.niederastroth.de/00/

Re: Neuer Formulargenerator

Verfasst: Sa 23. Aug 2025, 07:58
von Tommy Herrmann
Moin,

warte - mache ich gleich - danach muss ich erst einmal für 1-2 Sunden weg.