DEV Community

ahmadasroni38
ahmadasroni38

Posted on

πŸ›‘οΈ Keamanan Website: Membangun Fondasi Pemahaman Keamanan Web

Mata Kuliah: Keamanan Siber - Fokus Ekosistem Website

Pertemuan: 1 dari 14

Pengajar: ahmadasroni38

Tanggal: 2025-07-17 06:25:44

Durasi: 3 jam (180 menit)

Level: Pemula


πŸ“‹ Daftar Isi

  1. Tujuan Pembelajaran
  2. Pengantar Keamanan Web
  3. Arsitektur Aplikasi Web
  4. Keamanan Frontend
  5. Keamanan Backend
  6. Keamanan Database
  7. Keamanan API
  8. Studi Kasus
  9. Praktikum
  10. Evaluasi

🎯 Tujuan Pembelajaran

Setelah menyelesaikan pertemuan ini, mahasiswa diharapkan dapat:

  1. Memahami konsep dasar keamanan aplikasi web
  2. Mengidentifikasi komponen-komponen arsitektur web modern
  3. Menjelaskan ancaman keamanan pada setiap layer aplikasi
  4. Mengimplementasikan praktik keamanan dasar
  5. Menganalisis kerentanan keamanan melalui studi kasus

🌐 Pengantar Keamanan Web

Apa itu Keamanan Web?

Keamanan web adalah praktik melindungi aplikasi web dan data dari ancaman cyber. Ini meliputi:

  • Perlindungan data: Menjaga kerahasiaan informasi pengguna
  • Integritas sistem: Memastikan data tidak diubah tanpa izin
  • Ketersediaan layanan: Menjamin sistem dapat diakses kapan saja
  • Otentikasi: Memverifikasi identitas pengguna
  • Otorisasi: Mengontrol akses ke resource

Mengapa Keamanan Web Penting?

  1. Melindungi data pribadi pengguna
  2. Menjaga reputasi perusahaan
  3. Mencegah kerugian finansial
  4. Mematuhi regulasi yang berlaku
  5. Mempertahankan kepercayaan pengguna

Ancaman Keamanan Web Umum

  • Cross-Site Scripting (XSS)
  • SQL Injection
  • Cross-Site Request Forgery (CSRF)
  • Session Hijacking
  • Broken Authentication
  • Insecure Direct Object References

πŸ—οΈ Arsitektur Aplikasi Web

Komponen Utama Aplikasi Web

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    CLIENT/BROWSER                           β”‚
β”‚                 (HTML, CSS, JavaScript)                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚ HTTP/HTTPS Request
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    WEB SERVER                               β”‚
β”‚                 (Apache, Nginx, IIS)                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚ Process Request
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                APPLICATION SERVER                           β”‚
β”‚            (Node.js, PHP, Python, Java)                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚ Database Query
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    DATABASE                                 β”‚
β”‚               (MySQL, PostgreSQL, MongoDB)                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Penjelasan Setiap Layer

1. Client/Browser Layer

  • Fungsi: Menampilkan interface ke pengguna
  • Komponen: HTML, CSS, JavaScript
  • Kerentanan: XSS, CSRF, Client-side injection

2. Web Server Layer

  • Fungsi: Menerima dan memproses HTTP request
  • Komponen: Apache, Nginx, IIS
  • Kerentanan: Server misconfiguration, DDoS

3. Application Server Layer

  • Fungsi: Menjalankan business logic aplikasi
  • Komponen: Node.js, PHP, Python, Java
  • Kerentanan: Authentication bypass, Logic flaws

4. Database Layer

  • Fungsi: Menyimpan dan mengelola data
  • Komponen: MySQL, PostgreSQL, MongoDB
  • Kerentanan: SQL injection, Data exposure

πŸ–₯️ Keamanan Frontend

Konsep Dasar Frontend Security

Frontend adalah bagian aplikasi yang berinteraksi langsung dengan pengguna. Keamanan frontend fokus pada:

  1. Input Validation: Memvalidasi data yang dimasukkan pengguna
  2. Output Encoding: Mengamankan data yang ditampilkan
  3. Client-side Authentication: Mengelola status login
  4. Secure Communication: Komunikasi aman dengan server

Ancaman Keamanan Frontend

1. Cross-Site Scripting (XSS)

Definisi: Serangan yang menyisipkan script berbahaya ke dalam halaman web.

Contoh Vulnerable Code:

<!DOCTYPE html>
<html>
<head>
    <title>XSS Vulnerability Demo</title>
</head>
<body>
    <h1>Welcome Message</h1>
    <div id="message"></div>

    <script>
        // ❌ VULNERABLE CODE
        function displayMessage() {
            // Mengambil parameter dari URL
            const urlParams = new URLSearchParams(window.location.search);
            const message = urlParams.get('msg');

            // Langsung memasukkan ke DOM tanpa sanitization
            document.getElementById('message').innerHTML = message;
        }

        displayMessage();
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Serangan:

http://example.com/page.html?msg=<script>alert('XSS Attack!')</script>
Enter fullscreen mode Exit fullscreen mode

Solusi:

<!DOCTYPE html>
<html>
<head>
    <title>XSS Protection Demo</title>
