良好實(shí)踐

過濾輸入:

是指轉(zhuǎn)義或者刪除不安全的字符。在數(shù)據(jù)到達(dá)應(yīng)用的儲(chǔ)存層(mysql or redis)之前,一定要過濾輸入的數(shù)據(jù)。

過濾HTML數(shù)據(jù):

使用htmlentities()函數(shù)。默認(rèn)情況下,這個(gè)函數(shù)不會(huì)轉(zhuǎn)義單引號(hào)。而且也檢測(cè)不出輸入字符串的字符集。htmlentities()函數(shù)的正確使用方式是:第一個(gè)參數(shù)是輸入字符串;第二個(gè)參數(shù)設(shè)定為ENT_QUOTES常量,轉(zhuǎn)義單引號(hào);第三個(gè)參數(shù)設(shè)定為輸入字符串的字符集。

Example:

<?php

$input = '<p><script>alert("You won the Nigerian lottery!");</script></p>';

echo htmlentities($input, ENT_QUOTES, 'UTF-8');


SQL查詢:

有時(shí)必須根據(jù)輸入數(shù)據(jù)構(gòu)建SQL查詢。直接拼接$_GET或者$_POST中的原始輸入數(shù)據(jù)會(huì)對(duì)數(shù)據(jù)庫(kù)造成巨大傷害。

要使用PDO預(yù)處理語句(prepared statement)。

<?php

$sql = 'SELECT id FROM users WHERE email = :email';

$statement = $pdo->prepare($sql);


$email = filter_input(INPUT_GET, 'email');

$statement->bindValue(':email', $email);

預(yù)處理語句會(huì)自動(dòng)過濾$email的值,防止數(shù)據(jù)庫(kù)收到SQL注入攻擊。一個(gè)SQL語句字符串中可以有很多個(gè)具名占位符,然后在預(yù)處理語句上調(diào)用bindValue()方法綁定各個(gè)占位符的值。


用戶資料信息:

應(yīng)用中如果有用戶資料信息,可能就需要處理郵件地址,電話,郵政編碼等資料信息。這種情況,則需要使用filter_var()和filter_input()函數(shù)。這兩個(gè)函數(shù)可以有效的過濾不同類型的輸入:電子郵件地址,URL編碼字符串,整數(shù)等。

Example:

<?php

$email = "victor.chen@sony.com<script>ALERT</script>";

$safeEmail = filter_var($email, FILTER_SANITIZE_EMAIL);


驗(yàn)證數(shù)據(jù):

在過濾數(shù)據(jù)之后,也需要驗(yàn)證數(shù)據(jù):

Example:

<?php

$email = "victor.chen@sony.com<script>ALERT</script>";

$isEmail = filter_var($email, FILTER_VALIDATE_EMAIL);

if ($isEmail !== false) {

? ? echo "Success";

} else {

echo "Fail";

}


轉(zhuǎn)義輸出:

把輸出渲染成網(wǎng)頁(yè)或者API響應(yīng)時(shí),一定要轉(zhuǎn)義輸出。這也是一種防護(hù)措施,能避免渲染惡意代碼,還能防止應(yīng)用的用戶無意之中執(zhí)行惡意代碼。

htmlentities函數(shù)可以轉(zhuǎn)義輸出,另外一些PHP模板引擎也能如smarty/smarty,會(huì)自動(dòng)轉(zhuǎn)義輸出。


使用bcrypt計(jì)算用戶密碼的哈希值:

我們應(yīng)該計(jì)算用戶密碼的哈希值,而不能加密用戶的密碼。加密和哈希不是一回事。加密是雙向算法,加密的數(shù)據(jù)以后可以解密。而哈希是單向算法,哈希后的數(shù)據(jù)不能再還原成原始值,而且相同的數(shù)據(jù)得到的哈希值始終相同。

數(shù)據(jù)庫(kù)中的密碼需要儲(chǔ)存成哈希值而不是明文。

最安全的哈希算法是bcrypt。


注冊(cè)用戶例子:

<?php

try {


? ? $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);

? ? if (!$email) {


? ? ? ? throw new Exception("Invalid Email");

? ? }

? ? $password = filter_input(INPUT_POST, 'password');

? ? if (!$password || mb_strlen($password) < 8) {


? ? ? ? throw new Exception("Password must contain 8+ characters");

? ? }

? ? $passwordHash = password_hash(

? ? ? ? $password,

? ? ? ? PASSWORD_DEFAULT,

? ? ? ? ['cost' => 12]

? ? );

? ? if ($passwordHash === false) {


? ? ? ? throw new Exception("Password hash failed");

? ? }

? ? $user = new User();

? ? $user->email = $email;

? ? $user->password_hash = $passwordHash;

? ? $user->save();

? ? header('HTTP/1.1 302 Redirect');

? ? header('Location: /login.php');

} catch (Exception $e) {

? ? header('HTTP/1.1 400 Bad request');

? ? echo $e->getMessage();

}

?>

注意:密碼的哈希值要儲(chǔ)存再VARCHAR(255)類型的數(shù)據(jù)庫(kù)中。這樣便于以后存儲(chǔ)比現(xiàn)在的bcrypt算法得到的哈希值更長(zhǎng)的密碼。


Login的示例:

<?php

session_start();

try {

? ? $email = filter_input(INPUT_POST, 'email');

? ? $password = filter_input(INPUT_POST, 'password');

? ? $user = User::findByEmail($email);

? ? if (password_verify($password, $user->password_hash) === false) {


? ? ? ? throw new Exception("Invalid Password", 1);

? ? }

? ? $currentHashAlgorithm = PASSWORD_DEFAULT;

? ? $currentHashOptions = array('cost' => 15);

? ? $passwordNeedsRehash = password_needs_rehash(

? ? ? ? $user->password_hash,

? ? ? ? $currentHashAlgorithm,

? ? ? ? $currentHashOptions?

? ? );

? ? if ($passwordNeedsRehash === true) {

? ? ? ? $user->password_hash = password_hash(

? ? ? ? ? ? $password,

? ? ? ? ? ? $currentHashAlgorithm,

? ? ? ? ? ? $currentHashOptions

? ? ? ? );

? ? ? ? $user->save();

? ? }

? ? $_SESSION['user_logged_in'] = 'yes';

? ? $_SESSION['user_email'] = $email;

? ? header('HTTP/1.1 302 Redirect');

? ? header('Location: /user-profile.php');

} catch (Exception $e) {

? ? header('HTTP/1.1 401 Unauthorized');

? ? echo $e->getMessage();

}

?>

password_verify()函數(shù)需要接受兩個(gè)參數(shù),第一個(gè)是純文本密碼,第二個(gè)是用戶記錄中現(xiàn)有的密碼哈希值。如果函數(shù)返回的是true,則證明純文本密碼正確。

password_needs_rehash()函數(shù)檢查用戶記錄中現(xiàn)有的密碼哈希值是否需要更新。這個(gè)函數(shù)能確保指定的密碼哈希值是使用的最新的哈希算法選項(xiàng)創(chuàng)建的。

密碼的哈希值需要更新是因?yàn)楣V档淖饔靡蜃訒?huì)增長(zhǎng)(因?yàn)橛?jì)算機(jī)越來越快,作用因子不夠多會(huì)影響安全性)


日期,時(shí)間和時(shí)區(qū):

首先要為PHP中處理日期和時(shí)間的函數(shù)設(shè)置默認(rèn)時(shí)區(qū)??梢苑旁趐hp.ini文件中:

date.timezone = 'America/New_York';

也可以在運(yùn)行時(shí)使用date_default_timezone_set()函數(shù)設(shè)置默認(rèn)時(shí)區(qū)。


PDO擴(kuò)展:

PHP Data Objects (數(shù)據(jù)對(duì)象)是一系列PHP的類,抽象了不同的數(shù)據(jù)庫(kù)具體實(shí)現(xiàn)。

