Panduan Perlindungan CSRF dalam Keamanan Musim Semi

1. Ikhtisar

Dalam tutorial ini, kita akan membahas serangan CSRF Pemalsuan Permintaan Lintas Situs dan cara mencegahnya menggunakan Spring Security.

2. Dua Serangan CSRF Sederhana

Ada beberapa bentuk serangan CSRF - mari kita bahas beberapa yang paling umum.

2.1. DAPATKAN Contoh

Mari pertimbangkan permintaan GET berikut yang digunakan oleh pengguna yang masuk untuk mentransfer uang ke rekening bank tertentu "1234" :

GET //bank.com/transfer?accountNo=1234&amount=100

Jika penyerang ingin mentransfer uang dari rekening korban ke rekeningnya sendiri - "5678" - ia harus membuat korban memicu permintaan:

GET //bank.com/transfer?accountNo=5678&amount=1000

Ada banyak cara untuk mewujudkannya:

  • Tautan: Penyerang dapat meyakinkan korban untuk mengklik tautan ini misalnya, untuk melakukan transfer:
 Show Kittens Pictures 
  • Gambar: Penyerang dapat menggunakan filetag dengan URL target sebagai sumber gambar - jadi klik tidak perlu. Permintaan akan dijalankan secara otomatis saat halaman dimuat:

2.2. Contoh POST

Jika permintaan utama harus berupa permintaan POST - misalnya:

POST //bank.com/transfer accountNo=1234&amount=100

Kemudian penyerang harus meminta korban menjalankan yang serupa:

POST //bank.com/transfer accountNo=5678&amount=1000

Baik itu atau akan bekerja dalam kasus ini. Penyerang membutuhkan file - sebagai berikut:

Namun, formulir dapat dikirimkan secara otomatis menggunakan Javascript - sebagai berikut:

  ...

2.3. Simulasi Praktis

Sekarang setelah kita memahami bagaimana serangan CSRF terlihat, mari kita simulasikan contoh-contoh ini dalam aplikasi Spring.

Kita akan mulai dengan implementasi pengontrol sederhana- BankController :

@Controller public class BankController { private Logger logger = LoggerFactory.getLogger(getClass()); @RequestMapping(value = "/transfer", method = RequestMethod.GET) @ResponseBody public String transfer(@RequestParam("accountNo") int accountNo, @RequestParam("amount") final int amount) { logger.info("Transfer to {}", accountNo); ... } @RequestMapping(value = "/transfer", method = RequestMethod.POST) @ResponseStatus(HttpStatus.OK) public void transfer2(@RequestParam("accountNo") int accountNo, @RequestParam("amount") final int amount) { logger.info("Transfer to {}", accountNo); ... } }

Dan mari kita juga memiliki halaman HTML dasar yang memicu operasi transfer bank:

 Transfer Money to John  Account Number  Amount     

Ini adalah halaman aplikasi utama, berjalan di domain asal.

Perhatikan bahwa kami telah mensimulasikan GET melalui tautan sederhana serta POST melalui tautan sederhana.

Sekarang - mari kita lihat bagaimana halaman penyerang akan terlihat:

  Show Kittens Pictures 

Halaman ini akan berjalan di domain yang berbeda - domain penyerang.

Terakhir, mari jalankan dua aplikasi - aplikasi asli dan penyerang - secara lokal, dan akses halaman asli terlebih dahulu:

//localhost:8081/spring-rest-full/csrfHome.html

Lalu, mari akses halaman penyerang:

//localhost:8081/spring-security-rest/api/csrfAttacker.html

Melacak permintaan persis yang berasal dari halaman penyerang ini, kami akan dapat segera menemukan permintaan yang bermasalah, mengenai aplikasi asli dan sepenuhnya diautentikasi.

3. Konfigurasi Keamanan Pegas

Untuk menggunakan perlindungan CSRF Keamanan Musim Semi, pertama-tama kita harus memastikan bahwa kita menggunakan metode HTTP yang tepat untuk apa pun yang mengubah status ( PATCH , POST , PUT , dan DELETE - bukan GET).

3.1. Konfigurasi Java

Perlindungan CSRF diaktifkan secara default di konfigurasi Java. Kami masih dapat menonaktifkannya jika kami perlu:

@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable(); }

3.2. Konfigurasi XML

Dalam konfigurasi XML yang lebih lama (sebelum Spring Security 4), perlindungan CSRF dinonaktifkan secara default dan kami dapat mengaktifkannya sebagai berikut:

 ...  