</head>
<body>
    <h1>Welcome Message</h1>
    <div id="message"></div>

    <script>
        // βœ… SECURE CODE
        function displayMessage() {
            const urlParams = new URLSearchParams(window.location.search);
            const message = urlParams.get('msg');

            if (message) {
                // Sanitize input dengan escape HTML characters
                const sanitizedMessage = escapeHtml(message);
                document.getElementById('message').innerHTML = sanitizedMessage;
            }
        }

        // Function untuk escape HTML characters
        function escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }

        displayMessage();
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

2. Input Validation

Pentingnya Input Validation:

  • Mencegah data berbahaya masuk ke sistem
  • Memastikan data sesuai format yang diharapkan
  • Mengurangi beban server processing

Contoh Implementasi:

<!DOCTYPE html>
<html>
<head>
    <title>Input Validation Demo</title>
    <style>
        .error { color: red; font-size: 12px; }
        .success { color: green; font-size: 12px; }
        .form-group { margin: 10px 0; }
        input, textarea { padding: 8px; width: 300px; }
        button { padding: 10px 20px; background: #007bff; color: white; border: none; }
    </style>
</head>
<body>
    <h1>Secure Contact Form</h1>

    <form id="contactForm">
        <div class="form-group">
            <label for="name">Nama:</label><br>
            <input type="text" id="name" name="name" maxlength="50" required>
            <div class="error" id="nameError"></div>
        </div>

        <div class="form-group">
            <label for="email">Email:</label><br>
            <input type="email" id="email" name="email" maxlength="100" required>
            <div class="error" id="emailError"></div>
        </div>

        <div class="form-group">
            <label for="phone">Telepon:</label><br>
            <input type="tel" id="phone" name="phone" maxlength="15">
            <div class="error" id="phoneError"></div>
        </div>

        <div class="form-group">
            <label for="message">Pesan:</label><br>
            <textarea id="message" name="message" rows="4" maxlength="500" required></textarea>
            <div class="error" id="messageError"></div>
        </div>

        <button type="submit">Kirim Pesan</button>
    </form>

    <div id="result"></div>

    <script>
        // Validation functions
        function validateName(name) {
            const nameRegex = /^[a-zA-Z\s]+$/;

            if (!name.trim()) {
                return 'Nama harus diisi';
            }

            if (name.length < 2) {
                return 'Nama minimal 2 karakter';
            }

            if (name.length > 50) {
                return 'Nama maksimal 50 karakter';
            }

            if (!nameRegex.test(name)) {
                return 'Nama hanya boleh mengandung huruf dan spasi';
            }

            return '';
        }

        function validateEmail(email) {
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

            if (!email.trim()) {
                return 'Email harus diisi';
            }

            if (!emailRegex.test(email)) {
                return 'Format email tidak valid';
            }

            return '';
        }

        function validatePhone(phone) {
            const phoneRegex = /^[0-9+\-\s()]+$/;

            if (phone && !phoneRegex.test(phone)) {
                return 'Nomor telepon tidak valid';
            }

            return '';
        }

        function validateMessage(message) {
            if (!message.trim()) {
                return 'Pesan harus diisi';
            }

            if (message.length < 10) {
                return 'Pesan minimal 10 karakter';
            }

            if (message.length > 500) {
                return 'Pesan maksimal 500 karakter';
            }

            return '';
        }

        // Real-time validation
        document.getElementById('name').addEventListener('input', function() {
            const error = validateName(this.value);
            document.getElementById('nameError').textContent = error;
        });

        document.getElementById('email').addEventListener('input', function() {
            const error = validateEmail(this.value);
            document.getElementById('emailError').textContent = error;
        });

        document.getElementById('phone').addEventListener('input', function() {
            const error = validatePhone(this.value);
            document.getElementById('phoneError').textContent = error;
        });

        document.getElementById('message').addEventListener('input', function() {
            const error = validateMessage(this.value);
            document.getElementById('messageError').textContent = error;
        });

        // Form submission
        document.getElementById('contactForm').addEventListener('submit', function(e) {
            e.preventDefault();

            // Get form data
            const formData = {
                name: document.getElementById('name').value,
                email: document.getElementById('email').value,
                phone: document.getElementById('phone').value,
                message: document.getElementById('message').value
            };

            // Validate all fields
            const errors = {
                name: validateName(formData.name),
                email: validateEmail(formData.email),
                phone: validatePhone(formData.phone),
                message: validateMessage(formData.message)
            };

            // Display errors
            document.getElementById('nameError').textContent = errors.name;
            document.getElementById('emailError').textContent = errors.email;
            document.getElementById('phoneError').textContent = errors.phone;
            document.getElementById('messageError').textContent = errors.message;

            // Check if there are any errors
            const hasErrors = Object.values(errors).some(error => error !== '');

            if (!hasErrors) {
                // Sanitize data before sending
                const sanitizedData = {
                    name: sanitizeInput(formData.name),
                    email: sanitizeInput(formData.email),
                    phone: sanitizeInput(formData.phone),
                    message: sanitizeInput(formData.message)
                };

                // Simulate form submission
                document.getElementById('result').innerHTML = `
                    <h2>Form Submitted Successfully!</h2>
                    <p><strong>Nama:</strong> ${sanitizedData.name}</p>
                    <p><strong>Email:</strong> ${sanitizedData.email}</p>
                    <p><strong>Telepon:</strong> ${sanitizedData.phone}</p>
                    <p><strong>Pesan:</strong> ${sanitizedData.message}</p>
                    <p><strong>Processed by:</strong> ahmadasroni38</p>
                    <p><strong>Time:</strong> ${new Date().toLocaleString()}</p>
                `;

                // Reset form
                this.reset();
            }
        });

        // Sanitization function
        function sanitizeInput(input) {
            return input.trim()
                       .replace(/</g, '&lt;')
                       .replace(/>/g, '&gt;')
                       .replace(/"/g, '&quot;')
                       .replace(/'/g, '&#x27;')
                       .replace(/\//g, '&#x2F;');
        }

        console.log('Contact Form loaded by ahmadasroni38 at', new Date());
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

πŸ”’ Keamanan Backend

Konsep Dasar Backend Security

Backend adalah server yang memproses request dari client. Keamanan backend meliputi:

  1. Authentication: Memverifikasi identitas pengguna
  2. Authorization: Mengontrol akses ke resource
  3. Input Validation: Validasi data dari client
  4. Session Management: Mengelola sesi pengguna
  5. Error Handling: Menangani error dengan aman

Implementasi Authentication

Contoh Simple Authentication System:

// simple-auth.js
// Simple authentication system
// Author: ahmadasroni38
// Date: 2025-07-17 06:25:44

const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const app = express();

// Middleware
app.use(express.json());
app.use(express.static('public'));

// Simple user database (in production, use real database)
const users = [
    {
        id: 1,
        username: 'admin',
        email: 'admin@example.com',
        password: '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // "password"
        role: 'admin'
    },
    {
        id: 2,
        username: 'user',
        email: 'user@example.com',
        password: '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // "password"
        role: 'user'
    }
];

const JWT_SECRET = 'your-secret-key-here';

// Login endpoint
app.post('/api/login', async (req, res) => {
    try {
        const { username, password } = req.body;

        // Input validation
        if (!username || !password) {
            return res.status(400).json({
                success: false,
                message: 'Username dan password harus diisi'
            });
        }

        // Find user
        const user = users.find(u => u.username === username);
        if (!user) {
            return res.status(401).json({
                success: false,
                message: 'Username atau password salah'
            });
        }

        // Verify password
        const isValidPassword = await bcrypt.compare(password, user.password);
        if (!isValidPassword) {
            return res.status(401).json({
                success: false,
                message: 'Username atau password salah'
            });
        }

        // Generate JWT token
        const token = jwt.sign(
            { 
                id: user.id, 
                username: user.username,
                role: user.role 
            },
            JWT_SECRET,
            { expiresIn: '1h' }
        );

        res.json({
            success: true,
            message: 'Login berhasil',
            token: token,
            user: {
                id: user.id,
                username: user.username,
                email: user.email,
                role: user.role
            }
        });

        console.log(`Login successful for user: ${username} by ahmadasroni38`);

    } catch (error) {
        console.error('Login error:', error);
        res.status(500).json({
            success: false,
            message: 'Server error'
        });
    }
});

// Authentication middleware
const authenticateToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];

    if (!token) {
        return res.status(401).json({
            success: false,
            message: 'Access token required'
        });
    }

    jwt.verify(token, JWT_SECRET, (err, user) => {
        if (err) {
            return res.status(403).json({
                success: false,
                message: 'Invalid or expired token'
            });
        }

        req.user = user;
        next();
    });
};

// Protected endpoint
app.get('/api/profile', authenticateToken, (req, res) => {
    const user = users.find(u => u.id === req.user.id);

    if (!user) {
        return res.status(404).json({
            success: false,
            message: 'User not found'
        });
    }

    res.json({
        success: true,
        user: {
            id: user.id,
            username: user.username,
            email: user.email,
            role: user.role
        }
    });
});

// Input validation and sanitization
app.post('/api/contact', (req, res) => {
    const { name, email, message } = req.body;

    // Input validation
    const errors = [];

    if (!name || name.length < 2) {
        errors.push('Nama minimal 2 karakter');
    }

    if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
        errors.push('Email tidak valid');
    }

    if (!message || message.length < 10) {
        errors.push('Pesan minimal 10 karakter');
    }

    if (errors.length > 0) {
        return res.status(400).json({
            success: false,
            message: 'Validation error',
            errors: errors
        });
    }

    // Sanitize input
    const sanitizedData = {
        name: name.trim().replace(/[<>]/g, ''),
        email: email.trim().toLowerCase(),
        message: message.trim().replace(/[<>]/g, '')
    };

    // Log contact submission
    console.log('Contact form submitted:', sanitizedData);
    console.log('Processed by: ahmadasroni38');
    console.log('Time:', new Date().toISOString());

    res.json({
        success: true,
        message: 'Pesan berhasil dikirim',
        data: sanitizedData
    });
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
    console.log(`Started by: ahmadasroni38`);
    console.log(`Time: ${new Date().toISOString()}`);
});
Enter fullscreen mode Exit fullscreen mode

πŸ—„οΈ Keamanan Database

Konsep Dasar Database Security

Database security meliputi:

  1. Access Control: Mengontrol akses ke database
  2. Data Encryption: Mengenkripsi data sensitif
  3. SQL Injection Prevention: Mencegah serangan SQL injection
  4. Backup Security: Backup data dengan aman
  5. Audit Logging: Mencatat aktivitas database

SQL Injection Prevention

Contoh Vulnerable Code:

// ❌ VULNERABLE - SQL Injection
app.get('/api/user/:id', (req, res) => {
    const userId = req.params.id;

    // Dangerous - String concatenation
    const query = `SELECT * FROM users WHERE id = ${userId}`;

    db.query(query, (err, results) => {
        if (err) {
            return res.status(500).json({ error: 'Database error' });
        }
        res.json(results);
    });
});

// Attack: /api/user/1 OR 1=1
// Query becomes: SELECT * FROM users WHERE id = 1 OR 1=1
// Returns all users!
Enter fullscreen mode Exit fullscreen mode

Secure Code:

// βœ… SECURE - Prepared Statements
app.get('/api/user/:id', (req, res) => {
    const userId = req.params.id;

    // Input validation
    if (!userId || isNaN(userId)) {
        return res.status(400).json({
            success: false,
            message: 'Invalid user ID'
        });
    }

    // Use prepared statement
    const query = 'SELECT id, username, email FROM users WHERE id = ?';

    db.query(query, [userId], (err, results) => {
        if (err) {
            console.error('Database error:', err);
            return res.status(500).json({
                success: false,
                message: 'Database error'
            });
        }

        if (results.length === 0) {
            return res.status(404).json({
                success: false,
                message: 'User not found'
            });
        }

        res.json({
            success: true,
            user: results[0]
        });
    });
});
Enter fullscreen mode Exit fullscreen mode

Database Setup dengan Security

-- Secure database setup
-- Author: ahmadasroni38
-- Date: 2025-07-17 06:25:44

-- Create database
CREATE DATABASE secure_app;
USE secure_app;

-- Create users table
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    role ENUM('admin', 'user') DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- Create sessions table
CREATE TABLE sessions (
    id VARCHAR(255) PRIMARY KEY,
    user_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- Create audit log table
CREATE TABLE audit_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    action VARCHAR(100) NOT NULL,
    table_name VARCHAR(50),
    record_id INT,
    ip_address VARCHAR(45),
    user_agent TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);

-- Create application user dengan limited privileges
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_123';
GRANT SELECT, INSERT, UPDATE, DELETE ON secure_app.* TO 'app_user'@'localhost';

-- Sample data
INSERT INTO users (username, email, password, role) VALUES
('admin', 'admin@example.com', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin'),
('user', 'user@example.com', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'user');
Enter fullscreen mode Exit fullscreen mode

πŸ”— Keamanan API

API Security Best Practices

  1. Authentication: Verifikasi identitas pengguna
  2. Authorization: Kontrol akses ke resource
  3. Rate Limiting: Mencegah abuse
  4. Input Validation: Validasi semua input
  5. HTTPS: Enkripsi data in transit
  6. API Versioning: Versioning untuk backward compatibility

Implementasi API Security

// api-security.js
// API Security implementation
// Author: ahmadasroni38
// Date: 2025-07-17 06:25:44

const express = require('express');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const cors = require('cors');

const app = express();

// Security middleware
app.use(helmet()); // Set security headers
app.use(cors({
    origin: ['http://localhost:3000', 'https://yourdomain.com'],
    credentials: true
}));

// Rate limiting
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // limit each IP to 100 requests per windowMs
    message: {
        success: false,
        message: 'Too many requests, please try again later.',
        timestamp: new Date().toISOString()
    },
    standardHeaders: true,
    legacyHeaders: false,
});