當(dāng)我們使用數(shù)據(jù)庫(kù)的時(shí)候,需要保證數(shù)據(jù)庫(kù)憑據(jù)的安全。應(yīng)該把數(shù)據(jù)庫(kù)憑據(jù)保存在一個(gè)位于文檔根目錄之外的配置文件中,然后在需要使用憑據(jù)時(shí)導(dǎo)入。千萬不能把憑據(jù)保存到公共的庫(kù)里,如github。


事務(wù):

PDO擴(kuò)展還支持事務(wù)。事務(wù)是指把一系列數(shù)據(jù)庫(kù)語句當(dāng)成個(gè)邏輯單元(具有原子性)執(zhí)行。也就是說,事務(wù)中的一系列SQL查詢要么都成功執(zhí)行,要么根本不執(zhí)行。事務(wù)的原子性能保證數(shù)據(jù)的一致性,安全性和持久性。事務(wù)還有個(gè)很好的副作用----提升性能,因?yàn)槭聞?wù)其實(shí)是把多個(gè)查詢排成隊(duì)列,一次性全部執(zhí)行。? ? ? ? ? ? ? ? ? ? ?

Example:

<?php

require 'settings.php';

// PDO Connection

try {

? ? $pdo = new PDO(

? ? ? ? sprintf(

? ? ? ? ? ? 'mysql:host=%s;dbname=%s;port=%s;charset=%s',

? ? ? ? ? ? $settings['host'],

? ? ? ? ? ? $settings['name'],

? ? ? ? ? ? $settings['port'],

? ? ? ? ? ? $settings['charset'],

? ? ? ? ),

? ? ? ? $settings['username'],

? ? ? ? $settings['password']

? ? );

} catch (PDOException $e) {

? ? // Failed to connect

? ? echo "Database connection failed";

? ? exit;

}

// Query

