Java Servlet Command Injection Vulnerability Challenges
Java Servlet Command Injection Vulnerability Challenges
Cấu trúc Project
1 | +---.idea |
- LabServlet.java:Xử lý HTTP request
với response thực hiện các tác vụ trên server và trả về kết quả cho
người dùng. - LabService.java: Nơi đây là nơi xử lý
logic chính của cả Web Application là nơi xử lý các level khác nhau. - Shell.java: Có nhiệm vụ thực thi các
lệnh shell hoặc command-line từ chương trình Java và trả về kết quả
của lệnh đó dưới dạng chuỗi.
Source Code:
Tiến hành Exploit và POC từng level
Level 1
1 | case 1: |
Đoạn code trên sẽ gọi qua Shell.java để xử lý OScommand
1 | package ci.util; |
Đây là đoạn code Shell.java khi nó được gọi nó sẽ cho ta xử lý các
OScommand ở đây tôi làm cả 2 OS là Windows và Linux. Ở level đầu thì
cũng dễ để có thể khai thác vì ta có thể thấy rõ rằng cái sink nó nằm
ngay ở đoạn nó cho phép thực thi nslookup nhưng không hề chặn đi những
dấu giúp nối dài câu lệnh để thay đổi hành vi của nó.
Ở đây ta test thử nhập google.com để xem nó có thực thi không và có thể thấy câu lệnh
nslookup có thực thi bây giờ ta sẽ thử nối dài nó và thực hiện chạy câu
lệnh dir để xem nó sẽ trả về gì.

Vậy là với payloadgoogle.com ; ls đã thực thi thành công nó trả về kết quả của cả câu
lệnh nslookup ở google.com và shell nó còn thực thi luôn cả câu lệnhdir và dấu ; là nhân tố nối dài câu lệnh giúp ta inject được thêm
những câu lệnh ngoài vào.
Level 2
Level 2
1 | case 2: |
Đến với lv này thì có thể thấy rõ ràng là dấu ; đã bị filter vì thế
payload cũ sẽ không còn hoạt động ở level này.

