https://github.blog/2023-06-15-codeql-zero-to-hero-part-2-getting-started-with-codeql/
code QL에 대한 Sylwia Budzynska 의 글을 읽고 정리하였습니다.
code QL은 https://codeql.github.com/ 이곳에서 사용할 수 있습니다.
Part 1은 code QL보다 static analysis에 대한 내용이 주를 이루고 있어 간략히 정리하겠습니다.
Part 1. 취약성 연구를 위한 정적 분석 기초
정적 분석은 코드를 자체를 실행하지 않고, 응용 프로그램 코드에서 잠재적 오류를 분석할 수 있는 절차이다. 이 기술을 사용하여 다양하 검사, 검증을 수행하고 코드의 문제를 찾아낼 수 있다.
Github에서는 semantic analysis engine인 CodeQL을 통해 코드를 스캔하고 정적 분석을 수행한다.
취약점 탐지 - sources and sinks
취약점 중에는 정적 분석으로 쉽게 찾을 수 있는 취약점도 있고 수동적인 분석을 통해서만 찾을 수 있는 취약점도 있다.
정적 분석으로 찾을 수 있는 취약점 중 하나는 injection 취약점이 있다. Injeciton 취약점의 주요 원인은 신뢰할 수 없는 사용자 제어 입력이 프로그램에 민감하거나 위험한 기능에 사용되기 때문이다. 정적 분석에서 이를 나타내기 위해서, data flow, sources 그리고 sinks라는 용어를 사용한다.
사용자 입력은 일반적으로 데이터의 출처인, 애플리케이션의 entry point에서 나온다. 여기에는 GET및 POST같은 HTTP 메서의 매개변수 또는 프로그램에 대한 command line argument가 포함된다. 이를 source라고 한다.
SQL injection을 예로 들었을 때, 신뢰할 수 없는, unsanitized data로 호출하면 안되는 위험한 함수의 예는 python MySQLdb 라이브러리의 MySQLCursor.execute() 또는 임의의 표현식을 평가하는 eval() 함수가 있다. 이러한 위험한 기능을 sink라고 한다.
기능이 잠재적으로 위험하다고 해서 바로 공격 가능한 취약점이라는 것을 의미하는건 아니다. 많은 sink들이 각각 안전하게 사용하는 방법들을 가지고 있다.
취약성이 존재하려면, 위험한 기능에서 적절한 삭제 또는 입력 유효성 겁사 없이, 안전하지 않은 user-controlled input을 사용해야 한다.즉, source와 sink 사이에 경로가 있어야 한다. 이 경우 source에서 sink로 데이터가 흐른다고 하며 data flow라고 칭한다.
소스 및 싱크 찾기
source는 취약점이 발생하는 곳, sink는 취약점이 실행되는 곳이기 때문에, 소스코드에서 취약점 중 하나를 찾아서 소스가 민감한 작업을 수행하는 기능(sink) 또는 그 반대로 sink에서 시작하여 역으로 찾아갈 수 있다.
그러나 단순 grep으로 소스코드를 찾는 경우 많은 오탐을 생성하기 때문에 분석에 불필요하다.
또한, 한 번에 한 유형의 소스만 검색하고 검토할 수 있다. GET 요청과 관련한 source를 찾으면 실제 취약점으로 이어지지 않는 소스가 수천개에 이를 것이다..
대신 취약할 가능성이 있는 sink를 검사하고 소스를 찾기 위해 역으로 이동할 수 있다.
간단한 패턴 일치 방식은 신뢰할 수 있는 방법이 아니다.
최초의 정적 분석 도구는 이러한 문제들을 해결하도록 설계되었다.
초기 정적 분석 도구 - 어휘 패턴 매칭
정적 분석 도구가 수행하는 많은 단꼐는 컴파일러와 유사하다. 컴파일러, 인터프리터에 대해 생각해보면, 정적 분석의 한 형태인 유형 검사를 수행하고 있다.
유형 검사는 오퍼레이션이 올바른 유형의 오브젝트에 적용되는 경우 오브젝트 유형이 지정된 컨텍스트에서 예상되는 것과 일치하는 지 확이하는 것이다.
어휘 분석은 소스 코드를 읽고 코드의 의미 체계에 기여하지 않는 문자 (주석 등)을 무시하고 토큰 스트림을 변환시킨다. (토큰은 문자, 리터럴로 구성됨)
정적 분석 도구에 도입된 기능은 위험한 싱크에 대한 정보를 포함하고 토큰에서 싱크 이름을 일치시키는 knowledge base였다.
이는 첫 번째 문제인 많은 오탐지를 해결하였다. 그러나 두 번째 문제 (취약성과 관린이 없음)는 여전히 남아있다.
이를 해결하기 위한 방법은 source와 sink간의 연결이 있는지 확인하는 것이다.
syntactic pattern matching, abstract syntax tree, and control flow graph
시간이 지남에 따라 정적 분석 도구는 AST(abstract syntax tree)와 같은 컴파일러의 더 많은 기술을 채택했다.
많은 정적 분석 기술이 존재하지만 가장 인기 많은 방법인 data flow analysis with taint analysis 를 이 블로그에서 소개한다.
코드에서 토큰을 스캔한 후에는 더 쉽게 쿼리할 수 있도록 추상적인 표현으로 빌드한다. 일반적인 접근 방식은 parse tree로 분석하고, AST를 구현하는 것이다.
AST( 추상 구문 트리)는 소스코드의 트리 표현이다.
소스 코드를 AST로 표현하면 메서도 호출을 나타내는 모든 노드를 쿼리할 수 있고, 결과적으로 보다 정확한 쿼리를 만들어 잘못된 output을 줄일 수 있다.
그러나 아직 데이터가 source에서 sink로 흐르는지는 알 수 없다.
데이터 흐름 분석 및 오염 추적
데이터 흐름 분석: 프로그램의 control flow graph를 통해 데이터가 어떻게 이동하는 지 추적한다. 값이 보존되는 데이터만 추적한다.
즉, 문자열이 다른 문자열과 연결되거나 객체의 속성에 할당되는 경우처럼 값이 변경되는 경우에는 추적하지 않는다.
오염 분석: 데이터 흐름 분석과 유사하지만 다른 규칙을 사용한다. 특정 입력(source)를 오염된 것으로 간주한다. 그러면 해당 오염이 애플리케이션의 특정 위치로 전파되는지를 확인할 수 있다. 값이 보존되지 않는 경우에도 추적 가능하다.
source에서 오염된 데이터가 시작하는 것으로 간주하고 sink는 오염된 데이터가 도달하면 안되는 곳이다. 오염 전파(tatint propagation)로 오염됨 데이터가 프로그램의 다른 부분으로 이동하며 오염을 전파한다.
이러한 기술을 통해 user-controlled input이 sink로 흐르는 경로를 자동으로 분석하고 결과를 쉽게 정리할 수 있다.
Part 2. Code QL 시작하기
Code QL은 애플리케이션의 취약점을 자동으로 스캔하고 manual code review를 지원하는데 사용할 수 있는 정적 분석 도구이다.
블로그에서는 Code QL과 CodeQL 쿼리를 작성하는 방법에 대해 자세히 살펴본다.
Common uses of Code QL for security research and application security
Code QL은 취약점에 대한 자동 스캐닝을 제공하며, codebase를 탐색하고 manual testing을 지원하는 도구로 사용할 수 있다.
다음과 같은 용도를 가진다.
- 취약점 유형에 대한 소스 코드 자동 스캔
- Variant analysis: 코드 베이스에서 취약점이 발견된 경우 다른 부분에 동일한 취약점 확인 가능
- manual code reiview: 분석된 코드베이스에 대해 "Code QL 질문" 가능. (ex. attack surface가 어디인지, codebase에서 soruce란 무엇일지, sink가 무엇일지, source가 취약한 함수로 이어지는 지)
Code QL
code QL은 Semmle에서 개발한 정적 분석 도구이고 데이터 흐름 분석 및 오염 분석을 사용하여 코드 오류를 찾아낸다.
지원 언어는 C/C++, C#, Go, Java, Kotlin, JavaScript, Python, Ruby, TypeScript, Swift가 있다.
핵심 아이디어는 프로그램에 대한 fact database를 만든 다음, QL이라는 특수 쿼리를 사용하여 취약한 패턴을 쿼리하고 코드를 데이터로 분석하는 것이다.
또한 Code QL은 오픈 소스이므로 누구나 Code QL을 만들고 기여할 수 있다.
Code QL로 코드 스캔하기
Code QL을 직접 시험해보는 가장 쉬운 방법은 Github Action으로 코드 스캔을 활성화 하는 것이다.
Code QL 데이터베이스
레포지토리에서 Code QL을 활성화하면 Code QL데이터베이스가 자동으로 생성된다. 특정 아티팩트에 대한 쿼리 또는 쿼리를 직접 수정하려면 어떻게 해야할까?
우선 Code QL 데이터베이스가 생성되는 방식과, 데이터베이스에 포함된 내용을 살펴보자.
high level에서 code QL은 각 언어에 대해 소스코드를 추출하고 구문 분석을 수행하거나 실행중인 빌드 내에서 이미 존재하는 컴파일러 실행을 계측하여 이해하도록 변환한다. 데이터베이스는 클래스, 함수같은 다양한 소스코드 요소 정보를 포함하고 각각을 별도의 테이블에 넣는 코드 베이스의 관계형 표현이다.
Code QL 데이터베이스는 Github에서 가장 인기 많은 프로젝트 중 하나이다.
VS Code에서 Code QL extension, 또는 Github API를 통해 다운로드 할 수 있다.
Code QL command line tool을 사용하여 로컬에서 직접 Code QL db를 생성할 수 있다.
Code QL DB 생성 및 분석
Code QL 데이터베이스를 성공적으로 생성하려면 일반적으로 프로그램 외부에 있는 라이브러리 및 디펜던시를 포함해야 한다.
인터프리터 언어의 경우, Code QL DB를 생성하기 위해 디펜던시를 설치할 필요가 없다.
컴파일 언어의 경우에는 빌드에 필요한 범위까지 디펜던시를 설치해야 한다. 간단히 말해, 빌드를 수행하는데 필요한 작업을 수행한다.
QL 쿼리 언어 - 고유한 Code QL query 작성
Code QL db는 다양한 소스 코드에 대한 정보를 포함하는 코드베이스의 관계형 표현이기 때문에 AST 노드와 같은 구문 요소 및 data flow 노드와 같은 의미 요소에 대해 Code QL db를 쿼리할 수 있다
Code QL query는 SQL 구문과 유사하며 from, where, select 가 존재한다.
- from: 쿼리할 유형 및 변수 정의
- where: 변수 조건 정의
- select: 쿼리 출력 정의
python codebase의 모든 함수 호출에 대해 CodeQL에 요청할 때 쿼리 예시는 다음과 같다.
import python
from Call c
where c.getLocation().getFile().getRelativePath().regexpMatch("2/challenge-1/.*")
select c, "This is a function call"
Call은 프로그램의 모든 호출을 나타내는 type이고 c는 변수명이다.
함수 호출이 있는 파일의 상대경로가 "2/challenge-1/"로 시작하는지 확인한다. regexMatch("2/challenge-1/.*") 은 2/challenge-1/로 시작하고 뒤에 어떤 문자든 올 수 있는 파일을 찾는다.
세부 내용들은 블로그에서 challenge 를 따라가며면서 실습할 수 있다.. (너무 많다 나중에 쓸 때 해봐야겠다.)
Code QL을 통한 오염 추적은 마지막 쯤에 위치한다. CWE(Common Weakness Enumeration)관련 쿼리는 별도로
https://codeql.github.com/codeql-query-help/full-cwe/
이곳에서 확인한다.