PHP安全之道學習筆記2:編碼安全指南

編碼安全指南

編程本身就應該是一門藝術,而安全編程更是一種在刀尖上舞蹈的藝術,不僅要小心腳下的鋒利寒刃,更要小心來自網絡黑客或攻擊者的狂轟亂炸。
– by code artist

  • 1.hash比較的缺陷
    經過試驗發現,當Hash值以”0e”開頭且後面都為数字,當和数字進行比較的時候總會被判斷和0相等

例如:

var_dump(‘0e1327544’ == 0); // bool(true)

當密碼被md5計算后,可能會以”0e”開頭,下面這個例子可以繞過密碼驗證。
經過我的驗證PHP 7.1.x后沒有這種問題。

<?php
    $password_from_db = "0e23434";
    $password = "2323"; // 隨意的一個密碼。來自$_POST,即表單提交
    if ($password_from_db == md5($password)) {
        echo "login success!";
    } else {
        echo "login fails";
    }

更安全的hash比較:
可以使用內置函數hash_equals()來比較hash值。(PHP版本必須是5.6及其以上)

 if (hash_equals($password_from_db, md5($password)) {
     .....// other logic
 }
  • 2.bool比較的缺陷

json_decode和unserialize函數可能將部分結構解析成bool值,造成一些比較上的缺陷。

先舉例json_decode的案例:

<?php
$str = '{"user":true, "pass": true}';

$data = json_decode($str, true);

if ($data['user'] == 'root' && $data['pass'] == 'pass') {
    echo "login success\r\n";
} else {
    echo "login fails\r\n";
}

執行結果為:login success
這樣利用bool比較的漏洞就繞過了登錄或者授權驗證。

unserialize過程相逆,結果類似,也會出現安全問題。

正確的做法還是使用”===”來進行比較,這不光是php,包括一些其他腳本語言或者靜態語言,都請嚴謹地使用全等於符號進行比較。

  • 3.數值比較

PHP雖然是弱類型語言,但是數據類型也有數值範圍。對於整型而言,最大值為PHP_INT_MAX(即9223372036854775807)
攻擊者可以利用最大值越界,繞過一些驗證,如登錄、賬號充值等等。

舉例:

$a = 9223372036854775807;
$b = 9223372036854775827;
var_dump($a === $b); // bool(true)
var_dump($a % 100); // int(0)

由此,可見全等號(===)也不是萬能的,具體場景下要更小心。經驗證,PHP7.1.x后不會出現該問題,5.x的可能出現。

在實際業務邏輯裏面一定要注意判斷最大值問題,避免越界帶來的問題。

當使用超長浮點數變量的時候,PHP也會出錯。

<?php
$uid = 0.999999999999999999;
if ($uid == "1") {
    echo "search uid is 1 for data\r\n"; // 這裏PHP將$uid約等於1了,進入該判斷條件里的邏輯
}

同理,2.999999999999也會被當成3,這就是超越浮點數精度造成的偏差。

解決辦法有很多,最簡單的就是用is_int()函數進行判斷,如果不是整型,則報錯或做錯誤處理。

  • 4.switch缺陷

當用case判斷数字的時候,switch會把參數轉換成int類型進行計算,代碼如下:

<?php
$num = "1FreePHP";
switch ($num) {
    case 0: echo "nothing";
        break;
    case 1: echo "1 hacker here!";
        break;
    case 2: echo "2 hackers here";
        break;
    default:
        echo "confused";
}

最後輸出:1 hacker here!

所以,請使用is_numeric()函數進行判斷,保證數據類型如預期的一致。

  • 5.數組缺陷。
    in_array()和array_search()函數在沒有使用嚴格模式的情況下會用鬆散比較,可能造成一些錯誤。
    例如:
<?php
$arr = [0, 2, 3, "4"];
var_dump(in_array('freephp', $arr)); // true
var_dump(array_search('freephp', $arr)); // 0: 下標
var_dump(in_array('2freephp', $arr)); // true
var_dump(array_search('3freephp', $arr)); // 2: 下標

總的來說,PHP工程師對於這種弱類型語言的使用上要更加小心,雖然平時寫起業務來“短平快”,但安全編程也不要忘記,能用上hint的高版本PHP就進行標註清楚入參、出參,讓PHP代碼更加健壯。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

Spring Security之多次登錄失敗后賬戶鎖定功能的實現

在上一次寫的文章中,為大家說到了如何動態的從數據庫加載用戶、角色、權限信息,從而實現登錄驗證及授權。在實際的開發過程中,我們通常會有這樣的一個需求:當用戶多次登錄失敗的時候,我們應該將賬戶鎖定,等待一定的時間之後才能再次進行登錄操作。

一、基礎知識回顧

要實現多次登錄失敗賬戶鎖定的功能,我們需要先回顧一下基礎知識:

  • Spring Security 不需要我們自己實現登錄驗證邏輯,而是將用戶、角色、權限信息以實現UserDetails和UserDetailsService接口的方式告知Spring Security。具體的登錄驗證邏輯Spring Security 會幫助我們實現。
  • UserDetails接口中有一個方法叫做isAccountNonLocked()用於判斷賬號是否被鎖定,也就是說我們應該通過該方法對應的set方法setAccountNonLocked(false)告知Spring Security該登錄賬戶被鎖定。
  • 那麼應該在哪裡判斷賬號登錄失敗的次數並執行鎖定機制呢?當然是我們之前文章給大家介紹的《自定義登錄成功及失敗結果處理》的AuthenticationFailureHandler。

建議您先閱讀本文,如果您對本文的實現過程感到迷惑,建議您再翻看本號之前的相關內容。

二、實現多次登錄失敗鎖定的原理

一般來說實現這個需求,我們需要針對每一個用戶記錄登錄失敗的次數nLock和鎖定賬戶的到期時間releaseTime。具體你是把這2個信息存儲在mysql、還是文件中、還是redis中等等,完全取決於你對你所處的應用架構適用性的判斷。具體的實現邏輯無非就是:

  • 登陸失敗之後,從存儲中將nLock取出來加1。
  • 如果nLock大於登陸失敗閾值(比如3次),則將nLock=0,然後設置releaseTime為當前時間加上鎖定周期。通過setAccountNonLocked(false)告知Spring Security該登錄賬戶被鎖定。
  • 如果nLock小於等於1,則將nLock再次存起來。
  • 在一個合適的時機,將鎖定狀態重置為setAccountNonLocked(true)。

這是一種非常典型的實現方式,筆者向大家介紹一款非常有用的開源軟件叫做:ratelimitj。這個軟件的功能主要是為API訪問進行限流,也就是說可以通過制定規則限制API接口的訪問頻率。那恰好登錄驗證接口也是API的一種啊,我們正好也需要限制它在一定的時間內的訪問次數。

三、具體實現

首先需要將ratelimitj通過maven坐標引入到我們的應用裏面來。我們使用的是內存存儲的版本,還有redis存儲的版本,大家可以根據自己的應用情況選用。

        <dependency>
            <groupId>es.moki.ratelimitj</groupId>
            <artifactId>ratelimitj-inmemory</artifactId>
            <version>0.4.1</version>
        </dependency>

之後通過繼承SimpleUrlAuthenticationFailureHandler ,實現onAuthenticationFailure方法。該實現是針對登錄失敗的結果的處理,在我們之前的文章中已經講過。

@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    UserDetailsManager userDetailsManager;

    //規則定義:1小時之內5次機會,就觸發限流行為
    Set<RequestLimitRule> rules = 
            Collections.singleton(RequestLimitRule.of(1 * 60, TimeUnit.MINUTES,5)); 
    RequestRateLimiter limiter = new InMemorySlidingWindowRequestRateLimiter(rules);


    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response, 
                                        AuthenticationException exception) 
                                        throws IOException, ServletException {

         String userId = //從request或request.getSession中獲取登錄用戶名
         //計數器加1,並判斷該用戶是否已經到了觸發了鎖定規則
         boolean reachLimit = limiter.overLimitWhenIncremented(userId);

        if(reachLimit){ //如果觸發了鎖定規則,通過UserDetails告知Spring Security鎖定賬戶
               user.setAccountNonLocked(false);
               userDetailsManager.updateUser(user);
               SysUser user = (SysUser) userDetailsManager.loadUserByUsername(userId);
        }
        

        
        //此處省略通過response做json或html響應
    }
}
  • 核心實現注意看代碼中的註釋
  • 代碼中的SysUser為UserDetails的實現類,如果不知道如何實現請參考本號之前的文章
  • userDetailsManager被用於管理UserDetails信息,通過改變UserDetails改變Spring Security驗證行為。

