mirror of https://github.com/halo-dev/halo
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
parent
ec5c70f951
commit
5cefefe130
|
@ -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) -> {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue