프로젝트에서 동적 쿼리를 적용하기 위해 Querydsl을 도입하기로 의사 결정했습니다.
예전 프로젝트에서 사용했던 Querydsl 5.1 버전을 사용하려 했으나 보안 취약점(CVE-2024-49203)이 발견되어 사용할 수 없었습니다.
CVE-2024-49203 보안 취약점
Querydsl and OpenFeign Querydsl Java Library Vulnerability Permits SQL/HQL Injection 내용에 따르면, 보안 취약점은 정렬을 수행하기 위해 `OrderSpecifier`를 사용할 때 공격자가 SQL/HQL Injection 공격을 할 수 있다는 것입니다.
`OrderSpecifier`가 공격자의 입력을 삭제하지 않기 때문에 발생하는 문제로 소개하고 있는데, 좀 더 자세한 내용을 살펴보겠습니다.
아래 코드는 위 링크의 내용의 일부를 발췌한 내용입니다.
OrderSpecifier order = new OrderSpecifier(Order.ASC, pathBuilder.get(orderBy));
JPAQuery<Test> orderedQuery = query.orderBy(order);
return orderedQuery.fetch();
위의 코드처럼 `OrderSpecifier`를 생성할 때, Expression 부분(`pathBuilder.get(String property)` 부분)은 정렬할 필드를 의미하고 Presentation 계층에서 받을 수 있습니다.
공격자는 정렬 조건을 전달할 때 쿼리 파라미터로 `orderBy`에 정렬할 필드와 정렬 순서를 전달할 수 있습니다.
그러나 `http://localhost:8000/products?orderBy=name+INTERSECT+SELECT+t+FROM+Test+t+WHERE+(SELECT+'2')='2'+ORDER+BY+t.id HTTP/1.1`처럼 입력한 예시를 보면, `orderBy`의 값으로 표현식을 전달할 수 있는데,
전달한 쿼리를 보면 `SELECT '2' = '2'` 표현식은 `True`가 됩니다.
이렇게 되면 `Test` 테이블의 모든 레코드를 읽을 수 있게되는 문제가 있습니다.
이 문제는 의도치 않은 정보가 유출되거나 테이블 크기가 클 경우 서비스 거부까지 이어질 수 있는 꽤 심각한 문제입니다.
직접 해결하는 방법
문제는 `pathBuilder.get(String property)` 부분에서 공격자의 SQL 또는 HQL이 그대로 쿼리되기 때문에 직접 필드를 지정하면 간단하게 해결할 수 있습니다.
// myQClass 내의 id 필드로 정렬하는 예시
queryFactory
.selectFrom(myQclass)
.orderBy(new OrderSpecifier<>(Order.DESC, myQclass.id))
.fetch();
만약 동적으로 여러 필드를 이용해 정렬을 수행해야 한다면 아래 코드 처럼 pathBuilder를 이용할 수도 있습니다. (보일러 플레이트 코드가 생기니 적절하게 함수로 빼면 되겠죠)
// Pageable 을 이용해 복합 필드로 정렬하는 예시
// OrderSpecifier 리스트 생성
List<OrderSpecifier> specifiers = new ArrayList<>();
for (Sort.Order order : pageable.getSort()) {
String property = order.getProperty();
if (property.equals("fieldA")) {
PathBuilder<MyQClass> pathBuilder = new PathBuilder<>(myQClass.getType(), myQClass.getMetadata());
specifiers.add(new OrderSpecifier<>(Order.DESC, pathBuilder.get(property, myQclass.fieldA.getType())));
}
if (property.equals("fieldB")) {
PathBuilder<MyQClass> pathBuilder = new PathBuilder<>(myQClass.getType(), myQClass.getMetadata());
specifiers.add(new OrderSpecifier<>(Order.DESC, pathBuilder.get(property, myQclass.fieldB.getType())));
}
}
// orderBy 적용
queryFactory.selectFrom(myQClass)
.orderBy(specifiers.toArray(OrderSpecifier[]::new));
다른 Querydsl 프로젝트 찾기
Querydsl 프로젝트는 2024년 5월 11일 마지막으로 JDK21 호환을 위한 패치, 충돌 해결 이외의 별다른 활동이 없습니다.
다른 querydsl 프로젝트에서 해결할 수 있는 방법이 없나 찾다 `CVE-2024-49203` 문제를 제기한 Issue 글들이 있었습니다.
몇 년 전부터 OpenFeign 팀에서 포크해서 유지 보수 중인 프로젝트가 있었고, 해당 보안 취약점을 패치한 것을 알 수 있었는데요,
마이그레이션은 정말 간단하게 `com.querydsl` 부분을 `io.github.openfeign.querydsl`로 바꾸기만 하면 된다고 합니다.
OpenFeign Querydsl 6 도입하기
기존에는 예전 프로젝트에서 사용했던 Querydsl 5 버전을 사용하려 했지만, Querydsl 6 버전을 도입하기로 의사 결정했습니다.
그 이유는 OpenFeign 팀의 Querydsl 프로젝트 릴리즈 노트에서 확인할 수 있었는데요.
Spring Boot 3.x 버전부터 Hibernate 6.x 버전을 사용하는데, Querydsl 5 버전은 Hibernate 6 버전을 완전히 지원하지 않았습니다.
Querydsl 6 버전부터 Hibernate 6.4 버전을 완전히 지원한다고 나와있어 Querydsl 6 버전을 도입하지 않을 이유가 없습니다.
의존성 추가
`build.gradle`에 의존성 추가를 합니다. 참고로 Querydsl 6 버전은 `jakarta` 패키지 의존성을 추가할 필요가 없습니다.
/* querydsl*/
implementation 'io.github.openfeign.querydsl:querydsl-jpa:6.10.1'
annotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:6.10.1' // QClass 생성이 안됨
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
이렇게 설정하고 빌드를 하면 QClass가 생성되지 않은 문제가 있었습니다.
이를 해결하기 위해선 `annotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:6.10.1'` 부분에 `jpa` 아티팩트 사용함을 명시해야 합니다.
AS-IS
annotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:6.10.1'
TO-BE
annotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:6.10.1:jpa'
의존성 추가 최종 버전
/* querydsl */
implementation 'io.github.openfeign.querydsl:querydsl-jpa:6.10.1'
annotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:6.10.1:jpa'
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
이렇게 설정하고 빌드를 하면 build 폴더에 QClass가 정상적으로 생성됨을 확인할 수 있습니다.
프로젝트에서 Querydsl을 사용하시는 분들이 이 포스팅을 읽고 도움이 되셨으면 합니다.
긴 글 읽어주셔서 감사합니다.
'Querydsl' 카테고리의 다른 글
[Querydsl] MySQL 공간 데이터(Point)의 반경 검색 (ST_CONTAINS) (1) | 2024.07.03 |
---|