I. Đặt vấn đề
1. Giới thiệu
OS command injection vulnerabilities chỉ loại lỗ hổng cho phép kẻ tấn công "inject" và thực thi tùy ý các câu lệnh tương ứng với hệ điều hành (OS) đang vận hành của hệ thống. Bởi vậy kiểu tấn công này còn có một tên gọi khác là Shell injection. Đây là một trong những lỗ hổng nguy hiểm nhất, thường được đánh giá trong khoảng trong điểm CVSS. Vì khi kẻ tấn công thực thi các lệnh shell tại hệ thống sẽ có thể trực tiếp xâm nhập máy chủ, chuyển cuộc tấn công tới các hệ thống khác trong tổ chức.
2. Phân biệt Code injection và Command injection
Để hiểu rõ hơn về kiểu tấn công này, trong bài viết này tôi sẽ so sánh các đặc điểm giữa Code injection và Command injection.
Code injection | Command injection |
---|---|
Thuật ngữ chỉ chung các cuộc tấn công chèn code thực thi vào mục tiêu | Chỉ cụ thể việc thực thi các lệnh shell (OS) tương ứng với hệ điều hành tại hệ thống |
Payload inject có thể là bất kỳ ngôn ngữ nào như php, Ruby, Python, ... | Payload inject là các lệnh shell như id, whoami, ls, ... và có thể thay đổi với tùy hệ điều hành khác nhau |
Kẻ tấn công phần lớn có đặc quyền là root/admin do mã nguồn thường được thực thi với đặc quyền cao | Kẻ tấn công có đặc quyền của ứng dụng bị xâm nhập, ví dụ với ứng dụng web thường mang đặc quyền www-data |
3. Một số lệnh shell và ký tự đặc biệt thường dùng trong tấn công OS command injection
Do các hệ thống hiện tại thường có server hoạt động trên nền tảng hệ điều hành Ubuntu, nên tôi sẽ tập trung các câu lệnh thường sử dụng trong hệ điều hành này, với các hệ điều hành khác dành cho các bạn tự khám phá thêm.
Khi kiểm tra tấn công OS command injection thành công trên mục tiêu, chúng ta có thể khai thác hệ thống theo một số hướng sau: định danh người dùng hiện tại bằng lệnh whoami, liệt kê tên tệp, thư mục bằng ls, đọc file với các lệnh cat, tac, head, tail, ...
Phần "nghệ thuật" hơn cả là việc kết hợp các ký tự đặc biệt với các câu lệnh nhằm bypass filter từ hệ thống, thực thi câu lệnh với các mục đích khác nhau. Bảng sau tổng hợp các ký tự thường sử dụng cùng với công dụng của chúng:
Câu lệnh | Ý nghĩa |
---|---|
cmd1 | cmd2 | Kết quả của cmd1 trở thành tham số truyền vào của cmd2, dù cmd1 thực thi thành công hay thất bại đều sẽ thực thi cmd2 |
cmd1 || cmd2 | cmd1 thực thi thất bại thì cmd2 mới thực thi |
cmd1 ; cmd2 | cmd1 thực thi thành công hay thất bại đều sẽ thực thi cmd2 |
cmd1 & cmd2 | cmd1 thực thi tại background, cmd1 và cmd2 đồng thời thực thi |
cmd1 && cmd2 | cmd1 thực thi thành công thì cmd2 mới thực thi |
( cmd1;cmd2; cmd3 ) | thực thi đồng thời cmd1, cmd2, cmd3 |
Ngoài ra còn nhiều các ký tự và câu lệnh hữu dụng khác, các bạn có thể tìm hiểu thêm và kết hợp khéo léo nhằm mang lại hiệu quả lớn trong quá trình xây dựng payload tấn công.
Các hàm thông dụng phục vụ cho mục đích đọc nội dung file:
highlight_file($filename);
show_source($filename);
print_r(php_strip_white($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen($filename, "r"), $size);
include($filename); // không phải file php
include_once($filename); // không phải file php
require($filename); // không phải file php
require_once($filename); // không phải file php
Các hàm thông dụng phục vụ cho mục đích duyệt thư mục:
print_r(glob("*")); // liệt kê các file trong thư mục hiện tại
print_r(glob("/*")); // liệt kê các file trong thư mục gốc
print_r(scandir("."));
print_r(scandir("/"));
var_export(scandir("/"));
var_dump(scandir("/"));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}
$a=glob("/*");foreach($a as $value){echo $value." "};
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
II. Phân tích và khai thác lỗ hổng OS command injection
1. Vì sao kẻ tấn công có thể khai thác lỗ hổng OS command injection
Khi một hệ thống sử dụng trực tiếp input từ người dùng (Cookie, HTTP Header, parameter, ...) đóng vai trò là tham số truyền vào thực thi trong lệnh shell, trong đó không có các cơ chế bảo vệ hoặc quá trình filter lỏng lẻo sẽ dẫn tới lỗ hổng Command injection. Khi đó hệ thống sẽ thực thi vô điều kiện các input nguy hiểm này!
2. Một số hàm có thể dẫn tới OS command injection
Mỗi ngôn ngữ lập trình đều có các hàm hỗ trợ thực thi lệnh shell, khi sử dụng các hàm này cần thận trọng với input từ người dùng, như tôi thường nói, kẻ tấn công cũng đóng vai trò là người dùng!
2.1. Ngôn ngữ PHP
- Hàm system()
Cú pháp: system(string $command, int &$result_code = null)
Thực thi và in ra kết quả lệnh $command
. Ví dụ:
if (isset($_GET['cmd'])) { $cmd = $_GET['cmd']; system($cmd);
}
- Hàm exec()
Cú pháp: exec(string $command, array &$output = null, int &$result_code = null)
Thực thi lệnh $command
. Nếu có biến $output
sẽ lưu kết quả vào $output
dưới dạng mảng. Ví dụ:
$output = null;
if (isset($_GET['cmd'])) { $cmd = $_GET['cmd']; exec($cmd, $output); var_dump($output);
}
- Hàm passthru()
Cú pháp: passthru(string $command, int &$result_code = null)
Thực thi và in ra kết quả lệnh $command
. Ví dụ:
if (isset($_GET['cmd'])) { $cmd = $_GET['cmd']; passthru($cmd);
}
- Hàm shell_exec()
Cú pháp: shell_exec(string $command)
Thực thi lệnh $command
. Ví dụ:
if (isset($_GET['cmd'])) { $cmd = $_GET['cmd']; $output = shell_exec($cmd); echo $output;
}
- Hàm popen()
Cú pháp: popen(string $command, string $mode)
Mở một "đường ống" dẫn đến chương trình được chỉ định trong biến $command
. Ví dụ:
if (isset($_GET['file'])) { $file = $_GET['file']; $content = popen($file, "r"); $read = fread($content, 2096); echo $read; pclose($content);
}
- Hàm proc_open()
Cú pháp: proc_open(array|string $command, array $descriptor_spec, array &$pipes, ?string $cwd = null, ?array $env_vars = null, ?array $options = null)
Công dụng giống với popen()
nhưng cung cấp mức độ kiểm soát lớn hơn trong việc thực thi chương trình.
- Cặp ký tự backtick: ` `
PHP sẽ thực thi nội dung của các tham số được đặt trong cặp ` ` dưới dạng lệnh shell.
if (isset($_GET['cmd'])) { $cmd = $_GET['cmd']; echo `$cmd`;
}
2.2. Ngôn ngữ Python
- Hàm system()
Cú pháp: system(command)
- Hàm popen()
Cú pháp: popen(cmd, mode='r', buffering=-1)
- Hàm subprocess.call()/subprocess.run()
Cú pháp: subprocess.call(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None, **other_popen_kwargs)
2.3. Ngôn ngữ Java
Cần chú ý java.lang.Runtime.getRuntime().exec(command)