Vậy thì liệu ngoài; ra thì powershell còn hỗ trợ kí tự nào có thể giúp ta nối dài câu
lệnh, sau một lúc tìm hiểu tôi chọn | hay còn gọi là pipeline để nối
dài câu lệnh thử xem liệu nó có được hay không.
Thành công với câu
inject ls.
Level 3
1 | case 3: |
Đến với level 3 có thể thấy rõ rằng 3 dấu ; & | đã bị block vậy bây
giờ ta phải tìm cách khác để nối dài câu lệnh ra. Sau một lúc tìm hiểu
ta có thể lợi dụng url encode cùng với bảng hex để xuống dòng ở đây mình
dùng %0A .
%0A là gì?
Trong URL encoding:
- Mỗi ký tự đặc biệt được mã hoá dưới dạng % + mã hex của nó theo bảng ASCII.
- 0A trong hệ hex chính là số thập phân 10, tức là ký tự Line Feed (LF) — hay xuống dòng \n.
Thành công thực thi
được câu lệnh ls.
Level 4
1 | private String winBackupStatus(String archiveName) throws Exception { |

Đây là nơi sẽ giúp
ta backup file zip nếu thành công nó trả về OK còn nếu không thì nó sẽ
trigger ERROR. Và ta có thể thấy rõ rằng là ở đây cmd đã rơi vàoShell.run hay là Untrusted Data đã rơi vào Unsafe Method ta có thể
thấy rằng đây là một sink có thể khai thác được, việc bây giờ ta sẽ test
thử liệu shell có hoạt động hay không bằng lệnh sleep

Có thể thấy nó báo
lỗi nhưng câu lệnh sleep đã được thực thi thành công vì ở thời gian
response đã là hơn 5 giây. Vậy bây giờ ta sẽ tìm cách để đưa được
response ra được bên ngoài để đọc được nó ở đây mình dùng webhook cùng
với Invoke-WebRequest vì mình sử dụng powershell chứ không phải linux.

Kết quả curl nhảy
liên tục vì nó in ra từng dòng ở trong câu lệnh ls. Ở đây là mô phỏng
với trường hợp chỉ trả về kết quả OK hoặc ERROR và mình phải test trong
môi trường blind còn với chall này thì những payload như ;ls vẫn sẽ
nhảy ra kết quả vì ở đây mình để nó in ra để debug.

Level 5
1 | private String winBackupBoolean(String archiveName) throws Exception { |
Ở đây vì là whitebox nên ta có thể thấy được đường dẫn bên trong nên ở
đây có 2 case có khả thi để có thể khai thác command injection. - Với
trường hợp đầu tiên là sử dụng bruteforce theo kiểu binary search để tìm
kí tự. Ở đây tôi sử dụng payload làheieiehehe; if ([int][char](whoami)[0] -gt 109) { Start-Sleep -Seconds 5 }
đoạn đầu tôi sẽ tiến hành backup file có tên heieiehehe sau đó tiến
hành sử dụng điều kiện if kiểm tra giá trị đầu tiên của mảng sau khi
câu lệnh whoami được thực thi nếu nó lớn hơn ascii = 109 là chữ m
thì nó sẽ sleep 5 giây.
Kết quả cho ra nó
hoàn toàn có sleep trên 5 giây vậy từ cách này ta hoàn toàn có thể brute
force ra được kết quả từng câu lệnh mình inject vào.
Còn với trường hợp thứ 2 thì giả thiết ở đây liệu ta có thể ghi một
file vào document root và cho nó thực thi được không. Tiến hành
inject payloadtududu; echo "pwned!" > D:\Web\apache-tomcat-10.1.43-windows-x64\apache-tomcat-10.1.43\webapps\ROOT\pwned.txt
để đưa filepwned.txtvào document root.
Có thể thấy file
được lưu vào Document Root.
Sau khi truy cập
thấy có hiển thị vậy bây giờ ta sẽ thử chạy lệnhwhoamirồi đẩy
thử kết quả ra file txt.
Payload :
duddddmmy;+whoami+>+D%3a\Web\apache-tomcat-10.1.43-windows-x64\apache-tomcat-10.1.43\webapps\ROOT\whoami.txt
Thành công.
Level 6
1 | private String winBackupNoStdout(String archiveName) throws Exception { |
Với trường hợp read only ta sẽ không thể ghi file ra ngoài nhưng ở lv 5
ta đã tiếp cận với 1 hướng đi đó là Boolean Base ta sẽ thử áp dụng vào
trường hợp này. Ở đây ta sẽ lợi dụng chuỗi tín hiệu booleankey để thực
hiện in ra kết quả Fail hoặc Success tuỳ vào trường hợp.
Với đoạn payload đầu
tiên là x.zip; if((whoami)[0] -eq 'a'){ 'BooleanKey' } ; ở đây nó sẽ
thực hiện so sánh vị trí thử 0 của kết quả câu lệnh whoami nếu nó là a
thì nó sẽ trả về fail và ngược lại nếu điều kiện sai kết quả sẽ trả về
success.
Còn với
trường hợp vị trí 0 bằng b thì kết quả đã khác là nó đã trả về success
vì vậy điều kiện trên là false. Ở đây những payload trên hoạt động kiểu
vậy nhờ BooleanKey cái BooleanKey được mặc định nếu nằm trong câu
lệnh sẽ trả về Fail vậy nên ta lợi dụng nó để khi mà ta so sánh chuỗi
hoặc kí tự mà nó có tồn tại thì mình sẽ in cái BooleanKey ra và từ đó
nó sẽ trả về Fail có nghĩa là điều kiện đúng. Và ngược lại nếu trong câu
if true thì nó sẽ không in ra Fail vì cái booleankey sẽ nằm ở bên else.
Level 7
Đến với level 7 thì cách hoạt động sẽ vẫn là backup file nhưng ở đây nó
sẽ khác đi là nó sẽ không còn trả về Fail hay Success mà chỉ trả vềĐã chạy tác vụ nên có thể thấy đây là trường hợp output silence.
1 | public void runLevel7Silent(String input) throws Exception { |
Test thử payload cũ
thì nó chỉ hiển thị cho ta mỗi dòng này vậy nên bây giờ boolean base đã
bị vô tác dụng trước dạng output như này. Sau một lúc test thử thì ta
hoàn toàn có thể lợi dụng câu lệnh sleep để thực hiện time base nếu
điều kiện true sẽ sleep theo ý thích của mình nếu không thì response trả
về nhanh. Tiến hành test thử payloadx.zip; Start-Sleep -Seconds 10 ; # để xem nó có thực sự sleep không.
Có thể thấy response
là 11 giây vậy là lệnh sleep có hiệu quả việc bây giờ là ta sẽ thử thêm
điều kiện vào. Bây giờ ta sử dụng payload giống lv6 nhưng chỉ sửa phần
boolean thành timex.zip; if((whoami)[0] -eq 'a'){ Start-Sleep -Seconds 10 } ; #
Response time trên
10s chứng tỏ chữ đầu tiên của kết quả câu lệnh whoami là a từ đây ta
hoàn toàn có thể viết script để chạy để in ra full kết quả.
Test thử với kí tự
thứ nhất bằng b thì response chỉ trong vòng 1 giây ta có thể kết luận
câu sleep 10 giây không thực thi nên là false.