Bảy thói quen để viết các ứng dụng PHP an toàn

Tăng cường an ninh cho các ứng dụng web của bạn

Bảo đảm an ninh trong PHP gồm các quan tâm an ninh cục bộ và từ xa. Phát hiện các thói quen mà các nhà phát triển PHP nên rèn luyện để thực hiện các ứng dụng Web có cả hai đặc trưng trên.

Nathan A. Good, Kỹ sư trưởng, tư vấn, Freelance Developer

Nathan Good sống tại vùng Twin Cities của bang Minnesota. Về chuyên môn, ông làm công việc phát triển phần mềm, kiến trúc phần mềm và quản trị các hệ thống. Khi ông không viết phần mềm, ông rất thích xây dựng các máy chủ và máy tính cá nhân, đọc và làm việc với các công nghệ mới và cố gắng khuyến khích bạn bè của mình chuyển sang phần mềm mã nguồn mở


Cấp độ đóng góp cho developerWorks của
        tác giả

31 07 2009

Khi nói đến an ninh, hãy nhớ rằng ngoài các vấn đề an ninh hệ điều hành và nền hệ thống thực tế, bạn cần phải đảm bảo rằng bạn viết ứng dụng của bạn là an toàn. Khi bạn viết các ứng dụng PHP, hãy áp dụng bảy thói quen này để đảm bảo rằng các ứng dụng của bạn là an toàn mức cao nhất có thể:

  • Kiểm tra hợp lệ đầu vào
  • Bảo vệ hệ thống tệp tin của bạn
  • Bảo vệ cơ sở dữ liệu của bạn
  • Bảo vệ dữ liệu phiên làm việc của bạn
  • Bảo vệ chống lại các sơ hở của kịch bản lệnh xuyên các trang (Cross-Site Scripting - XSS)
  • Kiểm tra các biểu mẫu gửi lên
  • Bảo vệ chống lại các giả mạo yêu cầu xuyên các trang (Cross-Site Request Forgeries - CSRF)

Kiểm tra hợp lệ đầu vào

Kiểm tra dữ liệu hợp lệ là thói quen quan trọng nhất mà bạn có thể tuân thủ khi nói về an ninh. Và khi nói đến đầu vào, đơn giản là: Đừng tin tưởng người sử dụng. Người sử dụng của bạn có lẽ là người tốt, và hầu hết họ có lẽ sử dụng ứng dụng của bạn đúng như bạn đã mong đợi. Tuy nhiên, bất cứ khi nào có cơ hội nhập đầu vào, có nghĩa là cũng có cơ hội để nhập đầu vào xấu, thực sự là xấu. Là một nhà phát triển ứng dụng, bạn phải bảo vệ ứng dụng của mình trước đầu vào xấu. Việc xem xét cẩn thận đầu vào từ người sử dụng của bạn đang hướng tới đâu và đó phải là cái gì sẽ cho phép bạn xây dựng một ứng dụng vững chãi, an toàn.

Mặc dù tương tác hệ thống tệp tin và cơ sở dữ liệu sẽ được trình bày sau, dưới đây là các lời khuyên chung về kiểm tra hợp lệ, bao gồm mọi loại:

  • Sử dụng danh sách các giá trị hợp lệ (white-listed)
  • Luôn luôn kiểm tra hợp lệ lại các lựa chọn bị hạn chế
  • Sử dụng các hàm thoát lập sẵn
  • Kiểm tra kiểu dữ liệu đúng đắn, ví dụ như là các số

Các giá trị trong danh sách trắng (white-listed) là các giá trị được chấp nhận, đối lập với các giá trị thuộc danh sách đen (black-listed) là không được chấp nhận. Sự phân biệt là ở chỗ, thông thường khi kiểm tra hợp lệ, danh sách hoặc dải các giá trị khả dĩ nhỏ hơn danh sách các giá trị không hợp lệ vì nhiều giá trị không hợp lệ còn chưa biết hoặc rất bất ngờ.

Khi bạn đang thực hiện kiểm tra hợp lệ, nên nhớ rằng thường dễ hình dung và kiểm tra hợp lệ những cái mà ứng dụng đó cho phép thay vì cố gắng bảo vệ chống lại tất cả các giá trị chưa biết. Ví dụ, để giới hạn các giá trị trong một trường chỉ là các số, hãy viết ra một thủ tục (routine) bảo đảm đầu vào tất cả phải là số. Đừng viết thủ tục để tìm kiếm các giá trị không phải là số và đánh dấu nó là không hợp lệ nếu tìm thấy.

