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.