app.use('/api/', limiter);

// Special rate limiting for auth endpoints
const authLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5, // limit each IP to 5 requests per windowMs
    message: {
        success: false,
        message: 'Too many authentication attempts, please try again later.',
        timestamp: new Date().toISOString()
    }
});

app.use('/api/auth/', authLimiter);

// Body parsing dengan size limit
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

// Request logging middleware
app.use((req, res, next) => {
    console.log(`${req.method} ${req.path} - ${req.ip} - ${new Date().toISOString()}`);
    next();
});

// Input validation middleware
const validateInput = (req, res, next) => {
    const { body, query, params } = req;

    // Check for potential XSS
    const checkXSS = (value) => {
        if (typeof value === 'string') {
            const xssPattern = /<script|javascript:|onload=|onerror=/i;
            return xssPattern.test(value);
        }
        return false;
    };

    // Check all inputs
    const allInputs = { ...body, ...query, ...params };
    for (const key in allInputs) {
        if (checkXSS(allInputs[key])) {
            return res.status(400).json({
                success: false,
                message: 'Potentially malicious input detected',
                timestamp: new Date().toISOString()
            });
        }
    }

    next();
};

app.use('/api/', validateInput);

// Error handling middleware
app.use((err, req, res, next) => {
    console.error(`Error: ${err.message}`);
    console.error(`Stack: ${err.stack}`);
    console.error(`User: ahmadasroni38`);
    console.error(`Time: ${new Date().toISOString()}`);

    res.status(500).json({
        success: false,
        message: 'Internal server error',
        timestamp: new Date().toISOString()
    });
});

