Skip to content

CodeQL for beginner [part 1]

Chào mọi người, cũng phải gần 2 tháng kể từ blog cuối, dạo này hơi bận bịu với nghề tay trái nên cũng chưa có thời gian viết lách, chứ ko phải là bị dính phốt nên phải chạy trốn đâu ae, hoho …

Mấy nay bên Github SecurityLab (GHSL) có tổ chức CTF về CodeQL, mình cũng có hóng hớt và đợt này mới có CTF về CodeQL for Java. Trước đó cũng có các CTF cho C/C++, JS nhưng mình ko có nhiều khái niệm của mảng đó nên cũng ko đủ tự tin để tham gia. Giải CTF đợt này các bạn có thể xem tại: https://securitylab.github.com/ctf/codeql-and-chill (:v Netflix and chill …).

Giải này theo chia sẻ của tác giả thì cũng khá là basic, tổ chức cho người mới tìm hiểu về CodeQL, nếu bạn muốn tìm hiểu về CodeQL mà chưa có điểm khởi đầu thì đây là một cơ hội tốt để tham gia, ngày cuối của CTF là 12/06 cơ. Do giải vẫn đang diễn ra nên blog này sẽ không nói quá nhiều liên quan tới challenge của giải.

Hiện tại thì mình đang làm đồ án tốt nghiệp với đề tài là nghiên cứu về CodeQL, và đang bắt đầu học CodeQL nên muốn viết vài dòng để note lại các vướng mắc, khó khăn cũng như cách giải quyết để cho những bạn nào bắt đầu có thể dễ dàng hơn với CodeQL. Hiểu biết về QL của mình hiện tại chưa thể nói là sâu được, nhưng cũng sẽ giúp nhiều bạn tìm được điểm khởi đầu dễ dàng hơn!