$stmtSubstract = $pdo->prepare('

? ? UPDATE accounts

? ? SET amount = amount - :amount

? ? WHERE name = :name

');

$stmtAdd = $pdo->prepare('

? ? UPDATE accounts

? ? SET amount = amount + :amount

? ? WHERE name = :name

');

// Start the transaction

$pdo->beginTransaction();

// Withdrawal money from the account

$fromAccount = 'Checking';

$withdrawal = 50;

$stmtSubstract->bindParam(':name', $fromAccount);

$stmtSubstract->bindParam(':amount', $withdrawal, PDO::PARAM_INT);

$stmtSubstract->execute();

// Save money to account 2

$toAccount = 'Savings';

$deposit = 50;

$stmtAdd->bindParam(':name', $toAccount);

$stmtAdd->bindParam(':amount', $deposit, PDO::PARAM_INT);

$stmtAdd->execute();

// submit transaction

$pdo->commit();

?>


多字節(jié)字符串:

PHP假設(shè)字符串中每個(gè)字符都是八位字符,占一個(gè)內(nèi)存,可是很多語言使用的字符串會(huì)多于8位字符串。mbstring擴(kuò)展提供了處理多字節(jié)字符串,能代替大多數(shù)PHP原生的處理字符串的函數(shù)。如,mb_strlen()函數(shù)用于替代PHP原生的strlen()函數(shù)。


字符編碼:

所有的現(xiàn)代的WEB瀏覽器都能處理UTF-8字符編碼。字符編碼是打包Unicode數(shù)據(jù)方式,以便把數(shù)據(jù)存儲(chǔ)在內(nèi)存中,或者通過線纜在服務(wù)器和客戶端之間傳輸。

1. 一定要知道數(shù)據(jù)的字符編碼。

2. 使用UTF-8字符編碼存儲(chǔ)數(shù)據(jù)。

3. 使用UTF-8字符編碼輸出數(shù)據(jù)。

mbstring擴(kuò)展不僅能處理Unicode字符串,還能再不同的字符編碼之間轉(zhuǎn)換多字節(jié)字符串。如果客戶使用Windows專用的字符編碼到處Excel電子表格,我們可以通過mb_detect_encoding()和mb_convert_encoding()函數(shù)可以把Unicode字符串從一種字符串編碼成另一種字符編碼。


流:

流的作用是在出發(fā)地和目的地之間傳輸數(shù)據(jù),流為PHP的很多IO函數(shù)提供底層實(shí)現(xiàn),如file_get_contents(), fopen(), fgets()等。

流的封裝協(xié)議:

我們讀寫文件可以通過HTTP, HTTPS或者SSH與遠(yuǎn)程服務(wù)器通信,還可以打開讀寫ZIP,RAR,等壓縮文件。

1. 開始通信

2. 讀取數(shù)據(jù)

3. 寫入數(shù)據(jù)

4. 結(jié)束通信

每個(gè)流都有一個(gè)協(xié)議和一個(gè)目標(biāo)。指定協(xié)議和目標(biāo)的方法是使用流標(biāo)識(shí)符,其格式如下:

<scheme>://<target>

其中,<scheme>是流的封裝協(xié)議,<target>是流的數(shù)據(jù)源。

<?php

$json = file_get_contents(

? ? 'http://api.flickr.com/services/feeds/photo_public.gne?format=json'

);

這其中“http”協(xié)議會(huì)讓PHP使用http流封裝協(xié)議。http之后是流的目標(biāo)。這就是HTTP流封裝協(xié)議所規(guī)定的。

file://也是流封裝協(xié)議,不過我們通常可以省略因?yàn)檫@是PHP默認(rèn)的封裝協(xié)議

<?php

$handle = fopen('/etc/hosts', 'rb');? ?// 我們省略了file://流封裝協(xié)議

$handle = fopen('file://etc/hosts', 'rb');? ?// 我們標(biāo)注了file://流封裝協(xié)議。

fopen(), fgets(), fputs()等文件系統(tǒng)函數(shù)可以處理ZIP壓縮文件以及Amazon S3服務(wù)。

我們也可以自定義流,用來支持部分或全部PHP文件系統(tǒng)函數(shù)。


流過濾器:

stream_filter_append()函數(shù)可以把過濾器附加到流上。

<?php

$handle = fopen('data.txt', 'rb');

stream_filter_append($handle, 'string.toupper');

while (feof($handle) !== true) {

? ? echo fgets($handle);

}

fclose($handle);

?>

這里stream_filter_append把string.toupper這個(gè)filter加到了流了輸出的全是大寫。

也可以使用php://filter來附加過濾器

<?php

$handle = fopen('php://filter/read=string.toupper/resource=data.txt', 'rb');

while (feof($handle) !== true) {

? ? echo fgets($handle);

}

fclose($handle);

?>

這個(gè)流標(biāo)識(shí)符如下:

filter/read=<filter_name>/resource=<scheme>://<target>

PHP某些文件系統(tǒng)函數(shù)在調(diào)用后無法附加過濾器,例如file(),所以這些函數(shù)使用時(shí)只能用php://filter封裝協(xié)議附加流過濾器。


Example:

<?php

$dateStart = new \DateTime();? // current date

$dateInterval = \DateInterval::createFromDateString('-1 day');? // get date interval

$datePeriod = new \DatePeriod($dateStart, $dateInterval, 30);? // get the period between dates

foreach ($datePeriod as $date) {

? ? $file = "sftp://USER:PASS@rsync.net" . $date->format('Y-m-d') . 'log.bz2';

? ? if (file_exists($file)) {

? ? ? ? $handle = fopen($file, 'rb');? // read the file into stream

? ? ? ? stream_filter_append($handle, 'bzip2.decompress');? // add a filter to stream to decompress the zip file

? ? ? ? while (feof($handle) !== true) {

? ? ? ? ? ? $line = fgets($handle);

? ? ? ? ? ? if (strpos($line, 'www.example.com') !== false) {? ? ? // if find the address in the line

? ? ? ? ? ? ? ? fwrite(STDOUT, $line);? ? ? // output the line

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? fclose($handle);

? ? }

}

?>


自定義流過濾器:

<?php

class DirtyWordsFilter extends php_user_filter

{

? ? /**

? ? * @param resource $in? ? ? 流來的桶隊(duì)列

? ? * @param resource $out? ? 流走的桶隊(duì)列

? ? * @param int? ? ? $consumed? ? 處理的字節(jié)數(shù)

? ? * @param bool? ? $closing? ? 是流中最后一個(gè)桶隊(duì)列么?

? ? */

? ? public function filter($in, $out, &$consumed, $closing)

? ? {

? ? ? ? $words = array('grime', 'dirt', 'grease');

? ? ? ? $wordData = array();

? ? ? ? foreach ($words as $word) {

? ? ? ? ? ? $replacement = array_fill(0, mb_strlen($word), '*');

? ? ? ? ? ? $wordData[$word] = implode('', $replacement);

? ? ? ? }

? ? ? ? $bad = array_keys($wordData);

? ? ? ? $good = array_values($wordData);

? ? ? ? while ($bucket = stream_bucket_make_writeable($in)) {

? ? ? ? ? ? // 檢查桶數(shù)據(jù)

? ? ? ? ? ? $bucket->data = str_replace($bad, $good, $bucket->data);

? ? ? ? ? ? // 增加已處理的數(shù)據(jù)量

? ? ? ? ? ? $consume += $backet->datalen;

? ? ? ? ? ? // 把桶放入流向下游隊(duì)列

? ? ? ? ? ? $stream_bucket_append($out, $bucket);

? ? ? ? }

? ? ? ? return PSFS_PASS_ON;

? ? }

}

?>

filter()方法的作用是接收,處理再轉(zhuǎn)運(yùn)桶中的數(shù)據(jù)流。在filter()方法中,我們迭代桶隊(duì)列$in中的桶,把臟字替換成審查后的值。這個(gè)方法的返回值是PSFS_PASS_ON常量,表示操作成功。

$in? ? 上游流來的一個(gè)隊(duì)列,有一個(gè)或多個(gè)桶,桶中是從出發(fā)地流來的數(shù)據(jù)

$out? ? 由一個(gè)桶或多個(gè)桶組成的隊(duì)列,流向下游的流目的地

&$consumed? ? 自定義的過濾器處理的流數(shù)據(jù)總字節(jié)數(shù)

$closing? ? ?filter()方法接受的是最后一個(gè)桶隊(duì)列么?

然后還需要注冊(cè)自定義的DirtyWordsFilter流過濾器

<?php

stream_filter_register('dirty_words_filter', 'DirtyWordsFilter');

第一個(gè)參數(shù)是用于識(shí)別這個(gè)自定義過濾器的過濾名,第二個(gè)參數(shù)是這個(gè)自定義過濾器的類名?,F(xiàn)在可以使用這個(gè)自定義的流過濾器。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 系列筆記:Modern PHP 筆記(一):語言特性Modern PHP 筆記(二):良好實(shí)踐Modern PHP...
    郝開心信札閱讀 980評(píng)論 0 5
  • 重寫 -isEqual: 必須重寫 -hash,因?yàn)閷?shí)現(xiàn)哈希需要這個(gè)方法配合--遙想當(dāng)年定義了一個(gè)Model的屬性...
    子達(dá)如何閱讀 400評(píng)論 0 3
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 28,818評(píng)論 1 45
  • 攻擊者無時(shí)無刻不在準(zhǔn)備對(duì)你的 Web 應(yīng)用程序進(jìn)行攻擊,因此提高你的 Web 應(yīng)用程序的安全性是非常有必要的。幸運(yùn)...
    代碼技巧閱讀 769評(píng)論 0 9
  • 以前健身房還在的時(shí)候,我今天去明天不去的,特別惰性,總想著來日方長(zhǎng),還有很長(zhǎng)時(shí)間可以揮霍,誰能想到噩耗說來就來,周...
    Cassiel小星星閱讀 234評(píng)論 0 1

友情鏈接更多精彩內(nèi)容