// API endpoints
app.get('/api/status', (req, res) => {
    res.json({
        success: true,
        message: 'API is running',
        timestamp: new Date().toISOString(),
        author: 'ahmadasroni38'
    });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`API Server running on port ${PORT}`);
    console.log(`Started by: ahmadasroni38`);
    console.log(`Time: ${new Date().toISOString()}`);
});
Enter fullscreen mode Exit fullscreen mode

πŸ“š Studi Kasus

Kasus 1: Toko Online Sederhana

Skenario: Sebuah toko online kecil mengalami serangan yang mencuri data pelanggan.

Analisis Kerentanan:

  1. XSS di Search Function
<!-- Vulnerable search function -->
<script>
function searchProducts() {
    const query = document.getElementById('searchInput').value;
    // Langsung memasukkan ke DOM tanpa sanitasi
    document.getElementById('searchResults').innerHTML = 
        '<h3>Hasil pencarian untuk: ' + query + '</h3>';
}
</script>
Enter fullscreen mode Exit fullscreen mode

Serangan:

<script>
// Attacker input di search box
alert('XSS Attack! Cookie: ' + document.cookie);
</script>
Enter fullscreen mode Exit fullscreen mode

Solusi:

<script>
function searchProducts() {
    const query = document.getElementById('searchInput').value;

    // Sanitasi input
    const sanitizedQuery = query.replace(/[<>]/g, '');

    // Gunakan textContent untuk mencegah XSS
    document.getElementById('searchResults').textContent = 
        'Hasil pencarian untuk: ' + sanitizedQuery;
}
</script>
Enter fullscreen mode Exit fullscreen mode
  1. SQL Injection di Login