Bảo vệ hệ thống tệp tin của bạn

Vào tháng Bảy năm 2000, một trang web đã để lọt dữ liệu của khách hàng trong các tệp tin trên một máy chủ Web. Một người xem truy cập vào trang web đó đã điều khiển URL để xem được các tệp tin có chứa dữ liệu. Mặc dù các tệp tin này bị đặt sai vị trí, ví dụ này nhấn mạnh tầm quan trọng của việc bảo vệ hệ thống tệp tin của bạn chống lại những kẻ thâm nhập.

Nếu ứng dụng PHP làm bất cứ điều gì với các tệp tin và có dữ liệu biến đổi mà người sử dụng có thể nhập vào, hãy cẩn thận rằng bạn phải lau chùi sạch sẽ dữ liệu đầu vào của người sử dụng để đảm bảo rằng người sử dụng không thể làm được bất cứ điều gì đối với hệ thống tệp tin mà bạn không muốn họ làm. Liệt kê 1 cho thấy một thí dụ về một trang web PHP để tải xuống một hình ảnh khi cung cấp tên.

Liệt kê 1. Tải xuống một tệp tin
<?php
if ($_POST['submit'] == 'Download') {
    $file = $_POST['fileName'];
    header("Content-Type: application/x-octet-stream");
    header("Content-Transfer-Encoding: binary");
    header("Content-Disposition: attachment; filename=\"" . $file . "\";" );
    $fh = fopen($file, 'r');
    while (! feof($fh))
    {
        echo(fread($fh, 1024));
    }
    fclose($fh);
} else {
    echo("<html><head><");
        echo("title>Guard your filesystem</title></head>");
    echo("<body><form id=\"myFrom\" action=\"" . $_SERVER['PHP_SELF'] .
        "\" method=\"post\">");
    echo("<div><input type=\"text\" name=\"fileName\" value=\"");
    echo(isset($_REQUEST['fileName']) ? $_REQUEST['fileName'] : '');
    echo("\" />");
    echo("<input type=\"submit\" value=\"Download\" name=\"submit\" /></div>");
    echo("</form></body></html>");
}

Như bạn có thể thấy, kịch bản tương đối nguy hiểm trong Liệt kê 1 đưa ra phục vụ bất kỳ tệp tin nào mà máy chủ Web có quyền đọc, kể cả các tệp tin trong thư mục phiên làm việc (xem phần "Bảo vệ dữ liệu phiên làm việc của bạn") và thậm chí một số tệp tin hệ thống như /etc/passwd. Thí dụ này có một hộp văn bản trong đó người sử dụng có thể gõ nhập vào tên tệp tin dùng làm ví dụ, nhưng tên tệp tin đó cũng có thể được cung cấp một cách dễ dàng trong chuỗi truy vấn.

Việc cấu hình cho phép truy cập hệ thống tệp tin tùy theo đầu vào của người sử dụng là nguy hiểm, vì vậy tốt nhất là tránh hoàn toàn việc đó bằng cách thiết kế ứng dụng của bạn để nó sử dụng một cơ sở dữ liệu và các tên tệp tin được tạo ra và giấu kín. Tuy nhiên, không phải lúc nào cũng có thể làm thế. Liệt kê 2 cung cấp một thí dụ về một thủ tục kiểm tra hợp lệ tên tệp tin. Nó sử dụng các biểu thức chính quy để đảm bảo rằng chỉ các ký tự hợp lệ là được sử dụng trong tên tệp tin và đặc biệt kiểm tra các ký tự chấm chấm: ...

Liệt kê 2. Kiểm tra hợp lệ với các ký tự trong tên tệp tin
function isValidFileName($file) {
    /* don't allow .. and allow any "word" character \ / */
    return preg_match('/^(((?:\.)(?!\.))|\w)+$/', $file);
}

Bảo vệ cơ sở dữ liệu của bạn

Vào tháng Tư năm 2008, Cục quản lý nhà tù (Department of Corrections) của một bang của Mỹ đã để rò rỉ dữ liệu nhạy cảm vì lý do tên cột SQL được sử dụng trong chuỗi vấn tin. Chỗ sơ hở này cho phép những người sử dụng ác ý chọn được (select) những cột nào muốn hiển thị, đưa ra các trang, và lấy dữ liệu. Vụ rò rỉ này cho thấy rằng người sử dụng có thể tính toán ra được các cách để làm cho đầu vào của họ làm những việc mà các nhà phát triển ứng dụng chắc chắn không lường trước được và nhấn mạnh sự cần thiết phải bảo vệ một cách cẩn thận chống lại các cuộc tấn công bằng bơm vào SQL (SQL injection).

