CodeQL là nền tảng phân tích mã nguồn được sử dụng bởi các nhà nghiên cứu bảo mật để tự động phân tích lỗi. CodeQL có thể thực hiện thông qua nền tảng online trên query console LGTM.com.
CodeQL dựa trên ngôn ngữ truy vấn mạnh mẽ được gọi là QL. Hiểu QL giúp cho ta có cái nhìn tốt hơn về việc đọc hiểu cũng như viết mã phân tích với CodeQL.
Hiện tại CodeQL đang hỗ trợ cho các loại ngôn ngữ: C/C++, C#, Go, Java, Python, Javascript, COBOL
Giới thiệu QL
QL là một ngôn ngữ truy vấn mạnh mẽ làm nền tảng cho CodeQL.Truy vấn được viết bởi CodeQL có thể tìm lỗi và phát hiện các loại biến thể của lỗ hổng bảo mật liên quan. Để đọc các ví dụ lỗ hổng bảo mật mới phát hiện trong open souce project vào GitHub Security Lab.
QL là ngôn ngữ truy vấn logic, vì vậy nó được xây dựng từ các cấu trúc logic. QL sử dụng các kết nối logic phổ biến (như and
, or
, not
), định lượng (như forall
, exists
), và các khái niệm logic quan trọng khác như predicate.
QL cũng hỗ trợ đệ quy và tập hợp. Điều này cho phép ta viết truy vấn đệ quy phức tạp sử dụng cú pháp QL đơn giản và sử dụng các hàm tập hợp như count
, sum
, average
một cách trực tiếp.
Để hiểu rõ hơn về QL vào About QL, QL language handbook
Cú pháp cơ bản
Cú pháp cơ bản của QL trông giống như SQL, nhưng nó được sử dụng hơi khác.
Một câu truy vấn được định nghĩa bởi mệnh đề select
, nó chỉ ra kết quả mong muốn ở đầu ra.
Một câu truy vấn đơn giản
1 2 | <span class="token keyword">select</span> <span class="token string">"Hello world"</span> |
Câu truy vấn chỉ đơn giản đưa ra kết quả là một chuỗi “Hello world”.
Câu truy vấn phức tạp hơn
1 2 3 4 | <span class="token keyword">from</span> <span class="token comment">/* ... variable declarations ... */</span> <span class="token keyword">where</span> <span class="token comment">/* ... logical formulas ... */</span> <span class="token keyword">select</span> <span class="token comment">/* ... expressions ... */</span> |
Ví dụ, kết quả câu truy vấn là 42
1 2 3 4 | <span class="token keyword">from</span> <span class="token keyword">int</span> x<span class="token punctuation">,</span> <span class="token keyword">int</span> y <span class="token keyword">where</span> x <span class="token operator">=</span> <span class="token number">6</span> <span class="token operator">and</span> y <span class="token operator">=</span> <span class="token number">7</span> <span class="token keyword">select</span> x <span class="token operator">*</span> y |
Một số khái niệm cơ bản
Predicates
Predicate được sử dụng để mô tả các mối quan hệ logic tạo nên một chương trình QL. Đúng hơn, một predicate đánh giá một bộ dữ liệu. Ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | predicate isCountry<span class="token punctuation">(</span>string country<span class="token punctuation">)</span> { country <span class="token operator">=</span> <span class="token string">"Germany"</span> <span class="token operator">or</span> country <span class="token operator">=</span> <span class="token string">"Belgium"</span> <span class="token operator">or</span> country <span class="token operator">=</span> <span class="token string">"France"</span> } predicate hasCapital<span class="token punctuation">(</span>string country<span class="token punctuation">,</span> string capital<span class="token punctuation">)</span> { country <span class="token operator">=</span> <span class="token string">"Belgium"</span> <span class="token operator">and</span> capital <span class="token operator">=</span> <span class="token string">"Brussels"</span> <span class="token operator">or</span> country <span class="token operator">=</span> <span class="token string">"Germany"</span> <span class="token operator">and</span> capital <span class="token operator">=</span> <span class="token string">"Berlin"</span> <span class="token operator">or</span> country <span class="token operator">=</span> <span class="token string">"France"</span> <span class="token operator">and</span> capital <span class="token operator">=</span> <span class="token string">"Paris"</span> } |
Predicate isCountry
có 1 tuple {("Belgium"),("Germany"),("France")}
, hasCapital
có 2 tuple {("Belgium","Brussels"),("Germany","Berlin"),("France","Paris")}
Định nghĩa một predicate
khi định nghĩ một predicate, cần phải chỉ định các:
- Từ khóa
predicate
(nếu không có dữ liệu trả về), hoặc kiểu của dữ liệu trả về. - Tên của predicate. Định danh bắt đầu bằng chữ thường.
- Các tham số của predicate, nếu có nhiều thì phân cách nhau bởi dấu phẩy. Với mỗi tham số đầu vào cần phải chỉ định kiểu dữ liệu.
- Nội dung của predicate.
Predicate không có dữ liệu trả về
1 2 3 4 | predicate isSmall<span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">)</span> { i <span class="token operator">in</span> <span class="token punctuation">[</span><span class="token number">1</span> <span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token number">9</span><span class="token punctuation">]</span> } |
Predicate có dữ liệu trả về
1 2 3 4 5 | <span class="token keyword">int</span> getSuccessor<span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">)</span> { result <span class="token operator">=</span> i <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">and</span> i <span class="token operator">in</span> <span class="token punctuation">[</span><span class="token number">1</span> <span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token number">9</span><span class="token punctuation">]</span> } |
Source
Trong quá trình phân tích luồng dữ liệu, source
được hiểu là nơi bắt đầu của luồng dữ liệu.
Sink
sink
được coi là điểm kết thúc của dòng chảy dữ liệu.
Flow
Luồng dữ liệu mô hình hóa cách dữ liệu chảy qua chương trình lúc chạy. Trong khi đó abstrct syntax tree phản ánh cấu trúc của chương trình.
Cài đặt môi trường
Để thực hành truy vấn mã CodeQL có 2 cách: sử dụng nền tảng lgtm console hoặc chạy trên local
Truy vấn trên lgtm console
Trước khi viết mã CodeQL ta chọn ngôn ngữ và project
Cuối cùng, viết câu truy vấn và nhấn run để thực hiện câu truy vấn.và cho ra kết quả.
Truy vấn trên local
Để truy vấn trên local ta cần phải cài đặt công cụ cần thiết.
Cài đặt công cụ
Đầu tiên tải file codeql-cli và giải nén ra. Tiếp theo cài đặt extension codeql cho vscode.
Sau khi cài xong extension codeql cho vscode, để có thể thực hiện các lệnh codeql ta cần phải cài codeQL-cli. Cài codeql-cli bằng cách thêm đường dẫn file thực thi codeql vào phần User setting, với linux dùng file codeql, windows sử dụng file codeql.exe.
Cuối cùng thêm thư viện QL vào workspace của vscode để ta có thể bắt đầu viết câu truy vấn.
Viết truy vấn
Sau khi đã cài đầy đủ các thứ cần thiết ta đến bước cuối cùng là viết câu truy vấn. Để viết câu truy vấn ta cần có database ( cũng như SQL, muốn truy vấn có kết quả thì cần phải có database để câu truy vấn hiển thị kết qua cho ta thấy).
Tạo database
Khi tạo database để truy vấn, codeql sẽ phân tích source code và tạo 1 bản snapshot trên source code. Để tạo database ta sử dụng câu lệnh sau.
1 2 | codeql database create databases/<database-name> -s projects/<source-code> -l javascript |
codeql
: đây là file thực thi nằm trong codeql-cli đã tải ở trên.databases/<database-name>
: đường dẫn đến nơi lưu trữ database-s
: đường dẫn đến source code muốn tạo database-l
: ngôn ngữ muốn tạo database
Viết truy vấn
Khi viết truy vấn ta cần đặt câu truy vấn vào nơi thích hợp. Trong trường hợp viết truy vấn cho source code javascript, ta cần đặt file mã truy vấn vào đường dẫn: ql/javascript/ql/src
Để hình dung rõ hơn, ta sử dụng một ví dụ đơn giảm tìm lỗi XSS trên source code javascript.
1 2 3 | <span class="token keyword">var</span> param <span class="token operator">=</span> location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">"#"</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> document<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token string">"Hello "</span> <span class="token operator">+</span> param <span class="token operator">+</span> <span class="token string">"!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Truy vấn tìm document.write
1 2 3 4 5 6 7 | <span class="token keyword">import</span> javascript <span class="token keyword">from</span> Expr dollarArg<span class="token punctuation">,</span>CallExpr dollarCall <span class="token keyword">where</span> dollarCall<span class="token punctuation">.</span>getCalleeName<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"write"</span> <span class="token operator">and</span> dollarCall<span class="token punctuation">.</span>getReceiver<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>toString<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"document"</span> <span class="token operator">and</span> dollarArg <span class="token operator">=</span> dollarCall<span class="token punctuation">.</span>getArgument<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">select</span> dollarArg |
Chạy truy vấn được kết quả như sau
Truy vấn location.hash.split
1 2 3 4 5 6 | <span class="token keyword">import</span> javascript <span class="token keyword">from</span> CallExpr dollarCall <span class="token keyword">where</span> dollarCall<span class="token punctuation">.</span>getCalleeName<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"split"</span> <span class="token operator">and</span> dollarCall<span class="token punctuation">.</span>getReceiver<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>toString<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"location.hash"</span> <span class="token keyword">select</span> dollarCall |
Phân tích luồng dữ liệu
Sau khi tìm được source
và sink
của lỗi xss. Ta tiến hành kết hợp chúng lại để tìm những đoạn code có dòng dữ liệu đi từ source
đến sink
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class XSSTracker extends TaintTracking::Configuration { XSSTracker<span class="token punctuation">(</span><span class="token punctuation">)</span> { <span class="token comment">// unique identifier for this configuration</span> this <span class="token operator">=</span> <span class="token string">"XSSTracker"</span> } override predicate isSource<span class="token punctuation">(</span>DataFlow::Node nd<span class="token punctuation">)</span> { <span class="token keyword">exists</span><span class="token punctuation">(</span>CallExpr dollarCall <span class="token operator">|</span> nd<span class="token punctuation">.</span>asExpr<span class="token punctuation">(</span><span class="token punctuation">)</span> instanceof CallExpr <span class="token operator">and</span> dollarCall<span class="token punctuation">.</span>getCalleeName<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"split"</span> <span class="token operator">and</span> dollarCall<span class="token punctuation">.</span>getReceiver<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>toString<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"location.hash"</span> <span class="token operator">and</span> nd<span class="token punctuation">.</span>asExpr<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> dollarCall <span class="token punctuation">)</span> } override predicate isSink<span class="token punctuation">(</span>DataFlow::Node nd<span class="token punctuation">)</span> { <span class="token keyword">exists</span><span class="token punctuation">(</span>CallExpr dollarCall <span class="token operator">|</span> dollarCall<span class="token punctuation">.</span>getCalleeName<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"write"</span> <span class="token operator">and</span> dollarCall<span class="token punctuation">.</span>getReceiver<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>toString<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"document"</span> <span class="token operator">and</span> nd<span class="token punctuation">.</span>asExpr<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> dollarCall<span class="token punctuation">.</span>getArgument<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> } } <span class="token keyword">from</span> XSSTracker pt<span class="token punctuation">,</span> DataFlow::Node source<span class="token punctuation">,</span> DataFlow::Node sink <span class="token keyword">where</span> pt<span class="token punctuation">.</span>hasFlow<span class="token punctuation">(</span>source<span class="token punctuation">,</span> sink<span class="token punctuation">)</span> <span class="token keyword">select</span> source<span class="token punctuation">,</span>sink |
Bonus: Luồng dữ liệu có thể nhìn bằng mắt
Để sử dụng tính năng này sẽ cần phải thay thế một số hàm sử dụng. Nhưng ý tưởng tìm lỗi vẫn như vậy, vẫn phải tìm source
và sink
. Sau khi code chạy xong thì ta có thể tìm bằng mắt xem dữ liệu của ta đi qua những chỗ nào.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <span class="token comment">/** * @name XSS * @kind path-problem * @id js/test */</span> <span class="token keyword">import</span> javascript <span class="token keyword">import</span> DataFlow::PathGraph class XSSTracker extends TaintTracking::Configuration { XSSTracker<span class="token punctuation">(</span><span class="token punctuation">)</span> { <span class="token comment">// unique identifier for this configuration</span> this <span class="token operator">=</span> <span class="token string">"XSSTracker"</span> } override predicate isSource<span class="token punctuation">(</span>DataFlow::Node nd<span class="token punctuation">)</span> { <span class="token keyword">exists</span><span class="token punctuation">(</span>CallExpr dollarCall <span class="token operator">|</span> nd<span class="token punctuation">.</span>asExpr<span class="token punctuation">(</span><span class="token punctuation">)</span> instanceof CallExpr <span class="token operator">and</span> dollarCall<span class="token punctuation">.</span>getCalleeName<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"split"</span> <span class="token operator">and</span> dollarCall<span class="token punctuation">.</span>getReceiver<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>toString<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"location.hash"</span> <span class="token operator">and</span> nd<span class="token punctuation">.</span>asExpr<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> dollarCall <span class="token punctuation">)</span> } override predicate isSink<span class="token punctuation">(</span>DataFlow::Node nd<span class="token punctuation">)</span> { <span class="token keyword">exists</span><span class="token punctuation">(</span>CallExpr dollarCall <span class="token operator">|</span> dollarCall<span class="token punctuation">.</span>getCalleeName<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"write"</span> <span class="token operator">and</span> dollarCall<span class="token punctuation">.</span>getReceiver<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>toString<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token string">"document"</span> <span class="token operator">and</span> nd<span class="token punctuation">.</span>asExpr<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span> dollarCall<span class="token punctuation">.</span>getArgument<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> } } <span class="token keyword">from</span> XSSTracker pt<span class="token punctuation">,</span> DataFlow::PathNode source<span class="token punctuation">,</span> DataFlow::PathNode sink <span class="token keyword">where</span> pt<span class="token punctuation">.</span>hasFlowPath<span class="token punctuation">(</span>source<span class="token punctuation">,</span> sink<span class="token punctuation">)</span> <span class="token keyword">select</span> sink<span class="token punctuation">.</span>getNode<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> source<span class="token punctuation">,</span> sink<span class="token punctuation">,</span> <span class="token string">"xss"</span> |