四、重置鎖定狀態的時機

user.setAccountNonLocked(true);

重置鎖定狀態很簡單,就是上面的代碼。但是更重要的是如何選擇重置鎖定狀態的時機。筆者能想到幾種方案如下

  • 下一次登陸的時候,自定義過濾器,加在Spring Boot過濾器鏈最前端做鎖定狀態重置的判斷。
  • 當登錄賬戶被鎖定之後,之後用戶的每一次登錄都會拋出LockedException。我們完全可以通過Spring Boot的全局異常捕獲機制,在其中捕獲LockedException,並做鎖定狀態的判斷及重置行為。
  • 寫一個Spring 的定時器輪詢,當然這是最差的方案。

    期待您的關注

  • 向您推薦博主的系列文檔:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※高價收購3C產品,價格不怕你比較

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

3c收購,鏡頭 收購有可能以全新價回收嗎?

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

Head First設計模式——適配器和外觀模式,Head First設計模式——裝飾者模式

前言:為什麼要一次講解這兩個模式,說點騷話:因為比較簡單(*^_^*),其實是他們兩個有相似和有時候我們容易搞混概念。

講到這兩個設計模式與另外一個“裝飾者模式”也有相似,他們三個按照結構模式分類都屬於“結構性模式”,所有我們接下來就來看什麼是適配器模式和外觀模式。

