Lembar Cheat Keamanan: Nodejs



Sudah banyak yang dikatakan tentang popularitas NodeJS. Peningkatan jumlah aplikasi jelas - NodeJS cukup mudah dipelajari, memiliki sejumlah besar perpustakaan, serta ekosistem yang berkembang secara dinamis.

Kami telah menyiapkan rekomendasi untuk pengembang NodeJS berdasarkan OWASP Cheat Sheets untuk membantu Anda mengantisipasi masalah keamanan saat mengembangkan aplikasi.

Rekomendasi keamanan untuk aplikasi NodeJS dapat dibagi ke dalam kategori berikut:

  • Keamanan selama pengembangan aplikasi;
  • Keamanan server;
  • Keamanan platform;


Keamanan Pengembangan Aplikasi


Menghindari panggilan balik

menggunakan fungsi panggilan balik (callback) adalah salah satu kekuatan terbesar NodeJS, namun ketika bersarang panggilan balik, Anda dapat dengan mudah lupa untuk menangani kesalahan di salah satu fungsi. Salah satu cara untuk menghindari panggilan balik neraka adalah dengan menggunakan janji. Bahkan jika modul yang Anda gunakan tidak mendukung bekerja dengan janji-janji, Anda selalu dapat menggunakan Promise.promisifyAll (). Namun meski menggunakan janji, perlu diperhatikan untuk bersarang. Untuk benar-benar menghindari kesalahan panggilan balik neraka, patuhi rantai janji β€œdatar”.

Contoh panggilan balik neraka:

function func1(name, callback) {
   setTimeout(function() {
      // operations
   }, 500);
}
function func2(name, callback) {
   setTimeout(function() {
      // operations
   }, 100);
}
function func3(name, callback) {
   setTimeout(function() {
      // operations
   }, 900);
}
function func4(name, callback) {
   setTimeout(function() {
      // operations
   }, 3000);
}

func1("input1", function(err, result1){
   if(err){
      // error operations
   }
   else {
      //some operations
      func2("input2", function(err, result2){
         if(err){
            //error operations
         }
         else{
            //some operations
            func3("input3", function(err, result3){
               if(err){
                  //error operations
               }
               else{
                  // some operations
                  func4("input 4", function(err, result4){
                     if(err){
                        // error operations
                     }
                     else {
                        // some operations
                     }
                  });
               }
            });
         }
      });
   }
});

Kode yang sama menggunakan janji rantai datar:

function func1(name, callback) {
   setTimeout(function() {
      // operations
   }, 500);
}
function func2(name, callback) {
   setTimeout(function() {
      // operations
   }, 100);
}
function func3(name, callback) {
   setTimeout(function() {
      // operations
   }, 900);
}
function func4(name, callback) {
   setTimeout(function() {
      // operations
   }, 3000);
}

func1("input1")
   .then(function (result){
      return func2("input2");
   })
   .then(function (result){
      return func3("input3");
   })
   .then(function (result){
      return func4("input4");
   })
   .catch(function (error) {
      // error operations
   });

Batasi ukuran permintaan.

Mem-parsing badan permintaan bisa menjadi sumber daya yang cukup intensif. Jika Anda tidak membatasi ukuran permintaan, penyerang akan dapat mengirim permintaan yang cukup besar yang dapat mengisi semua ruang disk atau menghabiskan semua sumber daya server, tetapi pada saat yang sama, membatasi ukuran permintaan untuk semua kasus mungkin salah, karena ada permintaan, seperti mengunduh file. Karena itu, disarankan untuk menetapkan batas untuk berbagai jenis konten. Misalnya, menggunakan kerangka kerja ekspres, ini dapat diimplementasikan sebagai berikut:

app.use(express.urlencoded({ limit: "1kb" }));
app.use(express.json({ limit: "1kb" }));
app.use(express.multipart({ limit:"10mb" }));