Liệt kê 3 cho thấy một thí dụ của một kịch bản chạy một lệnh SQL. Trong thí dụ này, lệnh SQL là một lệnh động, có thể để lọt cùng một kiểu tấn công đó. Chủ nhân của biểu mẫu này có thể đã nghĩ rằng chúng là an toàn vì họ đã hạn chế các tên cột chỉ trong một danh sách chọn. Tuy nhiên, mã này bỏ qua sự chú ý nói trong thói quen cuối cùng về việc nhại mẫu (form spoofing) — chỉ vì mã hạn chế việc lựa chọn chỉ trong các hộp thả xuống không có nghĩa rằng một ai đó không thể gửi lên một biểu mẫu với bất cứ cái gì mà họ muốn trong đó (kể cả một dấu sao [*]).

Liệt kê 3. Thi hành một lệnh SQL
<html>
<head>
<title>SQL Injection Example</title>
</head>
<body>
<form id="myFrom" action="<?php echo $_SERVER['PHP_SELF']; ?>"
    method="post">
<div><input type="text" name="account_number"
    value="<?php echo(isset($_POST['account_number']) ? 
        $_POST['account_number'] : ''); ?>" />
<select name="col">
<option value="account_number">Account Number</option>
<option value="name">Name</option>
<option value="address">Address</option>
</select>
<input type="submit" value="Save" name="submit" /></div>
</form>
<?php
if ($_POST['submit'] == 'Save') {
    /* do the form processing */
    $link = mysql_connect('hostname', 'user', 'password') or 
        die ('Could not connect' . mysql_error());
    mysql_select_db('test', $link);
		
		$col = $_POST['col'];

    $select = "SELECT " . $col . " FROM account_data WHERE account_number = " 
        . $_POST['account_number'] . ";" ;
    echo '<p>' . $select . '</p>';

    $result = mysql_query($select) or die('<p>' . mysql_error() . '</p>');

    echo '<table>';
    while ($row = mysql_fetch_assoc($result)) {
        echo '<tr>';
        echo '<td>' . $row[$col] . '</td>';
        echo '</tr>';
    }
    echo '</table>';

    mysql_close($link);
}
?>
</body>
</html>

Vì vậy, để hình thành thói quen bảo vệ cơ sở dữ liệu của bạn, tránh mã SQL động càng nhiều càng tốt. Nếu bạn không thể tránh được mã SQL động, đừng sử dụng trực tiếp đầu vào đối với các cột. Liệt kê 4 hiển thị một thí dụ về năng lực khi bổ sung một thủ tục kiểm tra hợp lệ đơn giản đối với trường số tài khoản để đảm bảo rằng nó không thể là một dữ liệu không-phải-số, đồng thời sử dụng các tên cột tĩnh.

Liệt kê 4. Bảo vệ bằng kiểm tra hợp lệ và mysql_real_escape_string()
<html>
<head>
<title>SQL Injection Example</title>
</head>
<body>
<form id="myFrom" action="<?php echo $_SERVER['PHP_SELF']; ?>"
    method="post">
<div><input type="text" name="account_number"
    value="<?php echo(isset($_POST['account_number']) ? 
        $_POST['account_number'] : ''); ?>" /> <input type="submit"
    value="Save" name="submit" /></div>
</form>
<?php
function isValidAccountNumber($number) 
{
    return is_numeric($number);
}

if ($_POST['submit'] == 'Save') {

    /* Remember habit #1--validate your data! */
    if (isset($_POST['account_number']) &&
    isValidAccountNumber($_POST['account_number'])) {

        /* do the form processing */
        $link = mysql_connect('hostname', 'user', 'password') or
        die ('Could not connect' . mysql_error());
        mysql_select_db('test', $link);

        $select = sprintf("SELECT account_number, name, address " .
		" FROM account_data WHERE account_number = %s;",
        mysql_real_escape_string($_POST['account_number']));
        echo '<p>' . $select . '</p>';

        $result = mysql_query($select) or die('<p>' . mysql_error() . '</p>');

        echo '<table>';
        while ($row = mysql_fetch_assoc($result)) {
            echo '<tr>';
            echo '<td>' . $row['account_number'] . '</td>';
            echo '<td>' . $row['name'] . '</td>';
            echo '<td>' . $row['address'] . '</td>';
            echo '</tr>';
        }
        echo '</table>';

        mysql_close($link);
    } else {
        echo "<span style=\"font-color:red\">" .
    "Please supply a valid account number!</span>";

    }
}
?>
</body>
</html>

