
CVE-2021-21380을 다루는 CodeQL 워크숍
이 워크숍에서는 구문적 및 의미적 추론을 사용하여 CVE-2021-21380에 문서화된 XWiki 플랫폼의 평점 컴포넌트에서 SQL 인젝션을 찾아보겠습니다.
가능하면 워크숍 전에 이 섹션을 완료하세요.
Visual Studio Code를 설치합니다.
Visual Studio Code용 CodeQL 확장을 설치합니다.
CodeQL CLI를 설치할 필요는 없습니다: 확장이 이 작업을 대신 처리해 줍니다.
이 저장소를 클론합니다:
git clone https://github.com/advanced-security/codeql-workshop-cve-2021-21380.git
xwiki-platform-CVE-2021-21380.zip 파일을 마우스 오른쪽 버튼으로 클릭하고 CodeQL: Set Current Database 명령을 선택합니다.Cmd/Ctrl+Shift+P)에서 CodeQL: Install Pack Dependencies 명령을 검색하여 실행합니다.github를 입력하여 목록을 필터링합니다.cve-2021-21380-exercises, cve-2021-21380-exercises-tests, cve-2021-21380-solutions, cve-2021-21380-solutions-tests 옆의 확인란을 선택합니다.이 워크숍은 Java에 대한 QL 언어 지원을 소개하는 여러 연습 문제로 구성되어 있으며, 알려진 SQL 인젝션을 찾는 최종 쿼리로 끝납니다. 이 연습 문제들을 통해 다음을 배우게 됩니다:
이 워크숍에서는 XWiki Platform의 평점 API 컴포넌트에서 알려진 _SQL 인젝션 취약점_을 찾아보겠습니다. 이러한 취약점은 외부 사용자가 제어하는 정보가 SQL 쿼리를 안전하지 않게 구성하고 실행하는 애플리케이션 코드에 도달할 때 애플리케이션에서 발생할 수 있습니다.
이 워크숍에서 다루는 알려진 SQL 인젝션은 GitHub Advisory Database의 GHSA-79rg-7mv3-jrr5에서 검토됩니다. SQL 인젝션과 가능한 변형을 찾기 위해 다음 하위 문제들을 다룰 것입니다:
처음 몇 개의 연습 문제에서는 추상 구문 트리(AST)를 사용하여 구문 정보를 추론하고 다음을 식별합니다:
쿼리 exercise1.ql을 완성하여 프로그램에서 이름이 getAverageRating인 모든 메서드와 해당 선언 타입을 찾으세요.
java 모듈은 프로그램의 메서드를 추론하기 위한 Method 클래스를 제공합니다.Method 클래스는 메서드의 이름을 추론하기 위한 멤버 조건자 getName과 hasName을 제공합니다.Method 클래스는 메서드를 선언하는 타입을 추론하기 위한 멤버 getDeclaringType을 제공합니다.솔루션은 쿼리 exercise1.ql에서 찾을 수 있습니다.
연습 문제 1의 솔루션은 메서드 목록을 반환합니다. 그중 일부는 RatingsManager라는 인터페이스에 정의되어 있고, 일부는 AbstractRatingsManager 및 RatingsScriptService 클래스에 정의되어 있습니다.
쿼리가 반환한 정보와 XWiki 컴포넌트 문서를 통해 다음을 추론할 수 있습니다:
Role 어노테이션이 달린 인터페이스와 Component 어노테이션이 달린 구현체로 구성됩니다.ScriptService를 확장하는 컴포넌트는 스크립팅을 통해 위키 페이지에서 접근할 수 있게 됩니다.쿼리 exercise2.ql을 완성하여 Component 어노테이션이 달린 모든 클래스를 찾으세요.
해당 어노테이션 타입의 정규화된 이름(fully qualified name)은 org.xwiki.component.annotation.Component입니다.
this 키워드를 사용하여 접근할 수 있습니다.Class 클래스는 연관된 어노테이션을 가져오기 위한 getAnAnnotation 메서드를 제공합니다.Annotation 클래스는 해당 타입을 추론하기 위한 getType 멤버 조건자를 제공합니다.Type 클래스는 타입의 이름을 추론하기 위한 멤버 조건자 getName과 hasName을 제공합니다.RefType 클래스는 RefType의 정규화된 이름을 추론하기 위한 멤버 조건자 getQualifiedName과 hasQualifiedName을 제공합니다.솔루션은 쿼리 exercise2.ql에서 찾을 수 있습니다.
쿼리 excercise3.ql을 완성하여 ScriptService 인터페이스를 구현하는 모든 컴포넌트를 찾으세요.
Class 타입은 클래스의 슈퍼 타입, 즉 extends하거나 implements하는 타입을 추론하기 위한 멤버 조건자 getASuperType을 제공합니다.솔루션은 쿼리 exercise3.ql에서 찾을 수 있습니다.
이 시점에서 우리는 사용자가 호출할 수 있는 메서드를 구문적으로 식별했으며, 해당 메서드의 매개변수는 워크숍 후반에 신뢰할 수 없는 데이터의 소스로 간주할 것입니다.
다음 연습 문제에서는 가능한 싱크를 조사하고 식별할 것입니다. 연습 문제 1의 결과를 통해 구현 중 하나가 getAverageRatingFromQuery 메서드를 호출한다는 것을 추론할 수 있습니다. 유사한 쿼리를 사용하여 getAverageRatingFromQuery의 선언 타입을 찾을 수 있으며, 이를 통해 AbstractRatingsManager 클래스의 구현이 search 메서드로 전달되는 SQL 문을 구성한다는 것을 확인할 수 있습니다.
search 메서드는 의존성에 구현되어 있으므로 해당 구현을 사용할 수 없습니다. 다음 연습 문제에서는 사용 가능한 타입 정보를 사용하여 이 search 메서드 호출과 해당 선언 타입을 식별할 것입니다.
쿼리 exercise4.ql을 완성하여 search 메서드에 대한 모든 메서드 호출을 찾고 해당 선언 타입을 식별하세요.
MethodCall 타입은 메서드 호출에 대해 추론할 수 있는 수단을 제공합니다.MethodAcccess 타입은 메서드 호출의 대상을 추론하기 위한 멤버 조건자 getMethod를 제공합니다.솔루션은 쿼리 exercise4.ql에서 찾을 수 있습니다.
쿼리 exercise5.ql을 완성하여 XWikiStorageInterface 인터페이스(정규화된 이름은 com.xpn.xwiki.store.XWikiStoreInterface)에 선언된 메서드에 대한 모든 메서드 호출을 찾으세요.
instanceof 키워드는 값이 타입으로 표현되는 값의 집합에 속한다는 것을 명시하는 데 사용할 수 있습니다.
예를 들어, 인터페이스 메서드에 대한 모든 호출을 식별하려면:
import java
from MethodCall ma
where ma.getMethod.getDeclaringType() instanceof Interface
select ma
솔루션은 쿼리 exercise5.ql에서 찾을 수 있습니다.
이 시점에서 우리는 QL을 사용하여 가능한 소스와 싱크를 구문적으로 설명했습니다. 프로그램에서 이 지점들 사이에 정보 흐름이 있는지 확인하기 위해 오염 추적(taint tracking)을 사용하여 프로그램을 의미적으로 분석할 것입니다. 지원하는 언어의 표준 라이브러리는 두 가지 데이터 흐름 메커니즘을 제공합니다:
DataFlow 모듈은 값을 보존하는 정보 흐름을 지원합니다.TaintTracking 모듈은 값이 수정되더라도 정보 흐름을 지원합니다.후자가 중요한 이유는 SQL 인젝션과 같은 인젝션 취약점에서는 신뢰할 수 없는 데이터가 실행되는 더 큰 문장의 일부가 되는 것이 일반적이기 때문입니다.
이 워크숍에서는 기존 SQL 인젝션 오염 추적 구성을 재사용하고, 모델링한 소스와 싱크로 확장하여 SQL 인젝션을 찾을 것입니다. 구성을 확장하는 방법을 이해하기 위해 아래 구성 정의를 살펴보는 것부터 시작하겠습니다.
class QueryInjectionFlowConfig extends TaintTracking::Configuration {
QueryInjectionFlowConfig() { this = "SqlInjectionLib::QueryInjectionFlowConfig" }
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink }
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
any(AdditionalQueryInjectionTaintStep s).step(node1, node2)
}
}
구성이 이러한 프로그램 위치를 식별하는 데 사용하는 isSource와 isSink 조건자는 둘 다 앞서 살펴본 instanceof 키워드를 사용합니다. RemoteFlowSource와 QueryInjectionSink 클래스는 둘 다 abstract 클래스입니다.
이것은 표준 라이브러리에서 자주 만나게 되는 일반적인 패턴이며, 이 패턴을 통해 RemoteFlowSource와 QueryInjectionSink 클래스가 나타내는 값의 집합을 확장할 수 있습니다.
abstract class를 확장하는 QL class는 슈퍼 클래스가 나타내는 값의 집합을 세분화하지 않지만, 서브클래스가 나타내는 값을 슈퍼클래스에 추가합니다.
다음 연습 문제에서는 이러한 서브클래스들을 구현하고 최종 쿼리를 구성할 것입니다.
쿼리 exercise6.ql을 완성하여 연습 문제 3에서 식별한 컴포넌트 클래스의 공용 메서드 매개변수로 RemoteFlowSource의 값 집합을 확장하세요.
instanceof 키워드는 값이 타입으로 표현되는 값의 집합에 속한다는 것을 명시하는 데 사용할 수 있습니다.
예를 들어, 인터페이스 메서드에 대한 모든 호출을 식별하려면:
import java
from MethodCall ma
where ma.getMethod.getDeclaringType() instanceof Interface
select ma
솔루션은 쿼리 exercise6.ql에서 찾을 수 있습니다.
쿼리 exercise7.ql을 완성하여 연습 문제 5에서 식별한 저장소 인터페이스의 메서드 호출 인자로 QueryInjectionSink의 값 집합을 확장하세요.
exists 수식은 exists의 범위 내에서 추론할 수 있는 임시 변수를 도입할 수 있게 해줍니다.
다음 예제는 exists 표현식을 사용하여 메서드 집합을 호출되는 메서드로 줄입니다.
from Method m
where m.hasName("foo") and exists(MethodCall ma | ma.getMethod() = m)
select m
MethodCall 클래스는 메서드 접근의 한정자(qualifier)를 추론하기 위한 멤버 조건자 getQualifer를 제공합니다.
솔루션은 쿼리 exercise7.ql에서 찾을 수 있습니다.
쿼리 exercise8.ql을 완성하여 이전 연습 문제들의 솔루션을 최종 솔루션으로 결합하세요.
솔루션은 쿼리 exercise8.ql에서 찾을 수 있습니다.
XWikiStoreInterface의 직접 사용으로 한정했습니다. 인터페이스 구현체의 직접 사용을 포함하도록 싱크를 확장하세요. Class 클래스는 extendsOrImplements 멤버 조건자를 제공하고, Method 클래스는 overridesOrInstantiates 멤버 조건자를 제공하므로 도움이 될 수 있습니다.