fix: restrict thumbnail generation to images in the attachment library (#7079)

#### What type of PR is this?
/kind improvement
/area core
/milestone 2.20.x

#### What this PR does / why we need it:
限制缩略图生成仅针对附件库中的图片,防止任意 URI 的生成行为带来的潜在攻击风险

先 merge #7077 后才能合并此 PR

#### Does this PR introduce a user-facing change?

```release-note
限制缩略图生成仅针对附件库中的图片,防止任意 URI 的生成行为带来的潜在攻击风险
```
pull/7081/head
guqing 2024-11-26 11:28:29 +08:00 committed by GitHub
parent ec5c70f951
commit 5cefefe130
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 24 additions and 6 deletions

View File

@ -65,7 +65,7 @@ public class HtmlThumbnailSrcsetInjector {
*/ */
public static Mono<String> generateSrcset(URI src, ThumbnailService thumbnailService) { public static Mono<String> generateSrcset(URI src, ThumbnailService thumbnailService) {
return Flux.fromArray(ThumbnailSize.values()) return Flux.fromArray(ThumbnailSize.values())
.flatMap(size -> thumbnailService.generate(src, size) .flatMap(size -> thumbnailService.get(src, size)
.map(thumbnail -> thumbnail.toString() + " " + size.getWidth() + "w") .map(thumbnail -> thumbnail.toString() + " " + size.getWidth() + "w")
) )
.collect(StringBuilder::new, (builder, srcsetValue) -> { .collect(StringBuilder::new, (builder, srcsetValue) -> {

View File

@ -18,5 +18,16 @@ public interface ThumbnailService {
*/ */
Mono<URI> generate(URI imageUri, ThumbnailSize size); Mono<URI> generate(URI imageUri, ThumbnailSize size);
/**
* <p>Get thumbnail by the given image uri and size.</p>
* <p>It depends on the {@link #generate(URI, ThumbnailSize)} method, currently the thumbnail
* generation is limited to the attachment service, that is, the thumbnail is strongly
* associated with the attachment.</p>
*
* @return if thumbnail exists, return the thumbnail uri, otherwise return the original image
* uri
*/
Mono<URI> get(URI imageUri, ThumbnailSize size);
Mono<Void> delete(URI imageUri); Mono<Void> delete(URI imageUri);
} }

View File

@ -72,6 +72,13 @@ public class ThumbnailServiceImpl implements ThumbnailService {
}); });
} }
@Override
public Mono<URI> get(URI imageUri, ThumbnailSize size) {
return fetchThumbnail(imageUri, size)
.map(thumbnail -> URI.create(thumbnail.getSpec().getThumbnailUri()))
.defaultIfEmpty(imageUri);
}
@Override @Override
public Mono<Void> delete(URI imageUri) { public Mono<Void> delete(URI imageUri) {
Assert.notNull(imageUri, "Image uri must not be null"); Assert.notNull(imageUri, "Image uri must not be null");

View File

@ -66,7 +66,7 @@ public class ThumbnailEndpoint implements CustomEndpoint {
private Mono<ServerResponse> getThumbnailByUri(ServerRequest request) { private Mono<ServerResponse> getThumbnailByUri(ServerRequest request) {
var query = new ThumbnailQuery(request.queryParams()); var query = new ThumbnailQuery(request.queryParams());
return thumbnailService.generate(query.getUri(), query.getSize()) return thumbnailService.get(query.getUri(), query.getSize())
.filterWhen(uri -> isAccessible(request, uri)) .filterWhen(uri -> isAccessible(request, uri))
.defaultIfEmpty(query.getUri()) .defaultIfEmpty(query.getUri())
.flatMap(uri -> ServerResponse.temporaryRedirect(uri).build()); .flatMap(uri -> ServerResponse.temporaryRedirect(uri).build());

View File

@ -18,7 +18,7 @@ public class ThumbnailFinderImpl implements ThumbnailFinder {
@Override @Override
public Mono<String> gen(String uriStr, String size) { public Mono<String> gen(String uriStr, String size) {
return Mono.fromSupplier(() -> URI.create(uriStr)) return Mono.fromSupplier(() -> URI.create(uriStr))
.flatMap(uri -> thumbnailService.generate(uri, ThumbnailSize.fromName(size))) .flatMap(uri -> thumbnailService.get(uri, ThumbnailSize.fromName(size)))
.map(URI::toString) .map(URI::toString)
.onErrorResume(Throwable.class, e -> { .onErrorResume(Throwable.class, e -> {
log.debug("Failed to generate thumbnail for [{}], error: [{}]", uriStr, log.debug("Failed to generate thumbnail for [{}], error: [{}]", uriStr,

View File

@ -39,7 +39,7 @@ class ThumbnailEndpointTest {
@Test @Test
void thumbnailUriNotAccessible() { void thumbnailUriNotAccessible() {
when(thumbnailService.generate(any(), any())) when(thumbnailService.get(any(), any()))
.thenReturn(Mono.just(URI.create("/thumbnail-not-found.png"))); .thenReturn(Mono.just(URI.create("/thumbnail-not-found.png")));
webClient.get() webClient.get()
.uri("/thumbnails/-/via-uri?size=l&uri=/myavatar.png") .uri("/thumbnails/-/via-uri?size=l&uri=/myavatar.png")

View File

@ -42,13 +42,13 @@ class ThumbnailFinderImplTest {
@Test @Test
void shouldGenWhenUriIsValid() { void shouldGenWhenUriIsValid() {
when(thumbnailService.generate(any(), any())) when(thumbnailService.get(any(), any()))
.thenReturn(Mono.just(URI.create("/test-thumb.jpg"))); .thenReturn(Mono.just(URI.create("/test-thumb.jpg")));
thumbnailFinder.gen("/test.jpg", "l") thumbnailFinder.gen("/test.jpg", "l")
.as(StepVerifier::create) .as(StepVerifier::create)
.expectNext("/test-thumb.jpg") .expectNext("/test-thumb.jpg")
.verifyComplete(); .verifyComplete();
verify(thumbnailService).generate(any(), any()); verify(thumbnailService).get(any(), any());
} }
} }