Trước đó mình cũng có đọc qua một blog tiếng Việt về CodeQL, mình cứ tưởng là có người tâm huyết rồi truyền lại sang tiếng Việt, nào ngờ check lại thì đó chỉ là blog post của 1 người tàu được translate (https://paper.seebug.org/1079/).

Hồi mới bắt đầu làm quen với Semmle (sau này là CodeQL), mình gặp khá nhiều khó khăn để bắt đầu do hầu như không có tài liệu gì public về cái Semmle này. Tất cả chỉ dựa vào official document tại help.semmle.com, hiện tại thì cũng đã khá khẩm hơn sau khi được github mua lại. Hy vọng sẽ có nhiều người hơn join cộng đồng để tài nguyên ngày càng phong phú!

#Note trước khi vào bài:

  • Nên có chút hiểu biết về Java
  • Không nên cứng ngắc áp khái niệm của ngôn ngữ lập trình khác vào CodeQL, vì bản chất nó là 1 ngôn ngữ riêng, có khá nhiều điểm trông thì giống ngôn ngữ lập trình nhưng thực tế nó không hoạt động như vậy.

CodeQL setup

Hiện tại thì việc setup khá là đơn giản khi CodeQL chính thức được đưa lên VSCode, chỉ cần follow guide tại đây là có thể setup được: https://help.semmle.com/codeql/codeql-for-vscode/procedures/setting-up.html

Guide này cũng bao gồm cả đoạn mở workspace, db …

Có một lưu ý là CodeQL được update khá thường xuyên nên thi thoảng nó lại báo update. Update trong CodeQL thường có rất nhiều ý nghĩa nên mình cũng khuyên là nên update!

Và khi mở db trong vscode thì cũng cần lưu ý version của db phải ≤ version của codeql extension. Nếu như version của db > version của codeql extension thì sẽ xảy ra lỗi như sau:

#ảnh mượn của người anh xã hội @ datlp

Remote Dev

Đây là 1 chức năng của VSCode mà mình rất thích.

Khi query thì codeql sẽ ngốn khá nhiều tài nguyên và làm nóng máy, Remote Dev là giải pháp cho vấn đề này.

Bạn hoàn toàn có thể cài codeql lên máy remote và chạy query trên đó, nhưng lại có được response về máy mình.

Chi tiết follow guide tại: https://code.visualstudio.com/docs/remote/remote-overview

*Trong bài này, mình sử dụng db liferay để làm ví dụ cho các query, các bạn có thể download tại đây: https://drive.google.com/file/d/1_qR2Az8a-gKhTIjuyL4eDsE1BCnmn_tw/view?usp=sharing

Basic Usage

Sau khi setup các thứ các thứ xong xuôi thì sẽ có được giao diện như sau:

Với folder codeql-custom-queries-XXX là folder sẽ chứa file query cho ngôn ngữ XXX.

Tất cả các file đặt trong folder này mới có thể sử dụng library và query bình thường được!

Ví dụ như mình có file test11.ql trong folder codeql-custom-queries-java/liferaytest/

Cấu trúc cơ bản của 1 file QL trong guide của mình có dạng như sau:

Trong đó với các annotation trong comment là @name, @kind đều có ý nghĩa cả. Xóa bỏ hoặc thay đổi các annotation này đều làm cho cả query thay đổi!

Ví dụ như mình đang sử dụng @kind = path-problem, để tìm lỗi và mong muốn output của CodeQL sẽ như sau:

Nếu không có @kind = path-problem thì output sẽ có dạng:

Sau các dòng define annotation này là tới phần import library, hiện tại mình đang query trên ngôn ngữ Java thì mình sẽ dùng “import java”. Các library này được đặt tại folder ql/java/ql/src/ với dạng đuôi .qll.

Sau khi import lib java thì sẽ tới lib “semmle.code.java.dataflow.FlowSources”. Lib này cung cấp các định nghĩa có sẵn về các nơi mà input có thể sẽ được nhận vào, ví dụ như trong HttpServletRequest thường có method “getParameter()” để lấy param từ input.

Lib DataFlow::PathGraph phía dưới là lib đi kèm với @kind = path-problem.

Và phía dưới cùng là select clause, Select clause này có dạng như sau:

from <Variable>where <formula>select <expression>

Trong đó thì với phần “from” và “where” có thể có hoặc không đều được.

Trông nó hơi giống với 1 câu query trong SQL bình thường:

select _X from _Y where _Z

Quay lại với query:

Với select clause của file query này là:

from Method mwhere m.hasName("deserialize")select m

Có thể hiểu một cách tóm lược ý nghĩa của mệnh đề select này như sau:

from Method m <= Từ tập hợp các method của bộ mã nguồn hiện tại
where m.hasName("deserialize") <= Những cái nào có tên như vậy
select m <= Thì chọn lấy nó

Nó cũng hơi giống với khi query trong SQL:

select m from Method where m.name = "deserialize"

Chạy y nguyên file query của mình và sẽ nhận được kết quả như sau:

Kết quả trả về là 0 results, nhưng đây là tab result của kiểu “path-problem”, hiện tại mới đang ở query test nên cần chuyển sang tab “#select” để hiện kết quả:

Và kết quả trả về là 68 method có tên “deserialize” trong bộ mã nguồn.

Có thể sửa mệnh đề select 1 chút để nó show ra cả tên Class chứa Method đó:

Method.getDeclaringType() sẽ trả về class owner của method đó (trong document của CodeQL thì nó có định nghĩa hơi khác 1 chút, nhưng để dễ gần hơn với người mới tìm hiểu thì mình sẽ tạm thời nói như vậy! Chi tiết hơn nằm tại đây https://help.semmle.com/qldoc/java/semmle/code/java/Member.qll/predicate.Member$Member$getDeclaringType.0.html).

Khi chạy lại query 1 lần nữa ta được kết quả như sau:

Kết quả trả về có bao gồm nhiều method tên deserialize, nhưng không phải là class mình đang cần tìm. Hiện tại mình đang muốn focus vào JSONFactoryUtil.deserialize(), như trong bài phân tích của mình (https://www.facebook.com/notes/nguy%E1%BB%85n-ti%E1%BA%BFn-giang/liferay-story-part-4-v%C3%A0-nh%E1%BB%AFng-c%C3%BA-l%E1%BB%ABa-/2408844002562884/) thì đây là nơi xảy ra bug JSON deserialize to RCE.

Để giới hạn lại chỉ còn “JSONFactoryUtil”, ta sử dụng query:

Ta đã biết ở trong query trước, “Method.getDeclaringType()” sẽ trả về class owner của method đó, do vậy Method.getDeclaringType().hasName(“ABC”) sẽ giới hạn lại class owner chỉ có tên “ABC”.

Kết quả sau đó chỉ còn 2 kết quả là 2 method của class JSONFactoryUtil:

Mặc dù trong phần kết quả hiện ra 2 method giống y chang nhau, nhưng thực tế đây là 2 method khác nhau:

Đối số của 2 method này không giống nhau, 1 cái là JSONObject, 1 là String.

Trong bug của liferay, method gây ra lỗi là method “JSONFactoryUtil.deserialize(String)”. Do đó cần thêm 1 lần filter nữa để chỉ lấy mỗi method này.

Query mình sử dụng để limit lần này là:

Với “Method.getParameterType(index)” sẽ trả về kiểu của parameter trong method hiện tại. Sử dụng thêm hasName(“String”) để lấy các parameter có type là “String”.

Sau khi modify query thì cuối cùng sẽ chỉ còn 1 kết quả là method deserialize(String) cần tìm:

#Class

Mặc dù câu query đã đạt được mục đích là tìm Method deserialize(String) nhưng nó vẫn khá cồng kềnh, để gọn gàng hơn thì có thể define thêm 1 class cho việc này:

Cú pháp để declare class có dạng như sau:

class NameFoo extends <type>{
  NameFoo(){
  ...
  }  predicate doSmt(){}
}

Lưu ý là 1 class cần được 1 extends ít nhất 1 kiểu nào đó và tên của class phải bắt đầu bằng chữ in hoa, bên trong class này sẽ có các thứ giống như 1 method của java, nhưng ở đây nó được gọi là predicate.

Mỗi class sẽ có 1 predicate đặc trưng, trùng với tên của class đó (hao hao giống constructor của java), predicate này có sử dụng biến “this”, biến “this” này để xác định chính class đó, nói thì hơi lằng nhằng nên quay trở lại ví dụ của mình cho dễ hiểu.

Class của mình define bên trên có tên là “JsonDeserializeMethod”, thừa kế kiểu Method. Bên trong class này có 1 predicate đặc trưng, trùng với tên của nó, predicate đặc trưng này sẽ xác định class này khác với class như thế nào. Body của predicate này:

Trong đó sử dụng formula exists(), hiểu 1 cách đơn giản, formula này sẽ kiểm tra xem có tồn tại thứ gì thỏa mãn điều kiện được đưa vào hay không.

Như trong trường hợp hiện tại, điều kiện cần thỏa mãn ở đây chính là phần điều kiện của select clause hồi nãy:

Chỉ khác 1 điều khác là trong phần body của predicate đặc trưng, sau khi xác định được các điều kiện thỏa mãn, nhất định phải xác định được biến “this” này, như trong trường hợp của mình là “this = m”.

#MethodAccess

Hiện tại đã có thể xác định được chính xác Method cần tìm, để tìm được các Method nào gọi đến Method đó thì QL có cung cấp 1 kiểu đó là MethodAccess.

MethodAccess nắm giữ các call method, ví dụ như method Foo.getBar() -> Foo2.setFoo(xyz).

  • MethodAccess.getMethod() (hoặc getCallee()): trả về method được call
  • MethodAccess.getCaller(): trả về method chủ động call.

Quay trở lại với query mình đang viết dở, khi áp dụng thêm MethodAccess vào sẽ có dạng như sau:

Trong đó:

  • JsonDeserializeMethod chính là method deserialize(String) đã define trước đó, và có kiểu là Method
  • ma.getMethod() = jsm: tìm ra tất cả các MethodAccess nào có method được gọi là JsonDeserializeMethod

Kết quả sau khi query sẽ ra được các method có call tới JsonDeserializeMethod:

#Lưu ý rất lớn: công thức với dấu “=” trong QL không hề giống với các ngôn ngữ lập trình khác. NÓ KHÔNG CÓ Ý NGHĨA LÀ PHÉP GÁN BIẾN.

Trong các ngôn ngữ lập trình thì 1 biến sẽ biểu diễn cho địa chỉ vùng nhớ nào đó có dữ liệu, và có thể thay đổi theo thời gian.

Ví dụ với cùng 1 công thức như sau:

n = n + 1
  • Trong Java hoặc các ngôn ngữ lập trình khác, nó có ý nghĩa: bằng giá trị của n hiện tại cộng thêm 1
  • Trong QL, đây là 1 công thức 2 vế, chỉ đúng khi tồn tại 1 giá trị nào đó thỏa mãn n bằng với n + 1

Do đó công thức phía trên không bao giờ thỏa mãn, vì không tồn tại bất cứ nào thỏa mãn “n = n + 1“.

Trên đây là 1 số khái niệm cơ bản đề bắt đầu với CodeQL, nếu bạn có hứng thú với nó thì nên thực hành luôn để hiểu rõ vấn đề hơn, chỉ đọc không thì kiến thức vẫn là của tác giả, khó mà đọng lại gì trong đầu!

Rất mong được sự góp ý của ae bạn bè để giúp cho series được chất lượng hơn!

Cảm ơn các bạn đã theo dõi tới đây, hẹn gặp lại trong phần 2 của series này.

Ref:

__Jang of VNPT__

14 lượt xem