Ví dụ này cũng cho thấy việc sử dụng hàm mysql_real_escape_string(). Hàm này chải sạch một cách đúng đắn đầu vào của bạn, sao cho nó không còn bao gồm các ký tự không hợp lệ. Nếu bạn dựa vào magic_quotes_gpc, xin báo trước là nó đã lạc hậu và sẽ được loại bỏ trong PHP V6. Bây giờ hãy tránh dựa vào nó và viết các ứng dụng PHP của bạn an toàn mà không cần đến nó. Ngoài ra, hãy nhớ rằng nếu bạn đang sử dụng một ISP, có nhiều khả năng là magic_quotes_gpc không được kích hoạt.

Cuối cùng, trong ví dụ được cải tiến, bạn có thể thấy rằng lệnh SQL và đầu ra không bao gồm việc lựa chọn cột động. Bằng cách này, nếu bạn sau này thêm cột vào bảng mà có các thông tin khác nhau, bạn có thể in chúng ra. Nếu bạn đang sử dụng một khung công tác để làm việc với cơ sở dữ liệu của bạn, có nhiều khả năng là khung công tác của bạn đã kiểm tra hợp lệ SQL cho bạn rồi. Hãy kiểm tra tài liệu về khung công tác của bạn xem đúng như thế không; nếu bạn vẫn chưa chắc chắn, hãy thực hiện việc kiểm tra hợp lệ với các lỗi về an toàn. Thậm chí nếu bạn đang sử dụng một khung công tác để tương tác cơ sở dữ liệu, bạn vẫn cần phải thực hiện những kiểm tra hợp lệ khác.


Bảo vệ phiên làm việc của bạn

Theo mặc định, thông tin phiên làm việc trong PHP được viết vào một thư mục tạm thời. Hãy xem xét biểu mẫu trong Liệt kê 5, nó cho thấy cách lưu một mã nhận dạng người sử dụng và số tài khoản trong một phiên làm việc.

Liệt kê 5. Lưu trữ dữ liệu trong phiên
<?php
session_start();
?>
<html>
<head>
<title>Storing session information</title>
</head>
<body>
<?php
if ($_POST['submit'] == 'Save') {
    $_SESSION['userName'] = $_POST['userName'];
    $_SESSION['accountNumber'] = $_POST['accountNumber'];
}
?>
<form id="myFrom" action="<?php echo $_SERVER['PHP_SELF']; ?>"
    method="post">
<div><input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="text" name="userName"
    value="<?php echo(isset($_POST['userName']) ? $_POST['userName'] : ''); ?>" />
<br />
<input type="text" name="accountNumber"
    value="<?php echo(isset($_POST['accountNumber']) ? 
    $_POST['accountNumber'] : ''); ?>" />
<br />
<input type="submit" value="Save" name="submit" /></div>
</form>
</body>
</html>

Liệt kê 6 cho thấy nội dung của thư mục /tmp.

Liệt kê 6. Các tệp tin phiên trong thư mục /tmp
-rw-------  1 _www    wheel       97 Aug 18 20:00 sess_9e4233f2cd7cae35866cd8b61d9fa42b

Như bạn có thể thấy, tệp tin phiên, khi được in ra (xem Liệt kê 7), chứa các thông tin trong một định dạng khá dễ đọc. Vì tệp tin phải đọc được và viết được đối với người sử dụng máy chủ web, các tệp tin phiên này có thể tạo ra một vấn đề lớn đối với bất cứ người nào trên một máy chủ chia sẻ. Người nào đó không phải là bạn có thể viết một kịch bản lệnh đọc các tệp tin này để họ có thể thử lợi dụng phiên làm việc đó.

Liệt kê 7. Nội dung của một tệp tin phiên
userName|s:5:"ngood";accountNumber|s:9:"123456789";

Lưu mật khẩu

Mật khẩu sẽ tuyệt đối không bao giờ được lưu giữ ở dạng văn bản rõ ở bất kỳ nơi nào — không được nằm trong một cơ sở dữ liệu, phiên làm việc, hệ thống tệp tin, hoặc ở bất kỳ dạng nào khác. Cách tốt nhất để xử lý các mật khẩu là lưu chúng đã mã hóa và so sánh với các mật khẩu đã mã hóa với nhau. Mặc dù điều này có vẻ là hiển nhiên, song việc lưu giữ chúng ở dạng văn bản rõ dường như được làm khá nhiều trong thực tế. Bất cứ khi nào bạn sử dụng một trang web mà có thể gửi cho bạn mật khẩu của bạn thay vì việc đặt lại, điều đó có nghĩa là hoặc mật khẩu được lưu ở dạng văn bản rõ hoặc đã có mã lệnh sẵn để giải mã mật khẩu nếu nó được mật mã hóa. Ngay cả ở trường hợp sau, mã lệnh để giải mã có thể tìm thấy và khai thác được.

