X Ét Ét
Last updated
Last updated
Bài này cho ta một ứng dụng có sử dụng electron và với tên chall thì biết ngay hướng của bài là XSS => RCE thông qua electron
Chall có cấu trúc thư mục như sau:
Với thư mục app
là code của electron, code web chính nằm ở server
còn bot
thì đơn giản là con bot sẽ gọi đến electron
Tóm tắt qua workflow của ứng dụng như sau
Trang web có tính năng login, signup
Sau khi đăng nhập thì có thể tạo ticket với các data sau
Khi tạo ticket ta sẽ được như sau
Khi report admin thì code server sẽ santitize title và content để gửi cho bot
Tại code xử lý của con bot nó sẽ base64 encode id, title và content lưu vào env, và sau đó gọi đến ứng dụng electron
Tại code của electron nó sẽ decode env và load giao diện
Ở trên là cách trang web hoạt động, mình bắt đầu nhảy vào phân tích src
Đầu tiên, trang ticket dùng jinja2 template để load nội dung 1 cách an toàn nên không thể XSS => loại
Tại trang admin.html
sẽ load data với options safe
do đó trang này có thể bị XSS
Ta có thể load admin.html
thông qua route /IsNew
. Tuy nhiên để đến được trang này thì phải là request internal, cộng thêm vào đó trang được trang bị CSP
Giá trị CSP của trang
Với script-src 'self'
thì ta có thể bypass bằng upload file và trang web cũng có tính năng đó
Để đi đến được admin.html
ta sẽ có 2 hướng
Thông qua iframe, vì code load iframe chỉ check phần content có tồn tại "http://localhost/tmp/" hay không, do đó mình có thể bypass bằng "http://localhost/tmp/../IsNew?id=<id>/#" và khiến iframe load một ticket bất kỳ. Tuy nhiên XSS được thì không thể RCE được, lý do vì sao thì một lát các bạn sẽ biết
Hướng thứ 2 là khiến renderer kích hoạt envent CreateViewer => createNotificationWindow(id) => child.loadURL("http://localhost/IsNew?id="+id) => admin.html => XSS
, để làm được điều đó mình phải bypass được check admin
Để bypass check admin thì đơn giản mình chỉ cần tạo user admin có thêm khoảng trắng phía sau, vì khi trang web lưu session sẽ strip username
Điều này khiến username admin
hay admin
đều có session['username']
là admin
Bypass được admin mình có thể trigger được CreateViewer
,lúc này mình sẽ XSS thông qua title, vì trong electron ta có thể load được internal resource, nên mình sẽ lợi dụng tag meta
để load file và trigger XSS.
Để kiểm chứng thì mình sẽ tạo một file html tại máy (giả lập như file upload trên server), và dựng debug ở vscode để test
Tạo ticket chứa payload gọi đến file html của mình
Debug gọi đến electron app, thì mình đã XSS thành công
Bây giờ đã có thể XSS, mình tìm cách để RCE
Mình tham khảo về cách XSS 2 RCE ở bài blog này
Để ý khi window popup thì sẽ set các options sau
Với option nodeIntegrationInSubFrames: false
thì ta không thể call ipc từ iframe do đó attack vector XSS từ iframe lúc nảy mình đề cập không thể nào lợi dụng để leo thành RCE được
Tuy nhiên option contextIsolation
cũng set là false, do đó main process và processor process có thể dùng chung global content, tuy nhiên không hề có preload nào được load trong event này 😭😭😭
Mình khá là bí đoạn này, sau một hồi search gg về các options được set có thể lợi dụng được gì không. Thì mình tìm được bài này: https://powerofcommunity.net/poc2022/HectorPeralta.pdf
Các bạn tập trung vào slide 49-52. Khi options contextIsolation
và sandbox
là false ta có thể lợi dụng để can thiệp vào quá trình khởi tạo môi trường nodeJS của renderer process
Ta sẽ lợi dụng cơ chế __webpack_require__
để load bất kỳ module nào mà ta mong muốn
Các bạn có thể tìm hiểu thêm về cơ chế load module webpack thông qua bài blog này : https://www.freecodecamp.org/chinese/news/webpack-module-loading/
Nói đơn giản thì webpack là cơ chế giúp load các module của commonJS, mà Nodejs mặc định sẽ sử dụng commonJS để làm module system. Do đó ý tưởng sẽ là ghi đè lại function call của webpack_require trong quá trình module loading, bằng cách check arguments thứ 4 có phải là __webpack_require__
không, sau đó lấy instance của __webpack_require__
và load module child_process để RCE
Ta có đoạn script sau để RCE
Tóm lại để exploit ta sẽ:
Đăng ký user với tên là admin
để bypass check admin
Tạo ticket1 để upload malicous html
Tạo ticket2, với title là <meta http-equiv="refresh" content="0; url=file:///tmp/<ticket1_uuid>.html">
Report ticket2
Ghi flag vào /app/server/static và truy cập để đọc flag