flawopen.com/ssrf/Javascript

● CWE-918 · Tinggi
Riset Keamanan · FlawOpen

Server-Side Request Forgery in JavaScript

How axios and fetch allow internal network scanning and cloud metadata theft, and how to implement safe lookup agents in Node.js.

💡 Penjelasan Sederhana (ELI5)

Bayangkan mengirim kurir kantor untuk mengambil paket di toko umum, tetapi memberinya alamat brankas terkunci di ruang manajer. Karena kurir memiliki lencana akses internal, dia membuka brankas dan menyerahkan rahasia perusahaan kepada Anda.

Konsep Kunci & Istilah

Web Application Security
Komponen arsitektur utama yang terpengaruh oleh CWE-918.
CWE-918
Klasifikasi standar Common Weakness Enumeration (CWE) untuk ssrf-javascript.
Defense-in-Depth
Verifikasi rekayasa berlapis dan isolasi batas waktu proses (runtime).

Alur Serangan Langkah demi Langkah

Step 1

Penerimaan URL Jarak Jauh Tidak Tepercaya

Aplikasi menerima parameter URL dari pengguna untuk mengunduh avatar, webhook, atau pratinjau dokumen.

Step 2

Pengalamatan Jaringan Internal

Penyerang memasukkan alamat loopback atau layanan metadata cloud (misal: http://169.254.169.254/latest/meta-data/).

Step 3

Koneksi Keluar Soket Tanpa Filter

Klien HTTP server mengirim permintaan dari dalam VPC privat tanpa memeriksa rentang subnet privat.

Step 4

Pencurian Kredensial Cloud IAM

Layanan metadata internal mempercayai permintaan dan mengembalikan token kredensial IAM sementara ke penyerang.

Kode Sumber: Rentan vs Aman

✕ IMPLEMENTASI RENTAN
// VULNERABLE: Naively fetching user URL with axios
const express = require('express');
const axios = require('axios');

const app = express();

app.get('/proxy-image', async (req, res) => {
  const imageUrl = req.query.url;
  // Attacker input: http://169.254.169.254/latest/meta-data/
  try {
    const response = await axios.get(imageUrl);
    res.send(response.data);
  } catch (err) {
    res.status(500).send('Fetch error');
  }
});
✓ PERBAIKAN AMAN & KUAT
// HARDENED: Custom DNS lookup agent blocking private & metadata IP ranges
const express = require('express');
const http = require('http');
const https = require('https');
const dns = require('dns');
const ipaddr = require('ipaddr.js'); // npm i ipaddr.js
const axios = require('axios');

const app = express();

function safeLookup(hostname, options, callback) {
  dns.lookup(hostname, options, (err, address, family) => {
    if (err) return callback(err);

    try {
      const addr = ipaddr.parse(address);
      const range = addr.range();

      // Deny private, loopback, link-local, and reserved ranges
      const blockedRanges = ['loopback', 'private', 'linkLocal', 'carrierGradeNat', 'reserved'];
      if (blockedRanges.includes(range)) {
        return callback(new Error(`SSRF Blocked: Destination IP ${address} is in blocked range (${range})`));
      }

      callback(null, address, family);
    } catch (parseErr) {
      callback(new Error('Invalid IP address resolved'));
    }
  });
}

const safeHttpAgent = new http.Agent({ lookup: safeLookup });
const safeHttpsAgent = new https.Agent({ lookup: safeLookup });

app.get('/proxy-image', async (req, res) => {
  const imageUrl = req.query.url;
  try {
    const parsed = new URL(imageUrl);
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return res.status(400).send('Invalid protocol');
    }

    const response = await axios.get(imageUrl, {
      httpAgent: safeHttpAgent,
      httpsAgent: safeHttpsAgent,
      maxRedirects: 0, // Prevent redirect bypasses
      timeout: 3000
    });

    res.send(response.data);
  } catch (err) {
    res.status(403).send('Request blocked: ' + err.message);
  }
});

Daftar Periksa Penguatan Sistem Rekayasa

References