Bạn có thể làm hai điều để bảo vệ dữ liệu phiên của bạn. Trước tiên là mã hóa mọi thứ mà bạn đưa vào phiên. Tuy nhiên chỉ riêng việc bạn đã mã hóa dữ liệu không có nghĩa là nó đã an toàn trọn vẹn, do đó hãy cẩn thận, đừng tin tưởng vào việc này như là phương tiện duy nhất của bạn để bảo vệ phiên làm việc của mình. Có cách khác là lưu dữ liệu phiên của bạn ở một nơi khác, ví dụ như một cơ sở dữ liệu. Bạn vẫn phải đảm bảo rằng bạn đang khóa kín cơ sở dữ liệu của bạn, nhưng cách tiếp cận này giải quyết được hai vấn đề: Trước nhất, nó đặt dữ liệu của bạn vào một nơi an toàn hơn một hệ thống tệp tin chia sẻ; thứ hai, nó cho phép ứng dụng của bạn mở rộng trải ra bao gồm nhiều máy chủ web dễ dàng hơn với các phiên được chia sẻ xuyên qua nhiều máy chủ.

Để thực hiện ghi lưu bền vững các dữ liệu về phiên làm việc của chính bạn, xem hàm session_set_save_handler() trong PHP. Dùng nó, bạn có thể lưu trữ thông tin phiên trong một cơ sở dữ liệu hoặc triển khai thực hiện một trình xử lý (handler) để mã hoá và giải mã toàn bộ các dữ liệu của bạn. Liệt kê 8 cung cấp một thí dụ về cách sử dụng hàm này và các hàm khung cho việc triển khai thực hiện. Bạn cũng có thể kiểm tra các thí dụ về cách sử dụng một cơ sở dữ liệu trong phần Tài nguyên.

Liệt kê 8. Ví dụ về hàm session_set_save_handler()
function open($save_path, $session_name)
{
    /* custom code */
    return (true);
}

function close()
{
    /* custom code */
    return (true);
}

function read($id)
{
    /* custom code */
    return (true);
}

function write($id, $sess_data)
{
    /* custom code */
    return (true);
}

function destroy($id)
{
    /* custom code */
    return (true);
}

function gc($maxlifetime)
{
    /* custom code */
    return (true);
}

session_set_save_handler("open", "close", "read", "write", "destroy", "gc");

Bảo vệ chống lại các lỗ hổng XSS

Các lỗ hổng XSS chiếm một tỷ lệ lớn trong tất cả các lỗ hổng về trang web được ghi chép lại trong năm 2007 (xem Tài nguyên). Một lỗ hổng XSS xuất hiện khi một người sử dụng có khả năng bơm mã HTML vào các trang web của bạn. Mã HTML có thể mang theo mã JavaScript bên trong các thẻ kịch bản (script tags), bằng cách đó cho phép JavaScript chạy bất cứ khi nào một trang được rút ra. Biểu mẫu trong Liệt kê 9 có thể đại diện cho một diễn đàn, trang mạng biên tập tự do (wiki), mạng xã hội, hoặc bất kỳ trang web nào khác thông dụng để gõ nhập văn bản.

Liệt kê 9. Biểu mẫu để nhập vào văn bản
<html>
<head>
<title>Your chance to input XSS</title>
</head>
<body>
<form id="myFrom" action="showResults.php" method="post">
<div><textarea name="myText" rows="4" cols="30"></textarea><br />
<input type="submit" value="Delete" name="submit" /></div>
</form>
</body>
</html>

Liệt kê 10 chứng tỏ cách biểu mẫu này in ra được các kết quả, cho phép tấn công bằng XSS.

Liệt kê 10. showResults.php
<html>
<head>
<title>Results demonstrating XSS</title>
</head>
<body>
<?php
echo("<p>You typed this:</p>");
echo("<p>");
echo($_POST['myText']);
echo("</p>");
?>
</body>
</html>

