Beispiele zum Versenden von Web-Formularen

In diesem Beispiel (ab PHP7.0 mit) kann der Besucher ein Formular ausfüllen.
Beim Absenden des Formulars wird das Script phpmailer.php aufgerufen und alle Formulardaten übergeben.
Das Script überprüft die Daten und zeigt entweder eine Webseite mit einem Fehler an oder sendet das Email und zeigt eine Webseite mit einer Erfolgsmeldung.

WICHTIG

Aus Sicherheitsgründen (Spam) darf die Emailadresse des/der Empfänger nicht einfach von einem öffentlichen Web-Formular übernommen werden.
Das Script muss vor Bots geschützt werden:

Massnahme Schützt gegen Aufwand
Honeypot-Feld (unsichtbares Feld, das nur Bots ausfüllen) einfache Bots sehr gering
Zeitprüfung (Formular in weniger als 3 s abgeschickt = Bot) automatisierte Bots gering
CSRF-Token (Session) direkte POST-Requests ohne Aufruf des Formulars gering
Rate Limit pro IP Massenversand gering
Captcha gezielte, bessere Bots mittel

 

Einfache Version OHNE SCHUTZ:

Dies nur als Funktionsbeispiel, ohne zusätzlichen Schutz darf dies so nicht verwendet werden.

Das Formular "kontakt.html"

<html>
<head>
<title>Mail Beispiel mit PHP</title>
</head>
<body>
<font face="Arial">
<b>Mail Beispiel mit PHP</b>
<form action="phpmailer.php" method="POST">
Name, Vorname: <input type="text" name="name_surname" />
<br />
Absender-Email: <input type="text" name="mailfrom" />
<br />
Betreff: <input type="text" name="subject" />
<br />
Text:<br /><textarea name="text" rows="20" cols="50"></textarea>
<br />
<input type="submit" value="E-Mail senden" />
</form>
</font>
</body>
</html>

Das Script "phpmailer.php"

<?php
echo 'Mail Beispiel mit PHP
'; $name = trim($_POST['name_surname'] ?? ''); $mailfrom = trim($_POST['mailfrom'] ?? ''); $subject = trim($_POST['subject'] ?? ''); $text = trim($_POST['text'] ?? ''); $errors = []; if ($name === '') $errors[] = 'Geben Sie Ihren Namen und Vornamen an!'; if (!filter_var($mailfrom, FILTER_VALIDATE_EMAIL)) $errors[] = 'Geben Sie eine gültige E-Mail-Adresse an!'; if ($subject === '') $errors[] = 'Kein Betreff angegeben!'; if ($text === '') $errors[] = 'Leere E-Mails werden nicht versendet!'; // Schutz gegen Header Injection foreach ([$name, $mailfrom, $subject] as $v) { if (preg_match('/[\r\n]/', $v)) { $errors[] = 'Ungültige Eingabe!'; break; } } if ($errors) { foreach ($errors as $e) echo htmlspecialchars($e) . '
'; exit; } $mailto = 'name@deine-domain.ch'; $headers = "From: Kontaktformular <name@deine-domain.ch>\r\n"; $headers .= "Reply-To: " . mb_encode_mimeheader($name) . " <$mailfrom>\r\n"; $headers .= "MIME-Version: 1.0\r\n"; $headers .= "Content-Type: text/plain; charset=UTF-8\r\n"; $headers .= "Content-Transfer-Encoding: 8bit\r\n"; $body = "Name: $name\r\nE-Mail: $mailfrom\r\n\r\n$text"; if (mail($mailto, mb_encode_mimeheader($subject, 'UTF-8'), $body, $headers, '-fnoreply@wunschname.ch')) { echo 'E-Mail wurde erfolgreich versendet!'; } else { echo 'Fehler beim Versand der E-Mail.'; }

 

Version MIT SCHUTZ:

Dies kann mit Cloudflare Turnstile oder Friendly Captcha benutzt werden.

Legen Sie im Cloudflare-Dashboard unter Turnstile ein Widget für Ihre Domain an. Die Domain muss dafür nicht über Cloudflare laufen. Tragen Sie dann den Site Key und den Secret Key ein.

Das Formular "kontakt.php"

<?php
session_start();
$_SESSION['csrf']      = bin2hex(random_bytes(32));
$_SESSION['form_time'] = time();
?>
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <title>Kontaktformular</title>
    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
    <style>.hp-field { position: absolute; left: -9999px; }</style>