// Vulnerable login code
$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
Enter fullscreen mode Exit fullscreen mode

Serangan:

Username: admin' OR '1'='1
Password: anything
Enter fullscreen mode Exit fullscreen mode

Solusi:

// Secure login code
$username = $_POST['username'];
$password = $_POST['password'];

$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();
Enter fullscreen mode Exit fullscreen mode

Kasus 2: Blog Pribadi

Skenario: Blog pribadi yang memiliki sistem komentar mengalami spam dan konten berbahaya.

Problem: Komentar tidak divalidasi dan disanitasi dengan baik.

Vulnerable Code:

<!DOCTYPE html>
<html>
<head>
    <title>Blog Comment System</title>
</head>
<body>
    <h1>Komentar</h1>
    <form id="commentForm">
        <textarea id="comment" placeholder="Tulis komentar..."></textarea>
        <button type="submit">Kirim</button>
    </form>

    <div id="comments"></div>

    <script>
        let comments = [];

        document.getElementById('commentForm').addEventListener('submit', function(e) {
            e.preventDefault();
            const comment = document.getElementById('comment').value;

            // ❌ VULNERABLE - No validation/sanitization
            comments.push(comment);
            displayComments();
        });

        function displayComments() {
            const commentsDiv = document.getElementById('comments');
            commentsDiv.innerHTML = '';

            comments.forEach(comment => {
                // ❌ VULNERABLE - Direct HTML insertion
                commentsDiv.innerHTML += '<div>' + comment + '</div>';
            });
        }
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Secure Solution:

