first commit
This commit is contained in:
36
forms/.htaccess
Normal file
36
forms/.htaccess
Normal file
@@ -0,0 +1,36 @@
|
||||
# Schütze config.php und andere sensible Dateien
|
||||
<Files "config.php">
|
||||
Require all denied
|
||||
</Files>
|
||||
|
||||
<Files "test-*.php">
|
||||
Require all denied
|
||||
</Files>
|
||||
|
||||
# Verhindere Directory Listing
|
||||
Options -Indexes
|
||||
|
||||
# Blockiere Zugriff auf Backup-Dateien
|
||||
<FilesMatch "\.(bak|backup|old|orig|save|swp|tmp)$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# Verhindere Zugriff auf versteckte Dateien
|
||||
<FilesMatch "^\.">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# PHP-Sicherheitseinstellungen
|
||||
<IfModule mod_php7.c>
|
||||
php_flag display_errors off
|
||||
php_flag log_errors on
|
||||
php_value upload_max_filesize 20M
|
||||
php_value post_max_size 20M
|
||||
</IfModule>
|
||||
|
||||
# Zusätzliche Sicherheits-Header
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
Header set X-Frame-Options "SAMEORIGIN"
|
||||
Header set X-XSS-Protection "1; mode=block"
|
||||
</IfModule>
|
||||
2
forms/Readme.txt
Normal file
2
forms/Readme.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Fully working PHP/AJAX contact form script is available in the pro version of the template.
|
||||
You can buy it from: https://bootstrapmade.com/day-multipurpose-html-template-for-free/
|
||||
54
forms/config.php
Normal file
54
forms/config.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* WICHTIG: Diese Datei ist veraltet!
|
||||
* Bitte verwenden Sie stattdessen die neue config.php im Hauptverzeichnis
|
||||
* oder noch besser: Verschieben Sie die Config außerhalb des Webroots
|
||||
*
|
||||
* Diese Datei wird aus Kompatibilitätsgründen beibehalten, sollte aber
|
||||
* durch die neue Sicherheitskonfiguration ersetzt werden.
|
||||
*
|
||||
* Siehe: /config.php oder /SECURITY.md für Anweisungen
|
||||
*/
|
||||
|
||||
// Sicherheitskonstante setzen
|
||||
define('CONFIG_LOADED', true);
|
||||
|
||||
// Empfänger-E-Mail-Adresse
|
||||
define('RECEIVING_EMAIL', 'webkontakt@hkw-anwaelte.de');
|
||||
|
||||
// SMTP-Konfiguration
|
||||
// WARNUNG: Diese Credentials sollten in Umgebungsvariablen gespeichert werden!
|
||||
define('SMTP_CONFIG', [
|
||||
'host' => 'mail.webfarben.net',
|
||||
'username' => 'hkw@webfarben.net',
|
||||
'password' => 'B^o16ei32', // ACHTUNG: Bitte in .env auslagern!
|
||||
'port' => '465',
|
||||
'secure' => 'ssl',
|
||||
'auth' => true
|
||||
]);
|
||||
|
||||
// reCAPTCHA Secret Key
|
||||
// WARNUNG: Sollte in Umgebungsvariablen gespeichert werden!
|
||||
define('RECAPTCHA_SECRET', '6LfXn6kqAAAAAJGZ3H371N7kFeNYj7-HW8osLgLq');
|
||||
|
||||
// Sicherheitseinstellungen
|
||||
define('RATE_LIMIT_MAX', 5);
|
||||
define('RATE_LIMIT_WINDOW', 3600);
|
||||
define('MIN_FORM_TIME', 5);
|
||||
define('MAX_FILE_SIZE', 20 * 1024 * 1024);
|
||||
|
||||
// Erlaubte Dateitypen
|
||||
define('ALLOWED_FILE_TYPES', ['pdf', 'doc', 'docx', 'jpg', 'jpeg', 'png']);
|
||||
|
||||
// Spam-Wort-Blacklist
|
||||
define('SPAM_WORDS', [
|
||||
'casino', 'viagra', 'lottery', 'winner', 'cialis',
|
||||
'sex', 'porn', 'loan', 'bitcoin', 'escort', 'xxx',
|
||||
'pharmacy', 'earn money', 'credit', 'crypto'
|
||||
]);
|
||||
|
||||
// E-Mail-Domain-Blacklist
|
||||
define('SPAM_DOMAINS', [
|
||||
'mail.ru', 'yopmail.com', 'tempmail', 'trashmail',
|
||||
'10minutemail', 'guerrillamail', 'mailinator'
|
||||
]);
|
||||
261
forms/contact.php
Normal file
261
forms/contact.php
Normal file
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
// Fehlerausgabe komplett deaktivieren
|
||||
error_reporting(0);
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', '/path/to/error.log'); // Optional: Fehler in Log-Datei schreiben
|
||||
|
||||
// Buffer starten
|
||||
ob_start();
|
||||
|
||||
// Sicherheitskonstante setzen
|
||||
define('CONFIG_LOADED', true);
|
||||
|
||||
// Config laden (prüfe zuerst außerhalb des webroot)
|
||||
if (file_exists(__DIR__ . '/../config.php')) {
|
||||
require_once __DIR__ . '/../config.php';
|
||||
} elseif (file_exists(__DIR__ . '/config.php')) {
|
||||
require_once __DIR__ . '/config.php';
|
||||
} else {
|
||||
die('Configuration file not found');
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../assets/vendor/php-email-form/php-email-form.php';
|
||||
|
||||
// Rate Limiting mit Konstanten aus Config
|
||||
function checkRateLimit() {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
$file = sys_get_temp_dir() . '/email_' . hash('sha256', $ip . date('Y-m-d-H'));
|
||||
|
||||
if (file_exists($file)) {
|
||||
$data = json_decode(file_get_contents($file), true);
|
||||
$now = time();
|
||||
|
||||
// Max RATE_LIMIT_MAX E-Mails pro RATE_LIMIT_WINDOW
|
||||
if ($data['count'] >= RATE_LIMIT_MAX && ($now - $data['timestamp']) < RATE_LIMIT_WINDOW) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($now - $data['timestamp']) >= RATE_LIMIT_WINDOW) {
|
||||
$data = ['count' => 1, 'timestamp' => $now];
|
||||
} else {
|
||||
$data['count']++;
|
||||
}
|
||||
} else {
|
||||
$data = ['count' => 1, 'timestamp' => time()];
|
||||
}
|
||||
|
||||
file_put_contents($file, json_encode($data), LOCK_EX);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Erweiterte Validierung mit XSS-Schutz
|
||||
function validateInput($data) {
|
||||
// Leere Felder prüfen
|
||||
if (empty($data['name']) || empty($data['email']) || empty($data['message'])) {
|
||||
return ['valid' => false, 'message' => 'Bitte füllen Sie alle Pflichtfelder aus.'];
|
||||
}
|
||||
|
||||
// E-Mail-Format prüfen
|
||||
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
|
||||
return ['valid' => false, 'message' => 'Bitte geben Sie eine gültige E-Mail-Adresse ein.'];
|
||||
}
|
||||
|
||||
// Zusätzliche E-Mail-Validierung (keine Header-Injection)
|
||||
if (preg_match('/[\r\n]/', $data['email']) || preg_match('/[\r\n]/', $data['name'])) {
|
||||
return ['valid' => false, 'message' => 'Ungültige Zeichen erkannt.'];
|
||||
}
|
||||
|
||||
// Längenbeschränkungen
|
||||
if (strlen($data['name']) > 100) {
|
||||
return ['valid' => false, 'message' => 'Der Name ist zu lang.'];
|
||||
}
|
||||
|
||||
if (strlen($data['message']) < 10) {
|
||||
return ['valid' => false, 'message' => 'Ihre Nachricht ist zu kurz.'];
|
||||
}
|
||||
|
||||
if (strlen($data['message']) > 5000) {
|
||||
return ['valid' => false, 'message' => 'Ihre Nachricht ist zu lang.'];
|
||||
}
|
||||
|
||||
// Spam-Wörter aus Config prüfen
|
||||
foreach (SPAM_WORDS as $word) {
|
||||
if (stripos($data['message'], $word) !== false || stripos($data['name'], $word) !== false) {
|
||||
return ['valid' => false, 'message' => 'Ihre Nachricht wurde als Spam erkannt.'];
|
||||
}
|
||||
}
|
||||
|
||||
return ['valid' => true];
|
||||
}
|
||||
|
||||
// XSS-Schutz für Ausgaben
|
||||
function sanitizeOutput($string) {
|
||||
return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
// reCAPTCHA Validierung mit Config-Konstante
|
||||
function validateRecaptcha($recaptchaResponse) {
|
||||
if (empty($recaptchaResponse)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$secret = RECAPTCHA_SECRET;
|
||||
$url = 'https://www.google.com/recaptcha/api/siteverify';
|
||||
$data = [
|
||||
'secret' => $secret,
|
||||
'response' => $recaptchaResponse,
|
||||
'remoteip' => $_SERVER['REMOTE_ADDR']
|
||||
];
|
||||
|
||||
$options = [
|
||||
'http' => [
|
||||
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
|
||||
'method' => 'POST',
|
||||
'content' => http_build_query($data),
|
||||
'timeout' => 10
|
||||
],
|
||||
'ssl' => [
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
]
|
||||
];
|
||||
|
||||
$context = stream_context_create($options);
|
||||
$result = @file_get_contents($url, false, $context);
|
||||
|
||||
if ($result === false) {
|
||||
error_log('reCAPTCHA API request failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
$responseData = json_decode($result);
|
||||
return isset($responseData->success) && $responseData->success === true;
|
||||
}
|
||||
|
||||
// Honeypot-Feld prüfen
|
||||
if (!empty($_POST['website'])) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Spam erkannt.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Zeitbasierte Validierung mit Config-Konstante
|
||||
if (isset($_POST['form_start'])) {
|
||||
$delta = time() - intval($_POST['form_start'] / 1000);
|
||||
if ($delta < MIN_FORM_TIME) { // Zu schnell ausgefüllt
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Zu schnell abgesendet. Bitte versuchen Sie es erneut.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Hauptlogik
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['success' => false, 'message' => 'Nur POST-Requests erlaubt']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// Rate Limit prüfen
|
||||
if (!checkRateLimit()) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Aus Sicherheitsgründen können Sie nur 5 Nachrichten pro Stunde senden. Bitte versuchen Sie es später erneut.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// reCAPTCHA prüfen
|
||||
$recaptchaResponse = $_POST['g-recaptcha-response'] ?? '';
|
||||
if (!validateRecaptcha($recaptchaResponse)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Bitte bestätigen Sie, dass Sie kein Robot sind.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Formulardaten sammeln und bereinigen
|
||||
$formData = [
|
||||
'name' => trim($_POST['name'] ?? ''),
|
||||
'email' => trim($_POST['email'] ?? ''),
|
||||
'mobile' => trim($_POST['mobilenumber'] ?? ''),
|
||||
'message' => trim($_POST['message'] ?? '')
|
||||
];
|
||||
|
||||
|
||||
// E-Mail-Domain-Blacklist aus Config
|
||||
foreach (SPAM_DOMAINS as $domain) {
|
||||
if (stripos($formData['email'], $domain) !== false) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Bitte verwenden Sie eine echte E-Mail-Adresse.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Eingaben validieren
|
||||
$validation = validateInput($formData);
|
||||
if (!$validation['valid']) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $validation['message']
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// E-Mail erstellen
|
||||
$contact = new PHP_Email_Form;
|
||||
$contact->ajax = true;
|
||||
$contact->to = RECEIVING_EMAIL;
|
||||
$contact->from_name = $formData['name'];
|
||||
$contact->from_email = 'hkw@webfarben.net';
|
||||
$contact->reply_to = $formData['email'];
|
||||
$contact->subject = "Neue Kontaktanfrage von " . $formData['name'];
|
||||
$contact->smtp = SMTP_CONFIG;
|
||||
|
||||
// Nachricht zusammenbauen
|
||||
$contact->add_message($formData['name'], 'Name');
|
||||
$contact->add_message($formData['email'], 'E-Mail');
|
||||
$contact->add_message($formData['mobile'], 'Telefon');
|
||||
$contact->add_message($formData['message'], 'Nachricht');
|
||||
|
||||
// Dateianhang verarbeiten mit Sicherheitschecks
|
||||
if (isset($_FILES['resume']) && $_FILES['resume']['error'] == UPLOAD_ERR_OK) {
|
||||
// Dateigröße prüfen
|
||||
if ($_FILES['resume']['size'] > MAX_FILE_SIZE) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Die Datei ist zu groß (max. 20 MB).'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$contact->add_attachment('resume', 20, ALLOWED_FILE_TYPES);
|
||||
}
|
||||
|
||||
// E-Mail senden
|
||||
$result = $contact->send();
|
||||
|
||||
echo json_encode([
|
||||
'success' => ($result === 'OK'),
|
||||
'message' => ($result === 'OK')
|
||||
? "Ihre Nachricht wurde erfolgreich gesendet an: " . RECEIVING_EMAIL
|
||||
: "Fehler beim Senden: " . $result
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Mailer Error: " . $e->getMessage());
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => "Ein Fehler ist aufgetreten: " . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
78
forms/test-form.php
Normal file
78
forms/test-form.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Kontaktformular Test</title>
|
||||
<style>
|
||||
.success-message { color: green; padding: 10px; }
|
||||
.error-message { color: red; padding: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Kontaktformular Test</h2>
|
||||
|
||||
<div id="response-message"></div>
|
||||
|
||||
<form action="contact.php" method="POST" enctype="multipart/form-data">
|
||||
<p>
|
||||
<label>Name:</label><br>
|
||||
<input type="text" name="name" value="Test Person">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>E-Mail:</label><br>
|
||||
<input type="email" name="email" value="test@example.com">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>Telefon:</label><br>
|
||||
<input type="text" name="mobilenumber" value="0123456789">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>Nachricht:</label><br>
|
||||
<textarea name="message">Dies ist eine Test-Nachricht</textarea>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>Anhang (optional):</label><br>
|
||||
<input type="file" name="resume">
|
||||
</p>
|
||||
|
||||
<button type="submit">Senden</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.querySelector('form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let formData = new FormData(this);
|
||||
|
||||
fetch('contact.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const messageDiv = document.getElementById('response-message');
|
||||
messageDiv.className = data.success ? 'success-message' : 'error-message';
|
||||
messageDiv.textContent = data.message;
|
||||
|
||||
if (data.success) {
|
||||
// Optional: Formular zurücksetzen
|
||||
this.reset();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
const messageDiv = document.getElementById('response-message');
|
||||
messageDiv.className = 'error-message';
|
||||
messageDiv.textContent = 'Ein Fehler ist aufgetreten';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
26
forms/test-paths.php
Normal file
26
forms/test-paths.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
$vendorDir = dirname(__DIR__) . '/assets/vendor';
|
||||
$files = [
|
||||
'/phpmailer/src/Exception.php',
|
||||
'/phpmailer/src/PHPMailer.php',
|
||||
'/phpmailer/src/SMTP.php'
|
||||
];
|
||||
|
||||
echo "<h2>Pfad-Test</h2>";
|
||||
echo "Vendor Directory: " . $vendorDir . "<br><br>";
|
||||
|
||||
foreach ($files as $file) {
|
||||
$fullPath = $vendorDir . $file;
|
||||
echo "Checking: " . $fullPath . "<br>";
|
||||
if (file_exists($fullPath)) {
|
||||
echo "✅ Datei existiert<br>";
|
||||
echo "Dateirechte: " . substr(sprintf('%o', fileperms($fullPath)), -4) . "<br>";
|
||||
echo "Dateigröße: " . filesize($fullPath) . " bytes<br>";
|
||||
} else {
|
||||
echo "❌ Datei nicht gefunden<br>";
|
||||
}
|
||||
echo "<br>";
|
||||
}
|
||||
Reference in New Issue
Block a user