mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Sanitize log input
This commit is contained in:
@@ -103,6 +103,7 @@ public class SchemaMigration implements ApplicationRunner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SECURITY: This is ok, because tableName and columnName are controlled internally and not from user input.
|
||||||
try {
|
try {
|
||||||
jdbcTemplate.execute(
|
jdbcTemplate.execute(
|
||||||
"ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + dataType + " DEFAULT " + defaultValue
|
"ALTER TABLE " + tableName + " ADD COLUMN " + columnName + " " + dataType + " DEFAULT " + defaultValue
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package dev.kruhlmann.imgfloat.config;
|
package dev.kruhlmann.imgfloat.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.http.Cookie;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -16,19 +20,15 @@ import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepo
|
|||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
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.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.CsrfException;
|
||||||
import org.springframework.security.web.csrf.CsrfFilter;
|
import org.springframework.security.web.csrf.CsrfFilter;
|
||||||
|
import org.springframework.security.web.csrf.CsrfToken;
|
||||||
|
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
import org.springframework.web.util.WebUtils;
|
import org.springframework.web.util.WebUtils;
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.http.Cookie;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@@ -85,10 +85,12 @@ public class SecurityConfig {
|
|||||||
)
|
)
|
||||||
.logout((logout) -> logout.logoutSuccessUrl("/").permitAll())
|
.logout((logout) -> logout.logoutSuccessUrl("/").permitAll())
|
||||||
.exceptionHandling((exceptions) ->
|
.exceptionHandling((exceptions) ->
|
||||||
exceptions.defaultAuthenticationEntryPointFor(
|
exceptions
|
||||||
|
.defaultAuthenticationEntryPointFor(
|
||||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
||||||
new AntPathRequestMatcher("/api/**")
|
new AntPathRequestMatcher("/api/**")
|
||||||
).accessDeniedHandler(csrfAccessDeniedHandler())
|
)
|
||||||
|
.accessDeniedHandler(csrfAccessDeniedHandler())
|
||||||
)
|
)
|
||||||
.csrf((csrf) ->
|
.csrf((csrf) ->
|
||||||
csrf
|
csrf
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public class ChannelApiController {
|
|||||||
LOG.warn(
|
LOG.warn(
|
||||||
"No authorized Twitch client found for {} while fetching admin suggestions for {}",
|
"No authorized Twitch client found for {} while fetching admin suggestions for {}",
|
||||||
sessionUsername,
|
sessionUsername,
|
||||||
broadcaster
|
broadcaster.replaceAll("[\n\r]", "_")
|
||||||
);
|
);
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ public class ChannelApiController {
|
|||||||
authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername);
|
authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername);
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Updating canvas for {} by {}: {}x{}",
|
"Updating canvas for {} by {}: {}x{}",
|
||||||
broadcaster,
|
broadcaster.replaceAll("[\n\r]", "_"),
|
||||||
sessionUsername,
|
sessionUsername,
|
||||||
request.getWidth(),
|
request.getWidth(),
|
||||||
request.getHeight()
|
request.getHeight()
|
||||||
@@ -276,7 +276,7 @@ public class ChannelApiController {
|
|||||||
LOG.info(
|
LOG.info(
|
||||||
"Updating visibility for asset {} on {} by {} to hidden={} ",
|
"Updating visibility for asset {} on {} by {} to hidden={} ",
|
||||||
assetId,
|
assetId,
|
||||||
broadcaster,
|
broadcaster.replaceAll("[\n\r]", "_"),
|
||||||
sessionUsername,
|
sessionUsername,
|
||||||
request.isHidden()
|
request.isHidden()
|
||||||
);
|
);
|
||||||
@@ -284,7 +284,12 @@ public class ChannelApiController {
|
|||||||
.updateVisibility(broadcaster, assetId, request)
|
.updateVisibility(broadcaster, assetId, request)
|
||||||
.map(ResponseEntity::ok)
|
.map(ResponseEntity::ok)
|
||||||
.orElseThrow(() -> {
|
.orElseThrow(() -> {
|
||||||
LOG.warn("Visibility update for missing asset {} on {} by {}", assetId, broadcaster, sessionUsername);
|
LOG.warn(
|
||||||
|
"Visibility update for missing asset {} on {} by {}",
|
||||||
|
assetId.replaceAll("[\n\r]", "_"),
|
||||||
|
broadcaster.replaceAll("[\n\r]", "_"),
|
||||||
|
sessionUsername
|
||||||
|
);
|
||||||
return new ResponseStatusException(NOT_FOUND, "Asset not found");
|
return new ResponseStatusException(NOT_FOUND, "Asset not found");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,11 @@ public class ViewController {
|
|||||||
broadcaster,
|
broadcaster,
|
||||||
sessionUsername
|
sessionUsername
|
||||||
);
|
);
|
||||||
LOG.info("Rendering admin console for {} (requested by {})", broadcaster, sessionUsername);
|
LOG.info(
|
||||||
|
"Rendering admin console for {} (requested by {})",
|
||||||
|
broadcaster.replaceAll("[\n\r]", "_"),
|
||||||
|
sessionUsername
|
||||||
|
);
|
||||||
Settings settings = settingsService.get();
|
Settings settings = settingsService.get();
|
||||||
model.addAttribute("broadcaster", broadcaster.toLowerCase());
|
model.addAttribute("broadcaster", broadcaster.toLowerCase());
|
||||||
model.addAttribute("username", sessionUsername);
|
model.addAttribute("username", sessionUsername);
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class AuthorizationService {
|
|||||||
String broadcaster,
|
String broadcaster,
|
||||||
String sessionUsername
|
String sessionUsername
|
||||||
) {
|
) {
|
||||||
|
broadcaster = broadcaster.replaceAll("[\n\r]", "_");
|
||||||
if (!userIsBroadcasterOrChannelAdminForBroadcaster(broadcaster, sessionUsername)) {
|
if (!userIsBroadcasterOrChannelAdminForBroadcaster(broadcaster, sessionUsername)) {
|
||||||
LOG.warn(
|
LOG.warn(
|
||||||
"Access denied for broadcaster/admin-only action by user {} on broadcaster {}",
|
"Access denied for broadcaster/admin-only action by user {} on broadcaster {}",
|
||||||
|
|||||||
@@ -129,11 +129,14 @@ public class ChannelDirectoryService {
|
|||||||
|
|
||||||
public Optional<AssetView> createAsset(String broadcaster, MultipartFile file) throws IOException {
|
public Optional<AssetView> createAsset(String broadcaster, MultipartFile file) throws IOException {
|
||||||
long fileSize = file.getSize();
|
long fileSize = file.getSize();
|
||||||
long maxSize = uploadLimitBytes;
|
if (fileSize > uploadLimitBytes) {
|
||||||
if (fileSize > maxSize) {
|
|
||||||
throw new ResponseStatusException(
|
throw new ResponseStatusException(
|
||||||
PAYLOAD_TOO_LARGE,
|
PAYLOAD_TOO_LARGE,
|
||||||
String.format("Uploaded file is too large (%d bytes). Maximum allowed is %d bytes.", fileSize, maxSize)
|
String.format(
|
||||||
|
"Uploaded file is too large (%d bytes). Maximum allowed is %d bytes.",
|
||||||
|
fileSize,
|
||||||
|
uploadLimitBytes
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Channel channel = getOrCreateChannel(broadcaster);
|
Channel channel = getOrCreateChannel(broadcaster);
|
||||||
|
|||||||
@@ -349,8 +349,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
const broadcaster = /*[[${broadcaster}]]*/ '';
|
const broadcaster = /*[[${broadcaster}]]*/ "";
|
||||||
const username = /*[[${username}]]*/ '';
|
const username = /*[[${username}]]*/ "";
|
||||||
const UPLOAD_LIMIT_BYTES = /*[[${uploadLimitBytes}]]*/ 0;
|
const UPLOAD_LIMIT_BYTES = /*[[${uploadLimitBytes}]]*/ 0;
|
||||||
const SETTINGS = JSON.parse(/*[[${settingsJson}]]*/);
|
const SETTINGS = JSON.parse(/*[[${settingsJson}]]*/);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -115,9 +115,9 @@ class ChannelApiIntegrationTest {
|
|||||||
|
|
||||||
mockMvc
|
mockMvc
|
||||||
.perform(
|
.perform(
|
||||||
delete("/api/channels/{broadcaster}/assets/{id}", broadcaster, assetId).with(
|
delete("/api/channels/{broadcaster}/assets/{id}", broadcaster, assetId)
|
||||||
oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster))
|
.with(oauth2Login().attributes((attrs) -> attrs.put("preferred_username", broadcaster)))
|
||||||
).with(csrf())
|
.with(csrf())
|
||||||
)
|
)
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user