Open komuw opened 2 years ago
Do note that some pple may be using password managers and thus are very fast in filling out a form, especially short forms like login screens.
Note that bots can still circumvent this by changing the timestamp(before submission) to add a delay. However according the following comment, they rarely do; and even if they were to do so, u can use a crypto nonce:
I'm already using this timestamp technique on my website and so far no bot operator has bothered
trying to work around this. However even if some bot operator were to specifically target a
website using this technique and try to decrease the timestamp,
I believe you could still force a bot to wait by just changing the website to use something
like a cryptographic nonce that includes a timestamp instead of just a simple timestamp that
can be understood easily.
https://news.ycombinator.com/item?id=32347438
If we ever do this, we should not use 4seconds, it is too large. We need to maybe use milliseconds in order not to block legitimate users who are too fast or use password managers.
package main
import (
cryptoRand "crypto/rand"
"fmt"
mathRand "math/rand"
"time"
"golang.org/x/exp/slices"
)
// Most of the code here is insipired by(or taken from):
// (a) https://github.com/gorilla/csrf whose license(BSD 3-Clause "New") can be found here: https://github.com/gorilla/csrf/blob/v1.7.1/LICENSE
//
// var tokenLength int
// func init() {
// mathRand.Seed(time.Now().UTC().UnixNano())
// tokenLength = mathRand.Intn(20) + 12 // plus 12 to guarantee that we do not get zero, or a vert small number
// }
var tokenLength = 15 // equal to len of bytes generated by time.MarshalBinary()
func generateRandomBytes(n int) []byte {
b := make([]byte, n)
if _, err := cryptoRand.Read(b); err != nil {
b = make([]byte, n)
mathRand.Seed(time.Now().UTC().UnixNano())
_, _ = mathRand.Read(b)
}
return b
}
func mask(realToken []byte) []byte {
otp := generateRandomBytes(len(realToken))
// XOR the OTP with the real token to generate a masked token. Append the
// OTP to the front of the masked token to allow unmasking in the subsequent
// request.
return append(otp, xorToken(otp, realToken)...)
}
// xorToken XORs tokens ([]byte) to provide unique-per-request CSRF tokens. It
// will return a masked token if the base token is XOR'ed with a one-time-pad.
// An unmasked token will be returned if a masked token is XOR'ed with the
// one-time-pad used to mask it.
func xorToken(a, b []byte) []byte {
n := len(a)
if len(b) < n {
n = len(b)
}
res := make([]byte, n)
for i := 0; i < n; i++ {
res[i] = a[i] ^ b[i]
}
return res
}
// unmask splits the issued token (one-time-pad + masked token) and returns the
// unmasked request token for comparison.
func unmask(issuedToken []byte) []byte {
// Issued tokens are always masked and combined with the pad.
if len(issuedToken) != tokenLength*2 {
return nil
}
// We now know the length of the byte slice.
otp := issuedToken[tokenLength:]
masked := issuedToken[:tokenLength]
// Unmask the token by XOR'ing it against the OTP used to mask it.
return xorToken(otp, masked)
}
func main() {
realTimeToken, timeThen := getTime()
fmt.Println("realTimeToken: ", realTimeToken, len(realTimeToken))
fmt.Println("timeThen: ", timeThen)
maskedTimeToken := mask(realTimeToken)
fmt.Println("maskedTimeToken: ", maskedTimeToken)
unmaskedTimeToken := unmask(maskedTimeToken)
fmt.Println("unmaskedTimeToken: ", unmaskedTimeToken)
if !slices.Equal(realTimeToken, unmaskedTimeToken) {
panic("real != unmasked")
}
time.Sleep(45 * time.Second)
timeAfter := time.Now()
e := timeAfter.UnmarshalBinary(unmaskedTimeToken)
if e != nil {
panic(e)
}
fmt.Println("timeAfter: ", timeAfter)
if !timeAfter.Equal(timeThen) {
panic("time are not equal")
}
}
func getTime() ([]byte, time.Time) {
now := time.Now().UTC()
b, err := now.MarshalBinary()
if err != nil {
panic("unable to time.MarshalBinary")
}
return b, now
}
or do this instead; https://news.ycombinator.com/item?id=35272685
for(i=0; i<INT_MAX; i++) {
output = hash(concat(server_provided_data, i));
if(check_output(output))
break;
One way to do that is to;
Maybe integrate it with the csrf middleware?