<!DOCTYPE html>
<html>
<head>
    <title>Secure Blog Comment System</title>
    <style>
        .error { color: red; }
        .comment { border: 1px solid #ccc; padding: 10px; margin: 10px 0; }
        .comment-meta { font-size: 12px; color: #666; }
    </style>
</head>
<body>
    <h1>Komentar</h1>
    <form id="commentForm">
        <div>
            <label for="name">Nama:</label>
            <input type="text" id="name" maxlength="50" required>
            <div class="error" id="nameError"></div>
        </div>

        <div>
            <label for="email">Email:</label>
            <input type="email" id="email" maxlength="100" required>
            <div class="error" id="emailError"></div>
        </div>

        <div>
            <label for="comment">Komentar:</label>
            <textarea id="comment" maxlength="1000" required></textarea>
            <div class="error" id="commentError"></div>
        </div>

        <button type="submit">Kirim</button>
    </form>

    <div id="comments"></div>

    <script>
        let comments = [];

        // Validation functions
        function validateName(name) {
            if (!name.trim()) return 'Nama harus diisi';
            if (name.length < 2) return 'Nama minimal 2 karakter';
            if (!/^[a-zA-Z\s]+$/.test(name)) return 'Nama hanya boleh huruf dan spasi';
            return '';
        }

        function validateEmail(email) {
            if (!email.trim()) return 'Email harus diisi';
            if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return 'Format email tidak valid';
            return '';
        }

        function validateComment(comment) {
            if (!comment.trim()) return 'Komentar harus diisi';
            if (comment.length < 10) return 'Komentar minimal 10 karakter';
            return '';
        }

        // Sanitization function
        function sanitizeInput(input) {
            return input.trim()
                       .replace(/</g, '&lt;')
                       .replace(/>/g, '&gt;')
                       .replace(/"/g, '&quot;')
                       .replace(/'/g, '&#x27;');
        }

        // Form submission
        document.getElementById('commentForm').addEventListener('submit', function(e) {
            e.preventDefault();

            const name = document.getElementById('name').value;
            const email = document.getElementById('email').value;
            const comment = document.getElementById('comment').value;

            // Validate inputs
            const nameError = validateName(name);
            const emailError = validateEmail(email);
            const commentError = validateComment(comment);

            document.getElementById('nameError').textContent = nameError;
            document.getElementById('emailError').textContent = emailError;
            document.getElementById('commentError').textContent = commentError;

            // If no errors, add comment
            if (!nameError && !emailError && !commentError) {
                const sanitizedComment = {
                    name: sanitizeInput(name),
                    email: sanitizeInput(email),
                    comment: sanitizeInput(comment),
                    timestamp: new Date().toLocaleString(),
                    author: 'ahmadasroni38'
                };

                comments.push(sanitizedComment);
                displayComments();

                // Reset form
                this.reset();
            }
        });

        function displayComments() {
            const commentsDiv = document.getElementById('comments');
            commentsDiv.innerHTML = '';

            comments.forEach(comment => {
                const commentDiv = document.createElement('div');
                commentDiv.className = 'comment';

                // βœ… SECURE - Use textContent instead of innerHTML
                commentDiv.innerHTML = `
                    <strong>${comment.name}</strong>
                    <div class="comment-meta">
                        ${comment.timestamp} | Processed by: ${comment.author}
                    </div>
                    <p>${comment.comment}</p>
                `;

                commentsDiv.appendChild(commentDiv);
            });
        }

        console.log('Secure comment system loaded by ahmadasroni38');
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Praktikum

Praktikum 1: Setup Environment (20 menit)

1.1 Install Prerequisites

# Install Node.js
# Download dari https://nodejs.org/

# Verify installation
node --version
npm --version

# Create project directory
mkdir security-lab
cd security-lab

# Initialize npm project
npm init -y

# Install dependencies
npm install express bcryptjs jsonwebtoken cors helmet express-rate-limit
Enter fullscreen mode Exit fullscreen mode

1.2 Create Project Structure

security-lab/
β”œβ”€β”€ package.json
β”œβ”€β”€ server.js
β”œβ”€β”€ public/
β”‚   β”œβ”€β”€ index.html
β”‚   β”œβ”€β”€ login.html
β”‚   └── dashboard.html
└── README.md
Enter fullscreen mode Exit fullscreen mode

Praktikum 2: Implementasi Basic Security (40 menit)

2.1 Create Server dengan Security

// server.js
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');

const app = express();

// Security middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(express.static('public'));

// Rate limiting
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);

// Simple user database
const users = [
    {
        id: 1,
        username: 'admin',
        password: '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // "password"
        role: 'admin'
    }
];

const JWT_SECRET = 'your-secret-key-here';

// Login endpoint
app.post('/api/login', async (req, res) => {
    try {
        const { username, password } = req.body;

        // Find user
        const user = users.find(u => u.username === username);
        if (!user || !await bcrypt.compare(password, user.password)) {
            return res.status(401).json({
                success: false,
                message: 'Invalid credentials'
            });
        }

        // Generate token
        const token = jwt.sign(
            { id: user.id, username: user.username, role: user.role },
            JWT_SECRET,
            { expiresIn: '1h' }
        );

        res.json({
            success: true,
            token: token,
            user: { id: user.id, username: user.username, role: user.role }
        });

    } catch (error) {
        res.status(500).json({ success: false, message: 'Server error' });
    }
});

// Protected endpoint
app.get('/api/dashboard', (req, res) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];

    if (!token) {
        return res.status(401).json({ success: false, message: 'Token required' });
    }

    try {
        const decoded = jwt.verify(token, JWT_SECRET);
        res.json({
            success: true,
            message: 'Welcome to dashboard',
            user: decoded,
            timestamp: new Date().toISOString(),
            processedBy: 'ahmadasroni38'
        });
    } catch (error) {
        res.status(403).json({ success: false, message: 'Invalid token' });
    }
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
    console.log(`Started by: ahmadasroni38`);
});
Enter fullscreen mode Exit fullscreen mode

2.2 Create Frontend Pages

public/index.html:

<!DOCTYPE html>
<html>
<head>
    <title>Security Lab - Home</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .card { border: 1px solid #ddd; padding: 20px; margin: 20px 0; }
        button { padding: 10px 20px; margin: 10px; }
        .error { color: red; }
        .success { color: green; }
    </style>
</head>
<body>
    <h1>πŸ›‘οΈ Security Lab</h1>
    <p>Author: ahmadasroni38 | Date: 2025-07-17 06:25:44</p>

    <div class="card">
        <h2>Available Pages</h2>
        <button onclick="location.href='/login.html'">Login Page</button>
        <button onclick="location.href='/dashboard.html'">Dashboard</button>
    </div>

    <div class="card">
        <h2>Test XSS Protection</h2>
        <input type="text" id="testInput" placeholder="Try entering <script>alert('XSS')</script>">
        <button onclick="testXSS()">Test Input</button>
        <div id="output"></div>
    </div>

    <script>
        function testXSS() {
            const input = document.getElementById('testInput').value;
            const output = document.getElementById('output');

            // Safe way to display user input
            output.textContent = 'You entered: ' + input;

            console.log('XSS test performed by ahmadasroni38');
        }
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

public/login.html:

<!DOCTYPE html>
<html>
<head>
    <title>Security Lab - Login</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .form-group { margin: 15px 0; }
        input { padding: 8px; width: 200px; }
        button { padding: 10px 20px; }
        .error { color: red; }
        .success { color: green; }
    </style>
</head>
<body>
    <h1>πŸ” Login</h1>
    <p>Author: ahmadasroni38</p>

    <form id="loginForm">
        <div class="form-group">
            <label>Username:</label><br>
            <input type="text" id="username" required>
        </div>

        <div class="form-group">
            <label>Password:</label><br>
            <input type="password" id="password" required>
        </div>

        <button type="submit">Login</button>
    </form>

    <div id="message"></div>

    <p>Test credentials: admin / password</p>

    <script>
        document.getElementById('loginForm').addEventListener('submit', async (e) => {
            e.preventDefault();

            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;

            try {
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ username, password })
                });

                const result = await response.json();
                const messageDiv = document.getElementById('message');

                if (result.success) {
                    localStorage.setItem('token', result.token);
                    messageDiv.innerHTML = '<p class="success">Login successful! Redirecting...</p>';
                    setTimeout(() => {
                        window.location.href = '/dashboard.html';
                    }, 1000);
                } else {
                    messageDiv.innerHTML = '<p class="error">' + result.message + '</p>';
                }

            } catch (error) {
                document.getElementById('message').innerHTML = '<p class="error">Network error</p>';
            }
        });
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

