Lỗ hổng này thường xảy ra khi lập trình dùng các lệnh để include file, chẳng hạn như đối với PHP thì là include, require, include_once, và require_once. Cần lưu ý là lỗ hổng này vẫn có thể xảy ra với những ngôn ngữ khác.

Để thực hiện tấn công LFI, ta sẽ sử dụng kỹ thuật Path Traversal.

First scenario

Giả sử trang web có hai ngôn ngữ là tiếng Anh và tiếng Việt.

Người dùng thay đổi ngôn ngữ bằng cách gửi request đến đường dẫn http://webapp.thm/index.php?lang=EN.php hoặc http://webapp.thm/index.php?lang=VI.php. Trong đó EN.phpVI.php là hai files cùng thư mục với index.php.

Đoạn code xử lý của server như sau:

// index.php
<?php
	include($_GET["lang"]);
?>

Có thể thấy, toàn bộ file được included vào index.php và được hiển thị ra cho người dùng. Ngoài ra, do không specify một directory cụ thể nên đường dẫn truyền vào hàm include có thể là bất cứ thứ gì 😱.

Khi đó, chúng ta có thể thay đổi tên file thành một đường dẫn tùy ý, chẳng hạn như /etc/passwd (là một file có chứa những thông tin nhạy cảm về các người dùng của hệ điều hành Linux).

Give Lab #1 a try to read /etc/passwd. What would the request URI be?

/lab1.php?file=/etc/passwd

Second scenario

Nếu ta thêm specify một thư mục ở phía trước:

// index.php
<?PHP 
	include("languages/". $_GET['lang']); 
?>

Mặc dù không thể truyền vào /etc/passwd như trước nhưng chúng ta vẫn có thể sử dụng path traversal để mò ra file /etc/passwd. Chẳng hạn như: http://webapp.thm/index.php?lang=../../../../etc/passwd.

In Lab #2, what is the directory specified in the include function?

includes

Third scenario

Xét trường hợp kiểm thử hộp đen, chúng ta không có mã nguồn. Khi đó, manh mối duy nhất để tấn công chính là các thông báo lỗi. Giả sử ta nhập vào một input sai, chẳng hạn THM, ta sẽ nhận được thông báo lỗi như sau:

Warning: include(languages/THM.php): failed to open stream: No such file or directory in /var/www/html/THM-4/index.php on line 12

Qua thông báo này, ta biết được rằng hàm include được gọi sẽ có dạng:

include(languages/THM.php)

Đồng thời, ta cũng biết được rằng code sẽ tự thêm vào đuôi .php phía sau input. Tức là, input nếu hợp lệ sẽ là EN hoặc VI thay vì EN.php hoặc VI.php.

Ngoài ra, thông báo này còn để lộ đường dẫn của toàn bộ ứng dụng web: /var/www/html/THM-4/ 🥶. Khi có thông tin này, ta sử dụng 4 lần ../ để truy ngược về thư mục gốc của hệ điều hành (bởi vì đường dẫn có 4 level). Sau đó truy cập vào đường dẫn /etc/passwd như các kịch bản ở trên:

http://webapp.thm/index.php?lang=../../../../etc/passwd

Tuy nhiên, đến lúc này, ta vẫn bị một lỗi như sau:

Warning: include(languages/../../../../etc/passwd.php): failed to open stream: No such file or directory in /var/www/html/THM-4/index.php on line 12

Lỗi này xảy ra vì code tự thêm vào đuôi .php làm cho tên file trở thành không chính xác. Để giải quyết vấn đề này, ta có thể sử dụng NULL BYTE, tức là giá trị %00. Giá trị này là một giá trị giúp kết thúc chuỗi được sử dụng trong URL-encoded (trong chuỗi đường dẫn).

Khi sử dụng giá trị này, câu lệnh:

include("languages/../../../../etc/passwd%00" . ".php");

Sẽ tương đương với:

include("languages/../../../../etc/passwd");

Null byte injection

Trick %00 không áp dụng với PHP 5.3.4 trở lên 😉.

Give Lab #3 a try to read /etc/passwd. What is the request look like?

/lab3.php?file=../../../../etc/passwd%00

Fourth scenario

Giả sử developer lọc các request có input là /etc/passwd. Khi đó, ta có thể sử dụng trick %00 như ở trên hoặc dùng /.. Chuỗi ký tự /. tương đương với việc truy cập vào tập tin/thư mục hiện tại: http://webapp.thm/index.php?lang=/etc/passwd/..

Khi đó, do input /etc/passwd/./etc/passwd/%00 khác /etc/passwd nên chúng ta có thể bypass được filter.

Which function is causing the directory traversal in Lab #4?

file_get_contents

Fifth scenario

Giả sử ta gửi đến server một request:

http://webapp.thm/index.php?lang=../../../../etc/passwd

Và server trả về một lỗi như sau:

Warning: include(languages/etc/passwd): failed to open stream: No such file or directory in /var/www/html/THM-5/index.php on line 15

Có thể thấy, các chuỗi ký tự ../ ở trong request đã bị thay thế bằng chuỗi rỗng. Nói cách khác, developer đã lọc ra các chuỗi ký tự dùng để thực hiện path traversal.

Chúng ta có thể bypass bằng cách thay input trong request thành: ....//....//....//....//....//etc/passwd.

Hàm thay thế của PHP chỉ thay thế tất cả các chuỗi con ../ một lần duy nhất mỗi lần gọi hàm nên input của chúng ta sẽ trở thành ../../../../etc/passwd.

Minh họa:

Sixth scenario

Trường hợp cuối cùng, nếu developer bắt buộc input phải có một thư mục được định nghĩa trước nào đó, chẳng hạn như includes như trong request sau đây:

http://webapp.thm/index.php?lang=languages/EN.php

Việc khai thác vô cùng đơn giản: chúng ta dùng path traversal (thêm một lần nữa) để truy ngược ra /etc/passwd. Cụ thể như sau:

http://webapp.thm/index.php?lang=languages/../../../../../etc/passwd

Try out Lab #6 and check what is the directory that has to be in the input field?

THM-profile

Try out Lab #6 and read /etc/os-release. What is the VERSION_ID value?

12.04