Mulai dari Spring Security 4.x - perlindungan CSRF juga diaktifkan secara default di konfigurasi XML; kami tentu saja masih dapat menonaktifkannya jika kami perlu:

 ...  

3.3. Parameter Bentuk Ekstra

Terakhir, dengan proteksi CSRF diaktifkan di sisi server, kami juga perlu menyertakan token CSRF dalam permintaan kami di sisi klien:

3.4. Menggunakan JSON

Kami tidak dapat mengirimkan token CSRF sebagai parameter jika kami menggunakan JSON; sebagai gantinya, kita bisa mengirimkan token di dalam header.

Pertama-tama kita harus menyertakan token di halaman kita - dan untuk itu, kita dapat menggunakan tag meta:

Kemudian kami akan membuat tajuk:

var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content"); $(document).ajaxSend(function(e, xhr, options) { xhr.setRequestHeader(header, token); });

4. Tes Penonaktifan CSRF

Dengan semua itu, kami akan pindah untuk melakukan beberapa pengujian.

Pertama-tama, coba kirimkan permintaan POST sederhana saat CSRF dinonaktifkan:

@ContextConfiguration(classes = { SecurityWithoutCsrfConfig.class, ...}) public class CsrfDisabledIntegrationTest extends CsrfAbstractIntegrationTest { @Test public void givenNotAuth_whenAddFoo_thenUnauthorized() throws Exception { mvc.perform( post("/foos").contentType(MediaType.APPLICATION_JSON) .content(createFoo()) ).andExpect(status().isUnauthorized()); } @Test public void givenAuth_whenAddFoo_thenCreated() throws Exception { mvc.perform( post("/foos").contentType(MediaType.APPLICATION_JSON) .content(createFoo()) .with(testUser()) ).andExpect(status().isCreated()); } }

Seperti yang mungkin telah Anda perhatikan, kami menggunakan kelas dasar untuk menampung logika helper pengujian umum - CsrfAbstractIntegrationTest :

@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration public class CsrfAbstractIntegrationTest { @Autowired private WebApplicationContext context; @Autowired private Filter springSecurityFilterChain; protected MockMvc mvc; @Before public void setup() { mvc = MockMvcBuilders.webAppContextSetup(context) .addFilters(springSecurityFilterChain) .build(); } protected RequestPostProcessor testUser() { return user("user").password("userPass").roles("USER"); } protected String createFoo() throws JsonProcessingException { return new ObjectMapper().writeValueAsString(new Foo(randomAlphabetic(6))); } }

Perhatikan bahwa, jika pengguna memiliki kredensial keamanan yang tepat, permintaan tersebut berhasil dijalankan - tidak diperlukan informasi tambahan.

Itu berarti bahwa penyerang dapat dengan mudah menggunakan salah satu vektor serangan yang telah dibahas sebelumnya untuk dengan mudah menyusupi sistem.

5. Tes Pengaktifan CSRF

Sekarang, mari aktifkan perlindungan CSRF dan lihat perbedaannya:

@ContextConfiguration(classes = { SecurityWithCsrfConfig.class, ...}) public class CsrfEnabledIntegrationTest extends CsrfAbstractIntegrationTest { @Test public void givenNoCsrf_whenAddFoo_thenForbidden() throws Exception { mvc.perform( post("/foos").contentType(MediaType.APPLICATION_JSON) .content(createFoo()) .with(testUser()) ).andExpect(status().isForbidden()); } @Test public void givenCsrf_whenAddFoo_thenCreated() throws Exception { mvc.perform( post("/foos").contentType(MediaType.APPLICATION_JSON) .content(createFoo()) .with(testUser()).with(csrf()) ).andExpect(status().isCreated()); } }

Sekarang bagaimana pengujian ini menggunakan konfigurasi keamanan yang berbeda - yang memiliki perlindungan CSRF diaktifkan.

Sekarang, permintaan POST hanya akan gagal jika token CSRF tidak disertakan, yang tentu saja berarti serangan sebelumnya tidak lagi menjadi pilihan.

Terakhir, perhatikan metode csrf () dalam tes; ini membuat RequestPostProcessor yang secara otomatis akan mengisi token CSRF yang valid dalam permintaan untuk tujuan pengujian.

6. Kesimpulan

Dalam artikel ini, kami membahas beberapa serangan CSRF dan cara mencegahnya menggunakan Spring Security.

The implementasi penuh dari tutorial ini dapat ditemukan dalam proyek GitHub - ini adalah proyek berbasis Maven, sehingga harus mudah untuk impor dan berjalan seperti itu.