Liệt kê 11 đưa ra một thí dụ cơ sở trong đó các cửa sổ mới bật ra mở đến trang chủ của Google. Nếu ứng dụng web của bạn không bảo vệ chống lại các tấn công bằng XSS, thì giới hạn thiệt hại duy nhất chỉ còn là sức tưởng tượng của kẻ thâm nhập mà thôi. Ví dụ, một ai đó có thể thêm vào một liên kết mà bắt chước kiểu dáng của trang web đó để lừa đảo (phishing) (xem Tài nguyên).

Liệt kê 11. Mẫu văn bản đầu vào độc hại
<script type="text/javascript">myRef = window.open('http://www.google.com','mywin',
'left=20,top=20,width=500,height=500,toolbar=1,resizable=0');</script>

Để tự bảo vệ bạn chống lại các tấn công XSS, hãy lọc đầu vào của bạn thông qua hàm htmlentities() bất cứ khi nào giá trị của một biến được in đến đầu ra. Hãy nhớ làm theo thói quen đầu tiên về kiểm tra hợp lệ dữ liệu đầu vào bằng các giá trị trong danh sách trắng trong ứng dụng web của bạn đối với tên, địa chỉ email, số điện thoại, và thông tin về hoá đơn thanh toán.

Một phiên bản an toàn hơn nhiều của trang để hiển thị văn bản đầu vào như dưới đây.

Liệt kê 12. Một biểu mẫu an toàn hơn
<html>
<head>
<title>Results demonstrating XSS</title>
</head>
<body>
<?php
echo("<p>You typed this:</p>");
echo("<p>");
echo(htmlentities($_POST['myText']));
echo("</p>");
?>
</body>
</html>

Bảo vệ chống lại việc gửi lên (post) không hợp lệ

Nhại mẫu (Form spoofing) là khi một ai đó thực hiện gửi lên đến một trong các biểu mẫu của bạn từ một nơi nào đó mà bạn không mong đợi. Cách dễ nhất để nhại mẫu chỉ đơn giản là tạo ra một trang web đệ trình một biểu mẫu, chuyển theo tất cả các giá trị. Do các ứng dụng web là phi trạng thái (stateless), nên không có cách nào để chắc chắn tuyệt đối là dữ liệu đã được gửi lên từ nơi mà bạn muốn nó đến từ đó. Mọi thứ từ các địa chỉ IP cho đến tên máy chủ, cuối cùng rồi đều có thể bị nhại. Liệt kê 13 hiển thị một biểu mẫu điển hình cho phép bạn nhập thông tin.

Liệt kê 13. Một biểu mẫu để xử lý văn bản
<html>
<head>
<title>Form spoofing example</title>
</head>
<body>
<?php
if ($_POST['submit'] == 'Save') {
    echo("<p>I am processing your text: ");
    echo($_POST['myText']);
    echo("</p>");
}
?>
</body>
</html>

Liệt kê 14 cho thấy một biểu mẫu sẽ được gửi đến biểu mẫu trong Liệt kê 13. Để thử việc này, bạn có thể đặt mẫu đó lên một trang web, sau đó ghi lưu mã trong Liệt kê 14 như một tài liệu HTML trên máy tính của bạn. Khi bạn đã lưu lại biểu mẫu này, hãy mở nó ra trong một trình duyệt. Sau đó bạn có thể điền dữ liệu vào và đệ trình biểu mẫu đó rồi quan sát trong khi dữ liệu được xử lý.

Liệt kê 14. Một biểu mẫu để thu thập dữ liệu của bạn
<html>
<head>
<title>Collecting your data</title>
</head>
<body>
<form action="processStuff.php" method="post">
<select name="answer">
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
<input type="submit" value="Save" name="submit" />
</form>
</body>
</html>

Tác động tiềm tàng của việc nhại mẫu thực ra là ở chỗ nếu bạn có một biểu mẫu mà có các hộp thả xuống, nút radio, hộp kiểm, hoặc các đầu vào hạn chế khác, những hạn chế đó không có ý nghĩa gì cả nếu biểu mẫu đó bị giả mạo. Xem xét mã trong Liệt kê 15, trong đó chứa một biểu mẫu với dữ liệu không hợp lệ.

Liệt kê 15. Một biểu mẫu với dữ liệu không hợp lệ
<html>
<head>
<title>Collecting your data</title>
</head>
<body>
<form action="http://path.example.com/processStuff.php" 
    method="post"><input type="text" name="answer"
    value="There is no way this is a valid response to a yes/no answer..." />
<input type="submit" value="Save" name="submit" />
</form>
</body>
</html>