Perlu dicatat bahwa penyerang dapat mengubah jenis konten permintaan dan menghindari batasan, oleh karena itu, perlu untuk memeriksa apakah konten permintaan cocok dengan jenis konten yang ditentukan dalam header permintaan. Jika memeriksa tipe konten memengaruhi kinerja, Anda hanya dapat memeriksa tipe atau kueri tertentu yang lebih besar dari ukuran tertentu.

Jangan memblokir loop acara

. Komponen penting dari bahasa ini adalah loop acara, yang hanya memungkinkan Anda untuk mengubah konteks eksekusi tanpa menunggu operasi selesai. Namun, ada operasi pemblokiran yang penyelesaiannya NodeJS harus menunggu sebelum melanjutkan dengan kode. Misalnya, sebagian besar metode sinkron memblokir:

const fs = require('fs');
fs.unlinkSync('/file.txt');

Disarankan untuk melakukan operasi seperti itu secara serempak:

const fs = require('fs');
fs.unlink('/file.txt', (err) => {
    if (err) throw err;
});

Pada saat yang sama, jangan lupa bahwa kode yang berdiri setelah panggilan asinkron akan dieksekusi tanpa menunggu selesainya operasi sebelumnya.

Misalnya, dalam kode di bawah ini, file akan dihapus sebelum dibaca, yang dapat menyebabkan kondisi balapan.

const fs = require('fs');
fs.readFile('/file.txt', (err, data) => {
  // perform actions on file content
});
fs.unlinkSync('/file.txt');

Untuk menghindari ini, Anda dapat menulis semua operasi dalam fungsi yang tidak menghalangi:

const fs = require('fs');
fs.readFile('/file.txt', (err, data) => {
  // perform actions on file content
  fs.unlink('/file.txt', (err) => {
    if (err) throw err;
  });
});

Periksa bidang input.

Memeriksa bidang input adalah bagian penting dari keamanan aplikasi apa pun. Kesalahan validasi dapat menyebabkan aplikasi Anda menjadi rentan segera terhadap berbagai jenis serangan: injeksi sql, xss, injeksi perintah, dan lainnya. Untuk menyederhanakan validasi formulir, Anda dapat menggunakan paket validator, mongo-express-sanitize.

Escape data pengguna

Salah satu aturan yang akan membantu Anda melindungi diri dari serangan xss adalah untuk melindungi data pengguna. Anda dapat menggunakan perpustakaan escape-html atau node-esapi untuk ini.

Simpan log

Selain itu, ini akan membantu dalam kesalahan debugging, logging dapat digunakan untuk menanggapi insiden. Anda dapat membaca lebih lanjut tentang perlunya login di sini.. Salah satu paket logging NodeJS yang paling populer adalah Winston dan Bunyan. Contoh di bawah ini menunjukkan cara menggunakan Winston untuk membuat log ke konsol dan file:

var logger = new (Winston.Logger) ({
    transports: [
        new (winston.transports.Console)(),
        new (winston.transports.File)({ filename: 'application.log' })
    ],
    level: 'verbose'
});

Mengontrol siklus peristiwa

Jika server Anda berada dalam kondisi lalu lintas jaringan yang intensif, pengguna mungkin mengalami kesulitan dengan ketersediaan layanan Anda. Ini pada dasarnya adalah serangan DoS. Dalam hal ini, Anda dapat melacak waktu respons dan, jika melebihi waktu yang ditentukan, mengirim pesan ke 503 Server Terlalu Sibuk. Modul toobusy-js dapat membantu.

Contoh menggunakan modul:

var toobusy = require('toobusy-js');
var express = require('express');
var app = express();
app.use(function(req, res, next) {
    if (toobusy()) {
        // log if you see necessary
        res.send(503, "Server Too Busy");
    } else {
    next();
    }
});

Ambil tindakan pencegahan terhadap kekuatan brutal

, sekali lagi, modul datang untuk menyelamatkan. Misalnya, express-brute atau express-bouncer. Contoh penggunaan:

var bouncer = require('express-bouncer');
bouncer.whitelist.push('127.0.0.1'); // whitelist an IP address
// give a custom error message
bouncer.blocked = function (req, res, next, remaining) {
    res.send(429, "Too many requests have been made. Please wait " + remaining/1000 + " seconds.");
};
// route to protect
app.post("/login", bouncer.block, function(req, res) {
    if (LoginFailed){  }
    else {
        bouncer.reset( req );
    }
});

Menggunakan CAPTCHA adalah penanggulangan brute force umum lainnya. Modul yang sering digunakan untuk membantu mengimplementasikan CAPTCHA adalah svg-captcha.

Menggunakan Token CSRF

Salah satu cara yang paling dapat diandalkan untuk melindungi terhadap serangan CSRF adalah dengan menggunakan token CSRF. Token harus dibuat dengan entropi tinggi, diperiksa ketat dan diikat ke sesi pengguna. Untuk memastikan pengoperasian token CSRF, Anda dapat menggunakan modul csurf.

Contoh penggunaan:

var csrf = require('csurf');
csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, function(req, res) {
    res.render('send', { csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function(req, res) {
    res.send('data is being processed');
});

Jangan lupa menambahkan token ke bidang tersembunyi di halaman:

<input type="hidden" name="_csrf" value="{{ csrfToken }}">

Anda dapat membaca lebih lanjut tentang token CSRF di artikel kami .

Hapus rute yang tidak perlu.

Aplikasi web tidak boleh berisi halaman yang tidak digunakan oleh pengguna, karena ini dapat meningkatkan permukaan serangan. Karena itu, semua rute API yang tidak digunakan harus dinonaktifkan. Anda terutama harus memperhatikan pertanyaan ini jika Anda menggunakan kerangka kerja Sails atau Feathers, karena mereka secara otomatis menghasilkan titik akhir API.

Lindungi diri Anda dari HPP (Polusi Parameter HTTP)

Secara default, express menambahkan semua parameter dari permintaan ke array. OWASP merekomendasikan menggunakan modul hpp, yang mengabaikan semua nilai parameter dari req.query dan / atau req.body dan hanya memilih nilai terakhir dari yang duplikat.

var hpp = require('hpp');
app.use(hpp());

Pantau nilai yang dikembalikan.

Misalnya, tabel pengguna dapat menyimpan data penting: kata sandi, alamat email, tanggal lahir, dll. Karena itu, penting untuk hanya mengembalikan data yang diperlukan.

Contohnya:

 exports.sanitizeUser = function(user) {
  return {
    id: user.id,
    username: user.username,
    fullName: user.fullName
  };
};

Gunakan deskriptor

Gunakan deskriptor untuk menggambarkan perilaku properti untuk berbagai operasi: dapat ditulis - apakah mungkin untuk mengubah nilai properti, dapat dihitung - apakah mungkin untuk menggunakan properti dalam for..in loop, dapat dikonfigurasi - apakah mungkin untuk menimpa properti. Dianjurkan untuk memperhatikan properti yang terdaftar, karena ketika mendefinisikan properti suatu objek, semua atribut ini disetel ke true secara default. Anda dapat mengubah nilai properti sebagai berikut:

var o = {};
Object.defineProperty(o, "a", {
    writable: true,
    enumerable: true,
    configurable: true,
    value: "A"
});

Gunakan ACL

Acl dapat membantu membedakan akses data berdasarkan peran. Misalnya, menambahkan izin terlihat seperti ini:

// guest is allowed to view blogs
acl.allow('guest', 'blogs', 'view')
// allow function accepts arrays as any parameter
acl.allow('member', 'blogs', ['edit', 'view', 'delete'])

Tangkap uncaughtException

Secara default, jika ada pengecualian yang tidak tertangkap, NodeJS akan membuang jejak stack saat ini dan mengakhiri utas eksekusi. Namun, NodeJS memungkinkan Anda untuk menyesuaikan perilaku ini. Jika ada pengecualian yang tidak tertangkap, peristiwa uncaughtException dimunculkan, yang dapat ditangkap menggunakan objek proses:

process.on("uncaughtException", function(err) {
    // clean up allocated resources
    // log necessary error details to log files
    process.exit(); // exit the process to avoid unknown state
});

Perlu diingat bahwa ketika terjadi uncaughtException, perlu untuk menghapus semua sumber daya yang dialokasikan (misalnya, deskriptor file dan penangan) sebelum menyelesaikan proses Z untuk menghindari kesalahan yang tidak terduga. Sangat tidak disarankan bahwa program terus berjalan jika uncaughtException terjadi.

Juga, saat menampilkan pesan kesalahan, pengguna tidak boleh mengungkapkan informasi kesalahan terperinci, seperti tumpukan jejak.

Keamanan server


Mengatur flag untuk header ketika bekerja dengan cookie.

Ada beberapa flag yang dapat membantu melindungi terhadap serangan seperti xss dan csrf: httpOnly, yang mencegah akses ke cookie melalui javascript; Aman - memungkinkan pengiriman cookie hanya melalui HTTPS dan SameSite, yang menentukan kemampuan untuk mentransfer cookie ke sumber daya pihak ketiga.

Contoh penggunaan:

var session = require('express-session');
app.use(session({
    secret: 'your-secret-key',
    key: 'cookieName',
    cookie: { secure: true, httpOnly: true, path: '/user', sameSite: true}
}));

Menyetel tajuk HTTP untuk keamanan

Berikut ini tajuk dan contoh cara menghubungkannya untuk membantu Anda melindungi diri dari sejumlah serangan umum. Header diatur menggunakan modul helm

β€’ Strict-Transport-Security: HTTP Strict Transport Security (HSTS) memberi tahu browser bahwa aplikasi hanya dapat diakses melalui HTTPS

app.use(helmet.hsts()); // default configuration
app.use(helmet.hsts("<max-age>", "<includeSubdomains>")); // custom configuration

β€’ X-Frame-Options: menentukan apakah halaman dapat digunakan dalam bingkai, iframe, embed atau objek

app.use(hemlet.xframe()); // default behavior (DENY)
helmet.xframe('sameorigin'); // SAMEORIGIN
helmet.xframe('allow-from', 'http://alloweduri.com'); //ALLOW-FROM uri

β€’ X-XSS-Protection: memungkinkan browser untuk berhenti memuat halaman jika mendeteksi serangan XSS yang dipantulkan.

var xssFilter = require('x-xss-protection');
app.use(xssFilter());

β€’ X-Content-Type-Options: digunakan untuk mencegah serangan menggunakan tipe MIME

app.use(helmet.noSniff());

β€’ Kebijakan Konten-Keamanan-: Mencegah serangan seperti XSS dan serangan injeksi data

const csp = require('helmet-csp')
app.use(csp({
   directives: {
       defaultSrc: ["'self'"],  // default value for all directives that are absent
       scriptSrc: ["'self'"],   // helps prevent XSS attacks
       frameAncestors: ["'none'"],  // helps prevent Clickjacking attacks
       imgSrc: ["'self'", "'http://imgexample.com'"],
       styleSrc: ["'none'"]
    }
}))

β€’ Kontrol Cache dan Pragma: untuk mengelola caching, terutama tajuk ini dapat berguna untuk halaman yang berisi data sensitif. Namun, ingat bahwa menonaktifkan caching di semua halaman dapat memengaruhi kinerja.

app.use(helmet.noCache());

β€’ Opsi X-Unduh: header mencegah Inter Explorer dari menjalankan file yang diunduh

app.use(helmet.ieNoOpen());

β€’ Expect-CT: Certificate Transparansi - mekanisme yang dibuat untuk menyelesaikan beberapa masalah dengan infrastruktur sertifikat SSL, header ini memberi tahu browser tentang perlunya verifikasi sertifikat tambahan dalam log CT

var expectCt = require('expect-ct');
app.use(expectCt({ maxAge: 123 }));
app.use(expectCt({ enforce: true, maxAge: 123 }));
app.use(expectCt({ enforce: true, maxAge: 123, reportUri: 'http://example.com'}));

β€’ X-Powered-By: Header opsional yang digunakan untuk menunjukkan teknologi yang digunakan di server. Anda dapat menyembunyikan tajuk ini sebagai berikut:

app.use(helmet.hidePoweredBy());

Selain itu, Anda dapat mengubah nilai untuk menyembunyikan informasi nyata tentang teknologi yang Anda gunakan:

app.use(helmet.hidePoweredBy({ setTo: 'PHP 4.2.0' }));

Keamanan platform


Perbarui paket Anda

Keamanan aplikasi Anda bergantung pada keamanan paket yang Anda gunakan, jadi penting untuk menggunakan versi terbaru paket. Untuk memastikan bahwa paket yang Anda gunakan tidak mengandung kerentanan yang diketahui, Anda dapat menggunakan daftar OWASP khusus . Anda juga dapat menggunakan pustaka yang memeriksa paket untuk kerentanan yang diketahui Retire.js.

Jangan gunakan fungsi yang tidak aman.

Ada beberapa fungsi yang direkomendasikan untuk dibuang jika memungkinkan. Di antara fungsi-fungsi ini adalah eval (), yang mengeksekusi string yang diambil sebagai argumen. Dalam kombinasi dengan input pengguna, menggunakan fungsi ini dapat menyebabkan kerentanan dalam eksekusi kode jarak jauh, karena untuk alasan yang sama, menggunakan child_process.exec juga tidak aman, karena fungsi meneruskan argumen yang diterima ke bin / sh.

Selain itu, ada sejumlah modul yang harus Anda gunakan dengan hati-hati. Misalnya, modul fs untuk bekerja dengan file. Jika dengan cara tertentu input pengguna yang dihasilkan dilewatkan ke fungsi, maka aplikasi Anda mungkin menjadi rentan untuk memasukkan file lokal dan direktori traversal.

Modul vm, yang menyediakan API untuk mengkompilasi dan menjalankan kode pada mesin virtual V8, harus digunakan hanya di kotak pasir.

Di sini Anda dapat membiasakan diri dengan fungsi-fungsi lain yang dapat membuat aplikasi Anda tidak aman.

Hati-hati menggunakan ekspresi reguler. Ekspresi

reguler dapat ditulis sehingga Anda dapat mencapai situasi di mana ekspresi akan tumbuh secara eksponensial, yang dapat menyebabkan penolakan layanan. Serangan semacam itu disebut ReDoS. Ada beberapa alat untuk memeriksa apakah ekspresi reguler aman, salah satunya adalah vuln-regex-detector.

Jalankan linter secara berkala

Selama pengembangan, sulit untuk mengingat semua rekomendasi keselamatan, dan ketika menyangkut pengembangan tim, tidaklah mudah untuk mencapai kepatuhan dengan aturan oleh semua anggota tim. Untuk tujuan tersebut, ada alat untuk analisis keamanan statis. Alat semacam itu, tanpa mengeksekusi kode Anda, mencari kerentanan di dalamnya. Selain itu, linter memungkinkan Anda untuk menambahkan aturan khusus untuk menemukan tempat dalam kode yang mungkin rentan. Linter yang paling umum digunakan adalah ESLint dan JSHint.

Gunakan mode ketat.

Javascript memiliki sejumlah fungsi tidak aman dan usang yang tidak boleh digunakan. Untuk mengecualikan kemungkinan menggunakan fungsi-fungsi ini, mode ketat juga disediakan.

Patuhi prinsip keselamatan umum

Rekomendasi tersebut menjelaskan fokus pada NodeJS, tetapi jangan lupa tentang prinsip keamanan umum yang harus diperhatikan terlepas dari platform yang digunakan.

All Articles