Fix CSRF bugs

This commit is contained in:
2026-01-05 17:45:54 +01:00
parent 0ebfc390c5
commit 929a0f2217
4 changed files with 18 additions and 4 deletions

View File

@@ -18,6 +18,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.csrf.CsrfFilter;
@@ -41,6 +42,9 @@ public class SecurityConfig {
HttpSecurity http,
OAuth2AuthorizedClientRepository authorizedClientRepository
) throws Exception {
CsrfTokenRequestAttributeHandler csrfRequestHandler = new CsrfTokenRequestAttributeHandler();
csrfRequestHandler.setCsrfRequestAttributeName("_csrf");
http
.authorizeHttpRequests((auth) ->
auth
@@ -89,6 +93,7 @@ public class SecurityConfig {
.csrf((csrf) ->
csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(csrfRequestHandler)
.ignoringRequestMatchers("/ws/**")
)
.addFilterAfter(csrfTokenCookieFilter(), CsrfFilter.class);
@@ -140,7 +145,10 @@ public class SecurityConfig {
HttpServletResponse response,
FilterChain filterChain
) throws java.io.IOException, jakarta.servlet.ServletException {
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
if (csrfToken == null) {
csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
}
if (csrfToken != null) {
String token = csrfToken.getToken();
Cookie existingCookie = WebUtils.getCookie(request, "XSRF-TOKEN");

View File

@@ -438,6 +438,7 @@ function connect() {
},
(error) => {
console.warn("WebSocket connection issue", error);
fetchAssets();
setTimeout(
() => showToast("Live updates connection interrupted. Retrying may be necessary.", "warning"),
1000,
@@ -2086,7 +2087,7 @@ function uploadAsset(file = null) {
return;
}
if (selectedFile.size > UPLOAD_LIMIT_BYTES) {
showToast(`File is too large. Maximum upload size is ${UPLOAD_MAX_BYTES / 1024 / 1024} MB.`, "error");
showToast(`File is too large. Maximum upload size is ${UPLOAD_LIMIT_BYTES / 1024 / 1024} MB.`, "error");
return;
}

View File

@@ -352,7 +352,7 @@
const broadcaster = /*[[${broadcaster}]]*/ '';
const username = /*[[${username}]]*/ '';
const UPLOAD_LIMIT_BYTES = /*[[${uploadLimitBytes}]]*/ 0;
const SETTINGS = /*[[${settingsJson}]]*/;
const SETTINGS = JSON.parse(/*[[${settingsJson}]]*/);
</script>
<script src="/js/cookie-consent.js"></script>
<script src="/js/toast.js"></script>

View File

@@ -1,6 +1,7 @@
package dev.kruhlmann.imgfloat;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -48,6 +49,7 @@ class ChannelApiIntegrationTest {
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"helper\"}")
.with(oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster)))
.with(csrf())
)
.andExpect(status().isOk());
@@ -70,6 +72,7 @@ class ChannelApiIntegrationTest {
multipart("/api/channels/{broadcaster}/assets", broadcaster)
.file(file)
.with(oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster)))
.with(csrf())
)
.andExpect(status().isOk())
.andReturn()
@@ -96,6 +99,7 @@ class ChannelApiIntegrationTest {
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(visibilityRequest))
.with(oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster)))
.with(csrf())
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.hidden").value(false));
@@ -113,7 +117,7 @@ class ChannelApiIntegrationTest {
.perform(
delete("/api/channels/{broadcaster}/assets/{id}", broadcaster, assetId).with(
oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster))
)
).with(csrf())
)
.andExpect(status().isOk());
}
@@ -126,6 +130,7 @@ class ChannelApiIntegrationTest {
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\":\"helper\"}")
.with(oauth2Login().attributes((attrs) -> attrs.put("preferred_username", "intruder")))
.with(csrf())
)
.andExpect(status().isForbidden());
}