Nếu bạn có một hộp thả xuống hoặc một nút radio, hạn chế người sử dụng chỉ có một số lượng đầu vào nhất định, bạn có thể bị thuyết phục rằng không phải lo lắng về việc kiểm tra hợp lệ đầu vào. Rốt cuộc, biểu mẫu đầu vào của bạn bảo đảm rằng người sử dụng chỉ có thể nhập vào một số dữ liệu nhất định, đúng không? Để hạn chế việc nhại mẫu, hãy lập ra các biện pháp để bảo đảm rằng những người gửi dữ liệu lên đúng là những người khai nhận rằng họ là những người đó. Một kỹ thuật mà bạn có thể sử dụng là thẻ bài sử dụng một lần (single-use token), nó không làm cho việc giả mạo mẫu của bạn bất khả thi nhưng làm cho nó trở thành một điều rắc rối kinh khủng. Vì thẻ bài thay đổi mỗi khi mẫu được rút xuống, một kẻ xâm nhập tương lai cần phải lấy một cá thể của biểu mẫu đang gửi, rút thẻ bài, và đặt nó vào phiên bản giả mạo của biểu mẫu đó. Kỹ thuật này làm cho rất khó có khả năng một ai đó lập ra một biểu mẫu web lâu dài để gửi các yêu cầu không mong muốn đến ứng dụng của bạn. Liệt kê 16 đưa ra một thí dụ về một thẻ bài biểu mẫu dùng một lần.

Liệt kê 16. Sử dụng một thẻ bài biểu mẫu một lần
<?php
session_start();
?>
<html>
<head>
<title>SQL Injection Test</title>
</head>
<body>
<?php

echo 'Session token=' . $_SESSION['token'];
echo '<br />';
echo 'Token from form=' . $_POST['token'];
echo '<br />';

if ($_SESSION['token'] == $_POST['token']) {
    /* cool, it's all good... create another one */

} else {
    echo '<h1>Go away!</h1>';
}
$token = md5(uniqid(rand(), true)); 
$_SESSION['token'] = $token; 
?>
<form id="myFrom" action="<?php echo $_SERVER['PHP_SELF']; ?>"
    method="post">
<div><input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="text" name="myText"
    value="<?php echo(isset($_POST['myText']) ? $_POST['myText'] : ''); ?>" />
<input type="submit" value="Save" name="submit" /></div>
</form>
</body>
</html>

Bảo vệ chống lại CSRF

Các giả mạo truy vấn xuyên các trang (các tấn công CSRF-Cross-Site Request Forgeries) khai thác các lợi thế về quyền ưu tiên của người sử dụng để thực hiện tấn công. Trong một cuộc tấn công CSRF, người sử dụng của bạn có thể dễ dàng trở thành các kẻ đồng loã không bị nghi ngờ. Liệt kê 17 cung cấp một thí dụ về một trang web thực hiện một số hành động. Trang web này tìm kiếm thông tin đăng nhập của người sử dụng từ cookie. Khi mà cookie này hợp lệ, trang web sẽ xử lý yêu cầu.

Liệt kê 17. Một thí dụ của CSRF
<img src="http://www.example.com/processSomething?id=123456789" />

Các cuộc tấn công CSRF thường được thực hiện dưới dạng các thẻ <img> vì trình duyệt gọi URL một cách không ý thức để lấy hình ảnh. Tuy nhiên, nguồn hình ảnh dễ dàng có thể chỉ là URL của một trang web trên cùng một site, thực hiện các việc xử lý nào đó dựa trên các tham số chuyển cho nó. Khi thẻ <img> này được đặt trong một cuộc tấn công XSS — cũng là các tấn công phổ biến nhất được ghi chép lại — người sử dụng dễ dàng có thể làm một việc gì đó với quyền ưu tiên được cấp mà không biết mình đã làm — như vậy, có thể giả mạo.

Để tự bảo vệ bạn chống lại CSRF, hãy sử dụng cách tiếp cận thẻ bài sử dụng một lần mà bạn áp dụng tuân theo thói quen xác minh các biểu mẫu gửi lên. Ngoài ra, sử dụng biến hiển $_POST thay vì $_REQUEST. Liệt kê 18 trình bày một ví dụ xấu về trang web mà xử lý y như thế — cho dù trang web đó được gọi bởi một yêu cầu GET hay bởi vì có một biểu mẫu gửi lên đến nó.

Liệt kê 18. Lấy dữ liệu từ $_REQUEST
<html>
<head>
<title>Processes both posts AND gets</title>
</head>
<body>
<?php
if ($_REQUEST['submit'] == 'Save') {
    echo("<p>I am processing your text: ");
    echo(htmlentities($_REQUEST['text']));
    echo("</p>");
}
?>
</body>
</html>