另外裝飾模式可以看我的另一篇博文→

一、適配器模式

適配器對應到我們現實生活中的例子,最典型的就是插頭接口適配器,比如我們買的有些港版手機充電頭是圓形三角插頭,而大陸的三角電源插板插不進去港版的插頭。

這時候我們就會在某寶上買個轉接頭轉換一下,而這個轉接頭就是適配器,用它來適配港版手機充電頭讓他能夠插入到我們的電源插板裏面。

在設計模式中這個適配器是什麼,用程序如何表現,先讓我舉個栗子:我們有一隻鴨子,一隻雞,我們如何通過適配器轉換鴨和雞。

鴨子有很多種,我們定義一個鴨子的接口,然後以綠頭鴨為例。關於這個綠頭鴨在策略模式也有用到,可以看看我另一篇綠頭鴨如何攪動策略模式→

    public  interface Duck
    {
        //叫
        public void Quack();
        //飛
        public void Fly();
    }

    public class GreenDuck : Duck
    {
        public void Fly()
        {
            Console.WriteLine("綠頭鴨,飛");
        }

        public void Quack()
        {
            Console.WriteLine("綠頭鴨,呱呱叫");
        }
    }

  同樣我們定義一個雞的接口,和一隻母雞的類

    public  interface Chicken
    {
        //叫
        public void Gobble();
        //飛
        public void Fly();
    }

    public class Hen : Chicken
    {
       
        public void Gobble()
        {
            Console.WriteLine("母雞,咯咯叫");
        }

        public void Fly()
        {
            Console.WriteLine("母雞,飛");
        }

    }

  鴨子和母雞的叫聲不一樣,現在我們讓母雞來冒充鴨子,利用適配器模式如何做。 直接看代碼吧

    /// <summary>
    /// 母雞適配器
    /// 適配母雞讓它變成鴨子
    /// </summary>
    public class HenAdapter : Duck
    {
        Chicken chicken;
        public HenAdapter(Chicken chicken)
        {
            this.chicken = chicken;
        }
        public void Quack()
        {
            //調用母雞咯咯叫
            chicken.Gobble();
        }

        public void Fly()
        {
            //調用母雞飛
            chicken.Fly();
        }

    }

  測試母雞適配器