</head>
<body style="font-family: Arial, sans-serif">
    <b>Kontaktformular</b>
    <form action="senden.php" method="POST">
        <input type="hidden" name="csrf" value="<?= $_SESSION['csrf'] ?>">

        <!-- Honeypot: für Menschen unsichtbar -->
        <div class="hp-field" aria-hidden="true">
            <label>Website <input type="text" name="website" tabindex="-1" autocomplete="off"></label>
        </div>

        Name, Vorname: <input type="text" name="name_surname" maxlength="100" required><br>
        Absender-E-Mail: <input type="email" name="mailfrom" maxlength="254" required><br>
        Betreff: <input type="text" name="subject" maxlength="150" required><br>
        Text:<br><textarea name="text" rows="20" cols="50" maxlength="5000" required></textarea><br>

        <div class="cf-turnstile" data-sitekey="IHR_SITE_KEY"></div>

        <input type="submit" value="E-Mail senden">
    </form>
</body>
</html>

Das Script "senden.php"

<?php
session_start();

const TURNSTILE_SECRET = 'IHR_SECRET_KEY';
const MAIL_TO          = 'wunschname@wunschname.ch';
const MAIL_FROM        = 'noreply@wunschname.ch';
const MIN_SECONDS      = 3;    // schneller = Bot
const MAX_PER_HOUR     = 5;    // Mails pro IP pro Stunde

function abbruch($msg) {
    http_response_code(400);
    exit(htmlspecialchars($msg));
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') abbruch('Ungültiger Aufruf.');

// 1) CSRF-Token (einmalig verwendbar)
$token = $_POST['csrf'] ?? '';
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $token)) {
    abbruch('Sitzung abgelaufen. Bitte Formular neu laden.');
}
unset($_SESSION['csrf']);

// 2) Honeypot: Bots erhalten eine scheinbare Erfolgsmeldung
if (!empty($_POST['website'])) {
    exit('E-Mail wurde erfolgreich versendet!');
}

// 3) Zeitprüfung
if (time() - ($_SESSION['form_time'] ?? 0) < MIN_SECONDS) {
    abbruch('Formular zu schnell abgeschickt.');
}

// 4) Rate Limit pro IP (dateibasiert)
$ip   = $_SERVER['REMOTE_ADDR'];
$file = sys_get_temp_dir() . '/mailform_' . md5($ip);
$now  = time();
$hits = is_file($file) ? json_decode(file_get_contents($file), true) : [];
$hits = array_filter((array)$hits, function ($t) use ($now) { return $t > $now - 3600; });
if (count($hits) >= MAX_PER_HOUR) abbruch('Zu viele Anfragen. Bitte später erneut versuchen.');

// 5) Captcha serverseitig prüfen
$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_POSTFIELDS     => http_build_query([
        'secret'   => TURNSTILE_SECRET,
        'response' => $_POST['cf-turnstile-response'] ?? '',
        'remoteip' => $ip,
    ]),
]);
$result = json_decode((string)curl_exec($ch), true);
curl_close($ch);
if (empty($result['success'])) abbruch('Captcha-Prüfung fehlgeschlagen.');

// Eingaben prüfen
$name     = trim($_POST['name_surname'] ?? '');
$mailfrom = trim($_POST['mailfrom'] ?? '');
$subject  = trim($_POST['subject'] ?? '');
$text     = trim($_POST['text'] ?? '');
$errors   = [];

if ($name === '' || mb_strlen($name) > 100)       $errors[] = 'Bitte Namen angeben.';
if (!filter_var($mailfrom, FILTER_VALIDATE_EMAIL)) $errors[] = 'Bitte gültige E-Mail-Adresse angeben.';
if ($subject === '' || mb_strlen($subject) > 150) $errors[] = 'Bitte Betreff angeben.';
if ($text === '' || mb_strlen($text) > 5000)      $errors[] = 'Leere oder zu lange E-Mails werden nicht versendet.';
foreach ([$name, $mailfrom, $subject] as $v) {
    if (preg_match('/[\r\n]/', $v)) { $errors[] = 'Ungültige Eingabe.'; break; }
}
if ($errors) abbruch(implode(' ', $errors));

// Versand
$headers  = "From: Kontaktformular <" . MAIL_FROM . ">\r\n";
$headers .= "Reply-To: " . mb_encode_mimeheader($name) . " <$mailfrom>\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
$headers .= "Content-Transfer-Encoding: 8bit\r\n";