public/dashboard.html:

<!DOCTYPE html>
<html>
<head>
    <title>Security Lab - Dashboard</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .card { border: 1px solid #ddd; padding: 20px; margin: 20px 0; }
        button { padding: 10px 20px; margin: 10px; }
        .error { color: red; }
        .success { color: green; }
    </style>
</head>
<body>
    <h1>πŸ“Š Dashboard</h1>
    <p>Author: ahmadasroni38</p>

    <div class="card">
        <h2>User Information</h2>
        <div id="userInfo">Loading...</div>
    </div>

    <div class="card">
        <h2>Actions</h2>
        <button onclick="loadDashboard()">Refresh Data</button>
        <button onclick="logout()">Logout</button>
    </div>

    <script>
        // Check if user is logged in
        const token = localStorage.getItem('token');
        if (!token) {
            window.location.href = '/login.html';
        }

        async function loadDashboard() {
            try {
                const response = await fetch('/api/dashboard', {
                    headers: { 'Authorization': 'Bearer ' + token }
                });

                const result = await response.json();

                if (result.success) {
                    document.getElementById('userInfo').innerHTML = `
                        <p><strong>Username:</strong> ${result.user.username}</p>
                        <p><strong>Role:</strong> ${result.user.role}</p>
                        <p><strong>Timestamp:</strong> ${result.timestamp}</p>
                        <p><strong>Processed by:</strong> ${result.processedBy}</p>
                    `;
                } else {
                    document.getElementById('userInfo').innerHTML = '<p class="error">' + result.message + '</p>';
                    if (response.status === 401 || response.status === 403) {
                        setTimeout(() => {
                            window.location.href = '/login.html';
                        }, 2000);
                    }
                }

            } catch (error) {
                document.getElementById('userInfo').innerHTML = '<p class="error">Network error</p>';
            }
        }

        function logout() {
            localStorage.removeItem('token');
            window.location.href = '/login.html';
        }

        // Load dashboard on page load
        loadDashboard();
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Praktikum 3: Testing Security (40 menit)

3.1 Test XSS Protection

Test Cases:

  1. Input <script>alert('XSS')</script> di form
  2. Input <img src=x onerror=alert('XSS')>
  3. Input javascript:alert('XSS')

3.2 Test Authentication

# Start server
node server.js

# Test login dengan curl
curl -X POST http://localhost:3000/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"password"}'

# Test protected endpoint tanpa token
curl http://localhost:3000/api/dashboard

# Test dengan token
curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  http://localhost:3000/api/dashboard
Enter fullscreen mode Exit fullscreen mode

3.3 Test Rate Limiting

// rate-limit-test.js
async function testRateLimit() {
    console.log('Testing rate limit...');

    for (let i = 1; i <= 110; i++) {
        try {
            const response = await fetch('http://localhost:3000/api/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username: 'test', password: 'test' })
            });

            console.log(`Request ${i}: Status ${response.status}`);

            if (response.status === 429) {
                console.log('Rate limit reached!');
                break;
            }

        } catch (error) {
            console.log(`Request ${i}: Error - ${error.message}`);
        }

        // Small delay between requests
        await new Promise(resolve => setTimeout(resolve, 100));
    }

    console.log('Rate limit test completed by ahmadasroni38');
}

testRateLimit();
Enter fullscreen mode Exit fullscreen mode

Praktikum 4: Implementasi Logging (20 menit)

