<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          用 Python 告訴你什么是計(jì)時(shí)攻擊

          共 4504字,需瀏覽 10分鐘

           ·

          2021-10-30 19:17

          用戶密碼登陸是一個(gè)系統(tǒng)常見(jiàn)的鑒權(quán)方法,如果處理不當(dāng)就會(huì)隱藏計(jì)時(shí)攻擊漏洞。本文用 Python 告訴你什么是計(jì)時(shí)攻擊,如何進(jìn)行計(jì)時(shí)攻擊,以及怎么避免。

          什么是計(jì)時(shí)攻擊

          比如說(shuō)你驗(yàn)證密碼時(shí)是按照字符串一位一位的比較,如果某一位不匹配,就返回 False,這樣就中招了。因?yàn)檎_的密碼必然需要每一位都參與比較,這樣,攻擊者就統(tǒng)計(jì)不同長(zhǎng)度的輸入所消耗的時(shí)間,消耗時(shí)間最長(zhǎng)的,就是正確的密碼長(zhǎng)度。在這個(gè)密碼長(zhǎng)度之下,再逐位通過(guò)計(jì)時(shí)攻擊進(jìn)行破解,消耗時(shí)間較長(zhǎng)的那個(gè)字符就是正確的,時(shí)間復(fù)雜度也就是O(n*k),n 允許的字符數(shù)量,k 表示密碼長(zhǎng)度。

          用 Python 進(jìn)行計(jì)時(shí)攻擊

          比如說(shuō)你使用這樣的方法來(lái)驗(yàn)證用戶登陸:

          password_database?=?{"somenzz":?"subscribe?to?python?seven"}

          def?check_password(user,?guess):
          ????actual?=?password_database[user]
          ????if?len(guess)?!=?len(actual):
          ????????return?False
          ????
          ????#?逐個(gè)字符比較
          ????for?i?in?range(len(actual)):
          ????????if?guess[i]?!=?actual[i]:
          ????????????return?False
          ????return?True

          上面代碼的邏輯雖然清晰,卻存在計(jì)時(shí)攻擊漏洞,因?yàn)殚L(zhǎng)度不一樣就返回了,花費(fèi)的時(shí)間最少,當(dāng)長(zhǎng)度正確時(shí)需要逐個(gè)字符比較,花費(fèi)時(shí)間最長(zhǎng)。根據(jù)程序的執(zhí)行耗時(shí)可以爆破出正確的密碼長(zhǎng)度。

          比如說(shuō)窮舉 1-30 長(zhǎng)度的密碼,花費(fèi)時(shí)間最長(zhǎng)的那個(gè)一定是正確的密碼長(zhǎng)度,因此可以編寫下面的代碼來(lái)破解密碼長(zhǎng)度:

          import?string
          import?timeit
          import?numpy?as?np

          allowed_chars?=?string.ascii_lowercase?+?"?"

          def?random_str(size):
          ????return?''.join(random.choices(allowed_chars,?k=size))


          def?crack_length(user,?max_len=30,?verbose=False)?->?int:
          ????trials?=?2000
          ????times?=?np.empty(max_len)
          ????for?i?in?range(max_len):
          ????????i_time?=?timeit.repeat(stmt='check_password(user,?x)',
          ???????????????????????????????setup=f'user={user!r};x=random_str({i!r})',
          ???????????????????????????????globals=globals(),
          ???????????????????????????????number=trials,
          ???????????????????????????????repeat=10)
          ????????times[i]?=?min(i_time)

          ????if?verbose:
          ????????#?排序,取最大的前?5?個(gè)
          ????????most_likely_n?=?np.argsort(times)[::-1][:5]
          ????????print(most_likely_n,?times[most_likely_n]?/?times[most_likely_n[0]])

          ????#取最大
          ????most_likely?=?int(np.argmax(times))
          ????return?most_likely?

          ?def?main():
          ????user?=?"somenzz"
          ????length?=?crack_length(user,?verbose=True)
          ????print(f"密碼最可能的長(zhǎng)度是?{length}")???

          if?__name__?==?'__main__':
          ????main()

          執(zhí)行結(jié)果如下:

          有了長(zhǎng)度,就可以逐個(gè)字符破解了,先隨機(jī)一個(gè) 25 位長(zhǎng)度的字符串,記為 guess,然后從第一位開(kāi)始逐位爆破嘗試,如果正確,那花費(fèi)的時(shí)間肯定比之前的多,然后就更新 guess,就這樣可以爆破出全部的字符串,運(yùn)行期間如果通過(guò)了 check_password,那就返回結(jié)果,終止運(yùn)行,代碼如下:

          import?itertools
          import?string
          import?timeit
          import?numpy?as?np

          """
          將上面的代碼復(fù)制過(guò)來(lái)
          """

          def?crack_password(user,?length,?verbose=False):
          ????guess?=?random_str(length)
          ????print(f"{guess=}")
          ????counter?=?itertools.count()
          ????print(f"{counter?=}")
          ????trials?=?1000
          ????while?True:
          ????????i?=?next(counter)?%?length
          ????????for?c?in?allowed_chars:
          ????????????alt?=?guess[:i]?+?c?+?guess[i?+?1:]

          ????????????alt_time?=?timeit.repeat(stmt='check_password(user,?x)',
          ?????????????????????????????????????setup=f'user={user!r};x={alt!r}',
          ?????????????????????????????????????globals=globals(),
          ?????????????????????????????????????number=trials,
          ?????????????????????????????????????repeat=10)
          ????????????guess_time?=?timeit.repeat(stmt='check_password(user,?x)',
          ???????????????????????????????????????setup=f'user={user!r};x={guess!r}',
          ???????????????????????????????????????globals=globals(),
          ???????????????????????????????????????number=trials,
          ???????????????????????????????????????repeat=10)

          ????????????if?check_password(user,?alt):
          ????????????????return?alt

          ????????????if?min(alt_time)?>?min(guess_time):
          ????????????????guess?=?alt
          ????????????????if?verbose:
          ????????????????????print(guess)

          ????????????????????
          def?main():
          ????user?=?"somenzz"
          ????length?=?crack_length(user,?verbose=True)
          ????print(f"密碼最可能的長(zhǎng)度是?{length}")
          ????input("按回車?yán)^續(xù)破解密碼...")
          ????password?=?crack_password(user,?length,?verbose=True)
          ????print(f"password?cracked:'{password}'")


          if?__name__?==?'__main__':
          ????main()

          運(yùn)行效果如下:

          crack_password?cost?40.3145?seconds

          考慮到都是小寫字母,時(shí)間復(fù)雜度也就是?O(n*k),n 字符的數(shù)量,k 表示密碼長(zhǎng)度,本案例中也就是?O(26*25), 在我的電腦上 40 秒就破解了,是不是很神奇?

          怎么解決計(jì)時(shí)攻擊?

          文章開(kāi)頭提到的那種驗(yàn)證字符串是否相等的寫法可以說(shuō)很常見(jiàn),如果某一位長(zhǎng)度不等就沒(méi)必要繼續(xù)向右判斷了,直接返回即可,還可以提升一點(diǎn)性能,但卻會(huì)造成計(jì)時(shí)攻擊,作為程序員的你,此時(shí)是否覺(jué)得有點(diǎn)懵呢?寫代碼還真是一點(diǎn)點(diǎn)都不能松懈啊。

          其實(shí),安全永遠(yuǎn)是高于性能的,一個(gè)不安全的系統(tǒng),在高的性能恐怕也沒(méi)人敢用。

          那字符串比較最安全的辦法是什么呢?那就是把所有的位都比較一遍,可以參考 Django 判斷字符串相等的源碼:

          def?constant_time_compare(val1,?val2):
          ????"""
          ????Returns?True?if?the?two?strings?are?equal,?False?otherwise.
          ????The?time?taken?is?independent?of?the?number?of?characters?that?match.
          ????For?the?sake?of?simplicity,?this?function?executes?in?constant?time?only
          ????when?the?two?strings?have?the?same?length.?It?short-circuits?when?they
          ????have?different?lengths.
          ????"""

          ????if?len(val1)?!=?len(val2):
          ????????return?False
          ????result?=?0
          ????for?x,?y?in?zip(val1,?val2):
          ????????result?|=?ord(x)?^?ord(y)
          ????return?result?==?0

          不過(guò),上述代碼你仍然可以通過(guò)計(jì)時(shí)爆破出長(zhǎng)度,卻不可能通過(guò)統(tǒng)計(jì)時(shí)間爆破出具體的密碼。

          如果你不想被爆破出密碼的長(zhǎng)度,可以將判斷長(zhǎng)度的邏輯刪除,然后補(bǔ)全字符串的長(zhǎng)度,讓兩個(gè)字符串的長(zhǎng)度始終相等即可。

          最后的話

          本文分享了什么是計(jì)時(shí)攻擊,用 Python 演示了如何通過(guò)計(jì)時(shí)攻擊破解密碼長(zhǎng)度及破解最終的密碼,最后分享了如何解決計(jì)時(shí)攻擊漏洞。如果有幫助的話,還請(qǐng)點(diǎn)贊、關(guān)注、轉(zhuǎn)發(fā),感謝老鐵的閱讀。

          留言討論


          瀏覽 42
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  麻豆美女裸身18禁www | 苍井空一区二区三区四区五区 | 狠久久 | 首页操逼网 | 黄色一级毛 |