$body = "Name: $name\r\nE-Mail: $mailfrom\r\nIP: $ip\r\n\r\n$text";

if (mail(MAIL_TO, mb_encode_mimeheader('[Kontakt] ' . $subject, 'UTF-8'),
         $body, $headers, '-f' . MAIL_FROM)) {
    $hits[] = $now;
    file_put_contents($file, json_encode(array_values($hits)), LOCK_EX);
    echo 'E-Mail wurde erfolgreich versendet!';
} else {
    abbruch('Fehler beim Versand der E-Mail.');
}


 

Turnstile einrichten

  1. Cloudflare-Konto erstellen oder anmelden: dash.cloudflare.com. Ein kostenloses Konto genügt. Die Domain muss nicht über Cloudflare laufen – weder DNS noch Proxy.
  2. Im Dashboard links Turnstile öffnen, direkt erreichbar unter dash.cloudflare.com/?to=/:account/turnstile.
  3. Add widget anklicken und ausfüllen:
    • Widget name: frei wählbar, z. B. Kontaktformular dein-name.ch
    • Hostnames: dein-name.ch und www.dein-name.ch – nur auf diesen Hosts funktioniert das Widget. Für lokale Tests zusätzlich localhost eintragen.
    • Widget mode: Managed ist die Empfehlung. Die meisten Besucher sehen nur kurz eine Häkchen-Box, ohne Rätsel. Invisible läuft ganz ohne sichtbares Element, Non-Interactive zeigt eine Box ohne Klick.
    • Pre-clearance brauchen man nicht, das ist für Single-Page-Apps.
  4. Create klicken. Danach erscheinen Site Key und Secret Key.
    • Site Key → in kontakt.php bei data-sitekey="IHR_SITE_KEY"
    • Secret Key → in senden.php bei TURNSTILE_SECRET. Er gehört nie in den HTML-Code, da er sonst öffentlich sichtbar wäre.
  5. Beide Schlüssel lassen sich im Dashboard jederzeit wieder einsehen und bei Bedarf rotieren (Settings beim Widget).

Kosten und Grenzen: Turnstile ist im Standard-Plan kostenlos, ohne veröffentlichtes Anfragelimit. Die Analytics reichen nur 24 Stunden zurück. Erweiterte Funktionen gibt es nur im Enterprise-Vertrag. Pro Account sollten man ein Widget pro Domain oder Formular anlegen. In der Community wurde gelegentlich von einer Obergrenze bei der Anzahl Widgets berichtet, bei einzelnen Formularen ist das aber kein Thema.

 

Testschlüssel für die Entwicklung, ohne eigenes Widget – damit lässt sich das Script prüfen, bevor die Domain freigeschaltet ist:

Zweck Site Key Secret Key
immer erfolgreich 1x00000000000000000000AA 1x0000000000000000000000000000000AA
immer abgelehnt 2x00000000000000000000AB 2x0000000000000000000000000000000AA

Wichtig zum Testen: Prüfen Sie, ob das Formular ohne gültiges Captcha wirklich abgewiesen wird. Rufen Sie senden.php dazu einmal direkt im Browser auf. Es muss die Meldung «Ungültiger Aufruf.» erscheinen. Der Server muss ausserdem challenges.cloudflare.com über HTTPS erreichen können, sonst schlägt die Prüfung mit curl fehl. Falls auf dem Server eine ausgehende Firewall aktiv ist, muss diese Verbindung offen sein.

Datenschutz: Turnstile übermittelt IP-Adresse und Browserdaten an Cloudflare (US-Anbieter mit EU-Rechenzentren). Das gehört in die Datenschutzerklärung. Cloudflare setzt dabei keine Tracking-Cookies für Werbung.

Alternative: Friendly Captcha aus Deutschland, mit Hosting in der EU. Die Einbindung ist analog, das Prüf-Script ist https://cdn.jsdelivr.net/npm/friendly-challenge/widget.module.min.js, das Feld heisst frc-captcha-solution und der Verify-Endpoint ist https://api.friendlycaptcha.com/api/v1/siteverify. Dort gibt es einen kostenlosen Plan für kleine Websites, grössere Kontingente sind kostenpflichtig

Cette réponse était-elle pertinente ? 45 Utilisateurs l'ont trouvée utile (58 Votes)