4.1 Add Logging to Server

// Add to server.js
const fs = require('fs');
const path = require('path');

// Create logs directory
const logsDir = path.join(__dirname, 'logs');
if (!fs.existsSync(logsDir)) {
    fs.mkdirSync(logsDir);
}

// Logging function
function logActivity(activity, details = {}) {
    const logEntry = {
        timestamp: new Date().toISOString(),
        activity: activity,
        details: details,
        author: 'ahmadasroni38'
    };

    const logFile = path.join(logsDir, `security-${new Date().toISOString().split('T')[0]}.log`);
    fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n');
}

// Add logging to login endpoint
app.post('/api/login', async (req, res) => {
    try {
        const { username, password } = req.body;

        // Log login attempt
        logActivity('LOGIN_ATTEMPT', { username, ip: req.ip });

        const user = users.find(u => u.username === username);
        if (!user || !await bcrypt.compare(password, user.password)) {
            logActivity('LOGIN_FAILED', { username, ip: req.ip });
            return res.status(401).json({
                success: false,
                message: 'Invalid credentials'
            });
        }

        // Log successful login
        logActivity('LOGIN_SUCCESS', { username, ip: req.ip });

        const token = jwt.sign(
            { id: user.id, username: user.username, role: user.role },
            JWT_SECRET,
            { expiresIn: '1h' }
        );

        res.json({
            success: true,
            token: token,
            user: { id: user.id, username: user.username, role: user.role }
        });

    } catch (error) {
        logActivity('LOGIN_ERROR', { error: error.message, ip: req.ip });
        res.status(500).json({ success: false, message: 'Server error' });
    }
});
Enter fullscreen mode Exit fullscreen mode

πŸ“‹ Evaluasi

Pertanyaan Teori (40 menit)

  1. Jelaskan perbedaan antara authentication dan authorization! (10 poin)

    • Authentication: Verifikasi identitas pengguna
    • Authorization: Kontrol akses ke resource
  2. Mengapa input validation harus dilakukan di frontend DAN backend? (10 poin)

    • Frontend: UX yang lebih baik, feedback langsung
    • Backend: Tidak bisa dibypass, security sesungguhnya
  3. Apa itu XSS dan bagaimana cara mencegahnya? (10 poin)

    • XSS: Penyisipan script berbahaya
    • Pencegahan: Input validation, output encoding, CSP
  4. Jelaskan pentingnya rate limiting dalam API security! (10 poin)

    • Mencegah brute force attacks
    • Melindungi dari DDoS
    • Mengontrol penggunaan resource

Tugas Praktik (60 menit)

  1. Implementasi CSRF Protection (20 poin)

    • Tambahkan CSRF token pada form
    • Validasi token di server
    • Test dengan dan tanpa token
  2. Password Strength Checker (20 poin)

    • Minimum 8 karakter
    • Harus ada huruf besar, kecil, angka, simbol
    • Real-time feedback
  3. Session Management (20 poin)

    • Implementasi session timeout
    • Logout otomatis setelah inactive
    • Multiple session handling

Rubrik Penilaian

Aspek Excellent (90-100) Good (80-89) Fair (70-79) Poor (<70)
Pemahaman Konsep Memahami semua konsep dengan sempurna Memahami sebagian besar konsep Memahami konsep dasar Pemahaman terbatas
Implementasi Code berfungsi sempurna dan secure Code berfungsi dengan minor issues Code berfungsi tapi ada kerentanan Code tidak berfungsi
Testing Comprehensive testing dengan berbagai skenario Good testing coverage Basic testing Minimal testing
Documentation Dokumentasi lengkap dan jelas Dokumentasi good Dokumentasi minimal Tidak ada dokumentasi

πŸ“– Referensi

Dokumentasi Resmi

Tutorial dan Artikel

Tools


🎯 Kesimpulan

Pada pertemuan ini, kita telah mempelajari:

  1. Konsep dasar keamanan aplikasi web
  2. Arsitektur aplikasi web modern dan titik kerentanannya
  3. Implementasi keamanan di frontend, backend, dan database
  4. Praktik terbaik untuk mengamankan aplikasi web
  5. Testing dan validasi keamanan aplikasi

Key Takeaways:

  • Security by Design: Keamanan harus dipikirkan dari awal pengembangan
  • Defense in Depth: Implementasi keamanan di multiple layers
  • Never Trust Input: Selalu validasi dan sanitasi input
  • Principle of Least Privilege: Berikan akses minimal yang diperlukan
  • Keep It Simple: Solusi sederhana lebih mudah di-secure

Next Steps:

  • Pertemuan 2: OWASP Top 10 - Injection Attacks
  • Praktik lebih lanjut dengan real-world applications
  • Implementasi advanced security measures

πŸ“ž Bantuan dan Dukungan

Pengajar: ahmadasroni38

Email: ahmad.asroni@motaacademy.id

Office Hours: Senin-Jumat, 09:00-16:00

Lab Repository: GitHub Security Lab

Emergency Contact:


Dibuat dengan ❀️ untuk pembelajaran keamanan web

Author: ahmadasroni38

Last Updated: 2025-07-17 06:25:44

Version: 1.0

Top comments (0)