Liệt kê 19 cho thấy một phiên bản đã làm sạch của trang này, sẽ chỉ làm việc khi POST biểu mẫu.

Liệt kê 19. Lấy dữ liệu chỉ từ $_POST
<html>
<head>
<title>Processes both posts AND gets</title>
</head>
<body>
<?php
if ($_POST['submit'] == 'Save') {
    echo("<p>I am processing your text: ");
    echo(htmlentities($_POST['text']));
    echo("</p>");
}
?>
</body>
</html>

Kết luận

Bắt đầu với bảy thói quen này để viết các ứng dụng web PHP an toàn hơn sẽ giúp bạn tránh bị trở thành nạn nhân dễ dàng của các tấn công độc hại. Giống như nhiều thói quen, chúng lúc đầu có vẻ như rắc rối, nhưng chúng trở nên tự nhiên hơn theo thời gian.

Nên nhớ rằng thói quen đầu tiên là chủ chốt: Hãy kiểm tra hợp lệ đầu vào của bạn. Khi bạn chắc chắn rằng đầu vào của bạn không chứa các giá trị xấu, bạn có thể chuyển tiếp sang việc bảo vệ hệ thống tệp tin, cơ sở dữ liệu, và phiên làm việc của bạn. Cuối cùng, hãy chắc chắn rằng mã PHP của bạn kiên cường kháng cự được các tấn công XSS, nhại mẫu, và các tấn công CSRF. Một cách tiếp cận có kỷ luật để hình thành những thói quen ấy sẽ khởi đầu một con đường dài hướng đến việc phòng ngừa các tấn công dễ dàng.

Tài nguyên

Học tập

  • Đọc tài liệu hướng dẫn "Khóa các ứng dụng PHP của bạn" trên developerWorks để tìm hiểu bốn quy tắc an ninh mà nhà phát triển không được vi phạm.
  • Đọc "Mật mã hoá PHP cho người bình thường" để tìm hiểu cách bảo vệ dữ liệu trong các ứng dụng PHP.
  • Xem PHP Security Consortium để có các thông tin tuyệt vời về an ninh PHP.
  • Xem trang PHP security tại trang web chính thức của PHP để nhận được các lời khuyên về an ninh.
  • Tìm hiểu thêm về việc thực hiện các trình xử lý phiên làm việc tuỳ chỉnh được từ mục session_set_save_handler trên trang web chính thức của PHP.
  • Đọc mục XSS rất xuất sắc của Wikipedia.
  • Đọc bài Essential PHP Security của Chris Shiflett có sẵn trên PHP.org.
  • PHP.net là nguồn tài nguyên trung tâm cho các nhà phát triển PHP.
  • Xem "Recommended PHP reading list."
  • Duyệt toàn bộ các nội dung PHP trên developerWorks.
  • Mở rộng kỹ năng PHP của bạn bằng cách xem các nguồn tài nguyên dự án PHP developerWorks của IBM .
  • Để nghe phỏng vấn và các cuộc thảo luận thú vị đối với các nhà phát triển phần mềm, xem developerWorks podcasts.
  • Bạn sử dụng cơ sở dữ liệu với PHP? Hãy thử Zend Core for IBM, đó là một môi trường phát triển và chạy sản xuất PHP liền khối, sẵn để dùng ngay, dễ dàng cài đặt và có hỗ trợ DB2 V9 của IBM.
  • Theo sát các sự kiện kỹ thuật và phát tin trên web của developerWorks.
  • Xem qua các hội nghị, trưng bày thương mại, các buổi phát tin trên mạng và các Sự kiện khác trên khắp thế giới sắp diễn ra mà các nhà phát triển mã nguồn mở của IBM quan tâm đến.
  • Hãy truy cập vào Open source zone của developerWorks để có các thông tin hướng dẫn cách làm, các công cụ, và các dự án cập nhật, giúp bạn phát triển với các công nghệ mã nguồn mở và sử dụng chúng với các sản phẩm của IBM.
  • Theo dõi và tìm hiểu về IBM và các công nghệ mã nguồn mở và các chức năng của sản phẩm qua các trình diễn miến phí theo yêu cầu trên developerWorks (developerWorks On demand demos).

Lấy sản phẩm và công nghệ

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở
ArticleID=416856
ArticleTitle=Bảy thói quen để viết các ứng dụng PHP an toàn
publish-date=07312009