如上我們使用母雞適配器將母雞適配成了鴨子,鴨子也可以用適配器將鴨子適配成母雞,適配器模式定義:

適配器模式:將一個類的接口,裝換成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間。

與適配器看起來相似的裝飾者模式是包裝對象的行為或責任,裝飾者被包裝后可能會繼續被包裝,他們不裝換接口,而適配器則一定會進行接口的轉換。

適配的工作是將一個接口轉換成另外一個接口,雖然大多數適配器採取的例子都是讓一個適配器包裝一個被適配者,但是有時候我們需要讓一個適配器包裝多個被適配者。

而這實際又涉及到另外一個模式,就是外觀模式,我們常常將適配器模式和外觀模式混為一談,那接着就來講解外觀模式。

二、外觀模式

外觀模式以家庭影院為例,家庭影院有許多組件構成,比如:显示屏、DVD、音響、燈光等等。

當我們要看電影的時候要打開显示屏,打開DVD,打開音響,關閉燈光等一系列動作,將這些動作寫成類方法的調用

            Screen screen = new Screen();
            DVD dvd = new DVD();
            SoundEngineer sound = new SoundEngineer();
            Light light = new Light();

            screen.Down();
            dvd.PlayDVD();
            sound.TurnOn();
            light.TurnOff();

可以看到每次我們要使用就要調用一篇這些方法,如果要關閉呢,我們也需要調用一篇。而我們正需要的就是一個外觀:通過實現一個提供更合理的接口的外觀類。

還是看代碼吧

 public class HomeThreaterFacade
    {
        Screen screen;
        DVD dvd;
        SoundEngineer sound;
        Light light;

        public HomeThreaterFacade(Screen screen, DVD dvd, SoundEngineer sound, Light light)
        {
            this.screen = screen;
            this.dvd = dvd;
            this.sound = sound;
            this.light = light;
        }

        public void WatchMovie()
        {
            Console.WriteLine("開始播放電影......");
            screen.Down();
            dvd.PlayDVD();
            sound.TurnOn();
            light.TurnOff();
        }
    }

由於其他類比較簡單就是一個打印輸出,我就不列出來了,還有關閉方法同理也很簡單就實現了。

還是測試一下效果:

外觀模式定義

外觀模式:提供了一個統一的接口,用來訪問子系統中的一群接口。外觀定義了一個高層接口,讓子系統更容易使用。

外觀模式遵循了一個設計原則

最少知識原則:之和你的密友談話。

這個原則希望我們在設計中,不要讓太多的類耦合在一起,免得修改系統中一部分,會影響其他部分。而外觀模式讓用戶不用關心全部子系統組件,讓客戶變得簡單有彈性。我們可以在不影響客戶的情況下升級外觀模式里的組件,而客戶只有一個朋友,也就是外觀模式。

三、適配器模式與外觀模式區別

從上面例子我們也許會覺得適配器和外觀模式之間的差異在於:適配器包裝一個類,而外觀可以代表許多類

但是實際它們的本質和作用並不是在於包裝多少類,適配器模式將一個或多個接口變成客戶期望的一個接口,我們一般適配一個類,但是特殊需求也可以適配多個類來提供一個接口。類是地,一個外觀也可以只爭對一個複雜接口的類提供簡化接口。兩中模式的差異在於他們的意圖。適配器模式意圖是將接口裝換成不同接口,外觀的意圖是簡化接口。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益