<pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
    <pre id="vvttv"></pre>

      <p id="vvttv"></p>

          <p id="vvttv"></p>

                <p id="vvttv"></p>

                <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                  <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                    <p id="vvttv"></p>

                    原文地址:http://drops.wooyun.org/tips/135

                    譯自:《Pro PHP Security》

                    驗證過濾用戶的輸入


                    即使是最普通的字母數字輸入也可能是危險的,列舉幾個容易引起安全問題的字符:

                    ! $ ^ & * ( ) ~ [ ] \ | { } ' " ; < > ? - `
                    

                    在數據庫中可能有特殊意義的字符:

                    ' " ; \
                    

                    還有一些非打印字符:

                    字符\x00或者說ASCII 0,NULL或FALSE

                    字符\x10和\x13,或者說ASCII 10和13,\n \r

                    字符\x1a或者說ASCII 26,表示文件的結束

                    輸入錯誤的參數類型,也可能導致程序出現意想不到的錯誤。

                    輸入過多的參數值,可能導致溢出等錯誤。

                    PHP中驗證用戶的輸入

                    這里特別要注意php.ini中的register_globals的設定,在早期的php版本中是默認開啟的,這會導致很嚴重的安全問題:

                    #!php
                    <?php
                    // set admin flag
                    if ($auth->isAdmin()) {
                    $admin = TRUE;
                    }
                    // ...
                    if ($admin) {
                    // do administrative tasks
                    }
                    ?>
                    

                    上面這段代碼看起來是安全的,但是如果register_globals開啟了的話,在訪問的url中加入?admin=1即可繞過前半部分的邏輯判斷。

                    更安全的代碼應該給$admin賦默認FALSE值:

                    #!php
                    <?php
                    // create then set admin flag
                    $admin = FALSE;
                    if ($auth->isAdmin()) {
                        $admin = TRUE;
                    }
                    // ...
                    if ($admin) {
                        // do administrative tasks
                    }
                    ?>
                    

                    早期人們開發調試的時候發現使用register_globals有極大的便利,所以早期的php版本中默認開啟。

                    但是隨著越來越多的安全問題,從php 4.2.0開始,register_globals變為了默認關閉。

                    當你發現register_globals是on的時候,你可能會在腳本當中加入如下代碼使其關閉:

                    #!php
                    ini_set('register_globals', 0);
                    

                    但實際上,當所有的全局變量已經創建了之后,以上代碼并不會起到作用。

                    但是你可以在文檔的根目錄下的.htaccess的文件中加上下面這一行:

                    php_flag register_globals 0
                    

                    變量聲明:強烈建議總是事先聲明變量。

                    檢查輸入的類型,長度和格式:

                    字符串檢查:在PHP中,字符串幾乎可以是任何事情,但有些值并不是嚴格的字符串類型,可以用is_string()函數來確定。

                    有些時候你不介意數字作為字符串,可以用empty()函數。

                    數字類型檢查:使用is_int()函數或者is_integer()或is_long(),例如:

                    #!php
                    $year = $_POST['year'];
                    if (!is_int($year))
                    exit("$year is an invalid value for year!");
                    

                    也可以使用gettype()函數判斷類型后做處理:

                    #!php
                    if (gettype($year) != 'integer') {
                    exit("$year is an invalid value for year!");
                    }
                    

                    至少還有三種方式可以吧$year變量轉變為整數:

                    #!php
                    $year = intval($_POST['year']);
                    $year = ( int ) $_POST['year'];
                    if (!settype($year, 'integer')) {exit("$year is an invalid value for year!");}
                    

                    如果允許浮點型與零的值,可以使用is_numeric()函數來做判斷。 判斷一個值是否為布爾型的時候使用is_bool()函數。

                    下表是對各種類型變量使用各函數判斷的結果:

                    http://static.wooyun.org/20141017/2014101711111019667.jpg

                    檢查字符串的長度使用strlen()變量:

                    #!php
                    if (strlen($year) != 4)
                        exit("$year is an invalid value for year!");
                    

                    概括總結一下PHP中類型,長度,格式等驗證:

                    #!php
                    <?php
                    // set up array of expected values and types
                    $expected = array(
                        'carModel' => 'string',
                        'year' => 'int',
                        'imageLocation' => 'filename'
                    );
                    // check each input value for type and length
                    foreach ($expected AS $key => $type) {
                        if (empty($_GET[$key])) {
                            ${$key} = NULL;
                            continue;
                        }
                        switch ($type) {
                            case 'string':
                                if (is_string($_GET[$key]) && strlen($_GET[$key]) < 256) {
                                    ${$key} = $_GET[$key];
                                }
                                break;
                            case 'int':
                                if (is_int($_GET[$key])) {
                                    ${$key} = $_GET[$key];
                                }
                                break;
                            case 'filename':
                                // limit filenames to 64 characters
                                if (is_string($_GET[$key]) && strlen($_GET[$key]) < 64) {
                                    // escape any non-ASCII
                                    ${$key} = str_replace('%', '_', rawurlencode($_GET[$key]));
                                    // disallow double dots
                                    if (strpos(${$key}, '..') === TRUE) {
                                        ${$key} = NULL;
                                    }
                                }
                                break;
                        }
                        if (!isset(${$key})) {
                            ${$key} = NULL;
                        }
                    }
                    // use the now-validated input in your application
                    

                    對于一些可能有害的字符,可以用如下的幾種方式進行保護:

                    可以嘗試在php.ini中開啟magic_quotes_gpc,這樣對于所有由用戶GET、POST、COOKIE中傳入的特殊字符都會轉義。

                    也可是使用addslashes()函數,但是開啟magic_quotes_gpc所造成的影響可能遠超過益處。

                    addslashes()也只對最常見的四個字符做了轉義:單引號、雙引號、反斜線、空字符。

                    同時為了使數據還原,需要使用stripslashes()函數,也可能破壞一些多字節的轉義。

                    推薦使用mysql_real_escape_string()函數,雖然只是用來設計轉義插入數據庫的數據,但是他能轉義更多的字符。

                    如:NULL、\x00、\n、\r、\、'、"和\x1a。使用用例:

                    #!php
                    <?php
                    $expected = array(
                        'carModel',
                        'year',
                        'bodyStyle'
                    );
                    foreach ($expected AS $key) {
                        if (!empty($_GET[$key])) {
                            ${$key} = mysql_real_escape_string($_GET[$key]);
                        }
                    }
                    ?>
                    

                    使用mysql_real_escape_string()函數也不是萬能的,轉義一些并非是要寫入mysql的數據庫的數據可能不會產生作用或者出現錯誤。

                    可以根據自己的實際需要,自己使用str_replace()函數寫一個針對特殊字符的轉義。

                    對于文件的路徑與名稱的過濾

                    文件名中不能包含二進制數據,否則可能引起問題。

                    一些系統允許Unicode多字節編碼的文件名,但是盡量避免,應當使用ASCII的字符。

                    雖然Unix系統幾乎可以在文件名設定中使用任何符號,但是應當盡量使用 - 和 _ 避免使用其他字符。

                    同時需要限定文件名的長度。

                    php中的文件操作通常使用fopen()函數與file_get_contents()函數。

                    #!php
                    <?php
                    $applicationPath = '/home/www/myphp/code/';
                    $scriptname      = $_POST['scriptname'];
                    highlight_file($applicationPath . $scriptname);
                    ?>
                    

                    上面代碼的問題在于用戶POST輸入的scriptname沒有做任何過濾,如果用戶輸入../../../../etc/passwd,則有可能讀取到系統的passwd文件。

                    #!php
                    <?php
                    $uri = $_POST['uri'];
                    if (strpos($uri, '..'))
                        exit('That is not a valid URI.');
                    $importedData = file_get_contents($uri);
                    

                    如果發現 .. 字符串就不執行會不會出現問題呢?如果前面并沒有路徑限制的話,仍然會出現問題:

                    使用file協議,當用戶輸入file:///etc/passwd的時候,會把passwd的內容帶入$importedData變量中。

                    防止SQL注入


                    SQL注入是如何產生的:

                    1、接收一個由用戶提交的變量,假設變量為$variety:

                    #!php
                    $variety = $_POST['variety'];
                    

                    2、接收的變量帶入構造一個數據庫查詢語句:

                    #!php
                    $query = "SELECT * FROM wines WHERE variety='$variety'";
                    

                    3、把構造好的語句提交給MySQL服務器查詢,MySQL返回查詢結果。

                    當由用戶輸入lagrein' or 1=1#時,產生的結果將會完全不同。

                    防止SQL注入的幾種方式:

                    檢查用戶輸入的類型,當用戶輸入的為數字時可以使用如下方式:

                    使用is_int()函數(或is_integer()或is_long()函數)

                    使用gettype()函數

                    使用intval()函數

                    使用settype()函數

                    檢查用戶輸入字符串的長度使用strlen()函數。

                    檢查日期或時間是否是有效的,可以使用strtotime()函數

                    對于一個已經存在的程序來說,可以寫一個通用函數來過濾:

                    #!php
                    function safe($string)
                    {
                        return "'" . mysql_real_escape_string($string) . "'";
                    }
                    

                    調用方式:

                    #!php
                    $variety = safe($_POST['variety']);
                    $query   = "SELECT * FROM wines WHERE variety=" . $variety;
                    

                    對于一個剛開始寫的程序,應當設計的更安全一些,PHP5中,增加了MySQL支持,提供了mysqli擴展:

                    PHP手冊地址:http://php.net/mysqli

                    #!php
                    <?php
                    // retrieve the user's input
                    $animalName = $_POST['animalName'];
                    // connect to the database
                    $connect    = mysqli_connect('localhost', 'username', 'password', 'database');
                    if (!$connect)
                        exit('connection failed:  ' . mysqli_connect_error());
                    // create a query statement resource
                    $stmt = mysqli_prepare($connect, "SELECT intelligence FROM animals WHERE name = ?");
                    if ($stmt) {
                        // bind the substitution to the statement
                        mysqli_stmt_bind_param($stmt, "s", $animalName);
                        // execute the statement
                        mysqli_stmt_execute($stmt);
                        // retrieve the result...
                        mysqli_stmt_bind_result($stmt, $intelligence);
                        // ...and display it
                        if (mysqli_stmt_fetch($stmt)) {
                            print "A $animalName has $intelligence intelligence.\n";
                        } else {
                            print 'Sorry, no records found.';
                        }
                        // clean up statement resource
                        mysqli_stmt_close($stmt);
                    }
                    mysqli_close($connect);
                    ?>
                    

                    mysqli擴展提供了所有的查詢功能。

                    mysqli擴展也提供了面向對象的版本:

                    #!php
                    <?php
                    $animalName = $_POST['animalName'];
                    $mysqli     = new mysqli('localhost', 'username', 'password', 'database');
                    if (!$mysqli)
                        exit('connection failed:  ' . mysqli_connect_error());
                    $stmt = $mysqli->prepare("SELECT intelligence FROM animals WHERE name = ?");
                    if ($stmt) {
                        $stmt->bind_param("s", $animalName);
                        $stmt->execute();
                        $stmt->bind_result($intelligence);
                        if ($stmt->fetch()) {
                            print "A $animalName has $intelligence intelligence.\n";
                        } else {
                            print 'Sorry, no records found.';
                        }
                        $stmt->close();
                    }
                    $mysqli->close();
                    ?>
                    

                    防止XSS攻擊


                    xss攻擊一個常用的方法就是注入HTML元素執行js腳本,php中已經內置了一些防御的函數(如htmlentities或者htmlspecialchars):

                    #!php
                    <?php
                    function safe($value)
                    {
                        htmlentities($value, ENT_QUOTES, 'utf-8');
                        // other processing
                        return $value;
                    }
                    // retrieve $title and $message from user input
                    $title   = $_POST['title'];
                    $message = $_POST['message'];
                    // and display them safely
                    print '<h1>' . safe($title) . '</h1>
                           <p>' . safe($message) . '</p>';
                    ?>
                    

                    過濾用戶提交的URL

                    如果允許用戶輸入一個URL用來調用一個圖片或者鏈接,你需要保證他不傳入javascript:或者vbscript:或data:等非http協議。

                    可以使用php的內置函數parse_url()函數來分割URL,然后做判斷。

                    設置允許信任的域:

                    #!php
                    <?php
                    $trustedHosts      = array(
                        'example.com',
                        'another.example.com'
                    );
                    $trustedHostsCount = count($trustedHosts);
                    function safeURI($value)
                    {
                        $uriParts = parse_url($value);
                        for ($i = 0; $i < $trustedHostsCount; $i++) {
                            if ($uriParts['host'] === $trustedHosts[$i]) {
                                return $value;
                            }
                        }
                        $value .= ' [' . $uriParts['host'] . ']';
                        return $value;
                    }
                    // retrieve $uri from user input
                    $uri = $_POST['uri'];
                    // and display it safely
                    echo safeURI($uri);
                    ?>
                    

                    防止遠程執行


                    遠程執行通常是使用了php代碼執行如eval()函數,或者是調用了命令執行如exec(),passthru(),proc_open(),shell_exec(),system()或popen()。

                    注入php代碼:

                    php為開發者提供了非常多的方法可以來調用允許php腳本,我們就需要注意對用戶可控的數據進行過濾。

                    調用的幾種方式:

                    include()和require()函數,eval()函數,preg_replace()采用e模式調用,編寫腳本模板。

                    #!php
                    <?php
                    print Hello . world;
                    ?>
                    

                    上面代碼將會輸出Helloworld,php在解析的時候會檢查是否存在一個名為Hello的函數。

                    如果沒有找到的話,他會自己創建一個并把它的名字作為它的值,world也是一樣。

                    上傳文件中嵌入php代碼:

                    攻擊者可以上傳一個看似很普通的圖片,PDF等,但是實際上呢?

                    linux下可以使用如下命令插入php代碼進入圖片中:

                    $ echo '<?php phpinfo();?>' >> locked.gif
                    

                    代碼插入到了locked.gif圖片中。

                    并且此時用file命令查看文件格式仍為圖片:

                    $ file -i locked.giflocked.gif: image/gif
                    

                    任何的圖像編輯或圖像處理的程序包括PHP的getimagesize()函數,都會認為它是一個GIF圖像。

                    但是當你使用cat命令查看圖片時,可以看到圖片末尾的

                    當把圖片的后綴改為php或者已php的方式解析時,插入的phpinfo()函數便會執行。

                    Shell命令執行

                    PHP提供了一些可以直接執行系統命令的函數,如exec()函數或者 `(反引號)。

                    PHP的安全模式會提供一些保護,但是也有一些方式可以繞過安全模式:

                    1、上傳一個Perl腳本,或者Python或Ruby等,服務器支持的環境,來執行其他語言的腳本可繞過PHP的安全模式。

                    2、利用系統的緩沖溢出漏洞,繞過安全模式。

                    下表列出了跟Shell相關的一些字符:

                    名稱 字符 ASCII 16進制 URL編碼 HTML編碼
                    換行 10 \x0a %0a &#10
                    感嘆號 ! 33 \x21 %21 &#33
                    雙引號 " 34 \x22 %22 &#34或&quot
                    美元符號 $ 36 \x24 %24 &#36
                    連接符 & 38 \x26 %26 &#38或&#amp
                    單引號 ' 39 \x27 %27 &#39
                    左括號 ( 40 \x28 %28 &#40
                    右括號 ) 41 \x29 %29 &#41
                    星號 * 42 \x2a %2a &#42
                    連字符號 - 45 \x2d %2d &#45
                    分號 ; 59 \x3b %3b &#59
                    左尖括號 < 60 \x3c %3c &#60
                    右尖括號 > 62 \x3e %3e &#62
                    問號 ? 63 \x3f %3f &#63
                    左方括號 [ 91 \x5b %5b &#91
                    反斜線 \ 92 \x5c %5c &#92
                    右方括號 ] 93 \x5d %5d &#93
                    插入符 ^ 94 \x5e %5e &#94
                    反引號 ` 96 \x60 %60 &#96
                    左花括號 { 123 \x7b %7b &#123
                    管道符 | 124 \x7c %7c &#124
                    右花括號 } 125 \x7d %7d &#125
                    波浪號 ~ 126 \x7e %7e &#126

                    如下PHP腳本:

                    #!php
                    <?php
                    // get the word count of the requested file
                    $filename = $_GET['filename'];
                    $command  = "/usr/bin/wc $filename";
                    $words    = shell_exec($command);
                    print "$filename contains $words words.";
                    ?>
                    

                    用戶可以輸入如下的URL來攻擊讀取passwd文件:

                    wordcount.php?filename=%2Fdev%2Fnull%20%7C%20cat%20-%20%2Fetc%2Fpasswd
                    

                    字符串拼接之后,將會執行 /usr/bin/wc /dev/null | cat - /etc/passwd這條命令

                    如果能夠不適用命令執行函數與eval()函數,可以在php.ini中禁止:disable_functions = "eval,phpinfo"

                    PHP中還有一個preg_replace()函數,可能引起代碼執行漏洞。

                    mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit ] )
                    

                    在 subject 中搜索 pattern 模式的匹配項并替換為 replacement 。如果指定了 limit ,則僅替換 limit 個匹配。

                    如果省略 limit 或者其值為 -1,則所有的匹配項都會被替換。

                    特別注意:

                    /e 修正符使 preg_replace() 將 replacement 參數當作 PHP 代碼(在適當的逆向引用替換完之后)。

                    提示:要確保 replacement 構成一個合法的 PHP 代碼字符串,否則 PHP 會在報告在包含 preg_replace() 的行中出現語法解析錯誤。

                    #!php
                    <?php
                    function test($str)
                    {
                        //......
                        //......
                        return $str;
                    }
                    echo preg_replace("/\s*\[p hp language=""](.+?)\[\/php\]\s*/ies", 'test("\1")', $_GET["h"]);
                    ?>
                    

                    當用戶輸入

                    ?h=[p hp]phpinfo()[/php]
                    

                    經過正則匹配后, replacement 參數變為'test("phpinfo()")',

                    此時phpinfo僅是被當做一個字符串參數了。

                    但是當我們提交

                    ?h=[p hp]{${phpinfo()}}[/php]
                    

                    時,phpinfo()就會被執行。

                    在php中,雙引號里面如果包含有變量,php解釋器會將其替換為變量解釋后的結果;單引號中的變量不會被處理。

                    注意:雙引號中的函數不會被執行和替換。

                    在這里我們需要通過{${}}構造出了一個特殊的變量,'test("{${phpinfo()}}")',達到讓函數被執行的效果 ${phpinfo()} 會被解釋執行。

                      <pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
                      <pre id="vvttv"></pre>

                        <p id="vvttv"></p>

                            <p id="vvttv"></p>

                                  <p id="vvttv"></p>

                                  <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                                    <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                                      <p id="vvttv"></p>

                                      这里只有精品视频