Panduan Perlindungan CSRF dalam Keselamatan Musim Semi

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan membincangkan serangan CSRF Permintaan Lintas Tapak dan bagaimana menghalangnya menggunakan Spring Security.

2. Dua Serangan CSRF Mudah

Terdapat pelbagai bentuk serangan CSRF - mari kita bincangkan beberapa serangan yang paling biasa.

2.1. DAPATKAN Contoh

Mari pertimbangkan permintaan GET berikut yang digunakan oleh pengguna yang masuk untuk memindahkan wang ke akaun bank tertentu "1234" :

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

Sekiranya penyerang ingin memindahkan wang dari akaun mangsa ke akaunnya sendiri - "5678" - dia perlu membuat mangsa mencetuskan permintaan:

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

Terdapat pelbagai cara untuk mewujudkannya:

  • Pautan: Penyerang dapat meyakinkan mangsa untuk mengklik pautan ini misalnya, untuk melakukan pemindahan:
 Show Kittens Pictures 
  • Gambar: Penyerang boleh menggunakantag dengan URL sasaran sebagai sumber gambar - jadi klik tidak perlu. Permintaan akan dilaksanakan secara automatik ketika halaman dimuat:

2.2. Contoh POST

Sekiranya permintaan utama mestilah permintaan POST - contohnya:

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

Kemudian penyerang perlu membuat mangsa melakukan yang serupa:

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

Sama ada atau akan berfungsi dalam kes ini. Penyerang akan memerlukan - seperti berikut:

Walau bagaimanapun, borang boleh dihantar secara automatik menggunakan Javascript - seperti berikut:

  ...

2.3. Simulasi Praktikal

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

Kita akan mulakan dengan pelaksanaan pengawal 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 mempunyai halaman HTML asas yang mencetuskan operasi pemindahan bank:

 Transfer Money to John  Account Number  Amount     

Ini adalah halaman aplikasi utama, berjalan di domain asal.

Perhatikan bahawa kami telah mensimulasikan kedua GET melalui pautan mudah dan juga POST melalui yang mudah.

Sekarang - mari kita lihat bagaimana halaman penyerang :

  Show Kittens Pictures 

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

Akhirnya, mari kita jalankan dua aplikasi - aplikasi asal dan penyerang - secara tempatan, dan mari kita mengakses halaman asal terlebih dahulu:

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

Kemudian, mari kita mengakses halaman penyerang:

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

Menjejaki permintaan tepat yang berasal dari halaman penyerang ini, kami dapat segera melihat permintaan yang bermasalah, memukul aplikasi yang asli dan disahkan sepenuhnya.

3. Konfigurasi Keselamatan Musim Semi

Untuk menggunakan perlindungan Spring Security CSRF, pertama-tama kita harus memastikan bahawa kita menggunakan kaedah HTTP yang betul untuk apa sahaja yang mengubah keadaan ( PATCH , POST , PUT , dan DELETE - not GET).

3.1. Konfigurasi Java

Perlindungan CSRF diaktifkan secara lalai dalam konfigurasi Java. Kami masih boleh mematikannya jika perlu:

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

3.2. Konfigurasi XML

Dalam konfigurasi XML yang lebih lama (pra Spring Security 4), perlindungan CSRF dilumpuhkan secara lalai dan kami dapat mengaktifkannya seperti berikut:

 ...  

Bermula dari Spring Security 4.x - perlindungan CSRF diaktifkan secara lalai dalam konfigurasi XML juga; kita tentu saja masih boleh mematikannya jika kita perlu:

 ...  

3.3. Parameter Bentuk Tambahan

Akhirnya, dengan perlindungan CSRF diaktifkan di sisi pelayan, kami perlu memasukkan token CSRF dalam permintaan kami di pihak pelanggan juga:

3.4. Menggunakan JSON

Kami tidak dapat menyerahkan token CSRF sebagai parameter jika kami menggunakan JSON; sebaliknya, kami boleh menyerahkan token dalam tajuk.

Mula-mula kita perlu memasukkan token di halaman kita - dan untuk itu, kita dapat menggunakan tag meta:

Kemudian kita akan membina 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. Ujian Cacat CSRF

Dengan semua itu, kami akan melakukan beberapa ujian.

Mari mula-mula cuba mengemukakan permintaan POST mudah apabila CSRF dilumpuhkan:

@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 anda perhatikan, kami menggunakan kelas asas untuk memegang logik pembantu pengujian biasa - 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 bahawa, apabila pengguna mempunyai kelayakan keselamatan yang tepat, permintaan itu berjaya dilaksanakan - tidak memerlukan maklumat tambahan.

Ini bermaksud bahawa penyerang hanya boleh menggunakan salah satu vektor serangan yang telah dibincangkan sebelumnya untuk mudah menjejaskan sistem.

5. Ujian Diaktifkan CSRF

Sekarang, mari kita aktifkan perlindungan CSRF dan lihat perbezaannya:

@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 ujian ini menggunakan konfigurasi keselamatan yang berbeza - yang mempunyai perlindungan CSRF diaktifkan.

Sekarang, permintaan POST akan gagal sekiranya token CSRF tidak disertakan, yang tentunya bermaksud bahawa serangan sebelumnya tidak lagi menjadi pilihan.

Akhirnya, perhatikan kaedah csrf () dalam ujian; ini membuat RequestPostProcessor yang secara automatik akan mengisi token CSRF yang sah dalam permintaan untuk tujuan pengujian.

6. Kesimpulannya

Dalam artikel ini, kami membincangkan beberapa serangan CSRF dan bagaimana menghalangnya menggunakan Spring Security.

The pelaksanaan penuh tutorial ini boleh didapati dalam projek GitHub - ini adalah projek berasaskan Maven, jadi ia harus mudah untuk import dan berjalan kerana ia adalah.