From a33b79712f2ef546c162b1f94cf62b1e5e3f7234 Mon Sep 17 00:00:00 2001 From: Ambrose Chua Date: Sat, 27 Jul 2019 18:13:25 +0800 Subject: [PATCH] feat: Improved bypass --- Dockerfile | 1 + main.go | 487 +++++++++++++++++++++++++++-------------------------- 2 files changed, 247 insertions(+), 241 deletions(-) diff --git a/Dockerfile b/Dockerfile index ca00e66..2583685 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ RUN CGO_ENABLED=0 go build -ldflags "-s -w" FROM scratch +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=build /src/login /login COPY --from=build /src/.env /.env diff --git a/main.go b/main.go index 20db280..2756f46 100644 --- a/main.go +++ b/main.go @@ -1,29 +1,28 @@ package main import ( - "crypto/rand" - "crypto/tls" - "crypto/rsa" - "database/sql" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "math/big" - "net/http" - "net/url" - "os" - "strings" - "time" + "crypto/rand" + "crypto/rsa" + "database/sql" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "math/big" + "net/http" + "net/url" + "os" + "strings" + "time" - "github.com/joho/godotenv" - "github.com/julienschmidt/httprouter" - "github.com/dgrijalva/jwt-go" - "github.com/go-redis/redis" - "github.com/ttacon/libphonenumber" - _ "github.com/lib/pq" + "github.com/dgrijalva/jwt-go" + "github.com/go-redis/redis" + "github.com/joho/godotenv" + "github.com/julienschmidt/httprouter" + _ "github.com/lib/pq" + "github.com/ttacon/libphonenumber" ) var listen string @@ -44,63 +43,63 @@ var db *sql.DB var redisClient *redis.Client func main() { - // Load .env - err := godotenv.Load() - if err != nil { - log.Fatal("Error loading .env file") - } - listen = os.Getenv("LISTEN") - postgres = os.Getenv("POSTGRES") - redisHost = os.Getenv("REDIS") + // Load .env + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + listen = os.Getenv("LISTEN") + postgres = os.Getenv("POSTGRES") + redisHost = os.Getenv("REDIS") - ttl, err = time.ParseDuration(os.Getenv("TTL")) - if err != nil { - log.Fatal("Error parsing ttl") - } + ttl, err = time.ParseDuration(os.Getenv("TTL")) + if err != nil { + log.Fatal("Error parsing ttl") + } - messagingSID = os.Getenv("MESSAGING_SID") - twilioSID = os.Getenv("TWILIO_SID") - twilioToken = os.Getenv("TWILIO_TOKEN") + messagingSID = os.Getenv("MESSAGING_SID") + twilioSID = os.Getenv("TWILIO_SID") + twilioToken = os.Getenv("TWILIO_TOKEN") - dummyToken = "{\"userid\":\"dummy\",\"clientid\":\"dummy\"}" - coreURL = os.Getenv("CORE_URL") + dummyToken = "{\"userid\":\"dummy\",\"clientid\":\"dummy\"}" + coreURL = os.Getenv("CORE_URL") - // Load RSA private key - privateKeyBytes, err := ioutil.ReadFile("key") - if err != nil { - log.Fatal(err) - } + // Load RSA private key + privateKeyBytes, err := ioutil.ReadFile("key") + if err != nil { + log.Fatal(err) + } - privateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes) - if err != nil { - log.Fatal(err) - } + privateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes) + if err != nil { + log.Fatal(err) + } - // Postgres - log.Printf("connecting to postgres %s", postgres) - db, err = sql.Open("postgres", postgres) + // Postgres + log.Printf("connecting to postgres %s", postgres) + db, err = sql.Open("postgres", postgres) if err != nil { log.Fatal(err) } defer db.Close() - // Redis - redisClient = redis.NewClient(&redis.Options{ - Addr: redisHost, - Password: "", - DB: 1, - }) + // Redis + redisClient = redis.NewClient(&redis.Options{ + Addr: redisHost, + Password: "", + DB: 1, + }) - // Routes + // Routes router := httprouter.New() - router.POST("/login", Login); - router.POST("/init", InitRequest) - router.POST("/verify", VerifyCode) - router.POST("/register/:code/:nonce", CreateUser) + router.POST("/init", InitRequest) + router.POST("/init/bypass", InitRequestBypass) + router.POST("/verify", VerifyCode) + router.POST("/register/:code/:nonce", CreateUser) - // Start server - log.Printf("starting server on %s", listen) + // Start server + log.Printf("starting server on %s", listen) log.Fatal(http.ListenAndServe(listen, router)) } @@ -119,249 +118,255 @@ func RandomHex() (string, error) { } type InitRequestBody struct { - PhoneNumber string `json:"phone_number"` + PhoneNumber string `json:"phone_number"` } + func InitRequest(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - // Get request body - req := InitRequestBody{} - decoder := json.NewDecoder(r.Body) + // Get request body + req := InitRequestBody{} + decoder := json.NewDecoder(r.Body) err := decoder.Decode(&req) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - // Make sure phone number is legitimate - phone, err := ParsePhone(req.PhoneNumber) - if err != nil { - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - - // Generate OTP code - c, err := rand.Int(rand.Reader, big.NewInt(1000000)) - code := fmt.Sprintf("%06d", c) - - // Generate nonce - b := make([]byte, 16) - _, err = rand.Read(b) + // Make sure phone number is legitimate + phone, err := ParsePhone(req.PhoneNumber) if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - bytes := hex.EncodeToString(b) - // Set code-nonce pair in redis first - redisClient.Set(code + "nonce", bytes, ttl) - // Set code-phone_number pair - redisClient.Set(code + "phone", phone, ttl) + // Generate OTP code + c, err := rand.Int(rand.Reader, big.NewInt(1000000)) + code := fmt.Sprintf("%06d", c) - // Send SMS via Twilio - data := url.Values {} - data.Set("MessagingServiceSid", messagingSID) - data.Set("To", phone) - data.Set("Body", fmt.Sprintf("Your OTP for Beep is %s", code)) - - url := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", twilioSID) - twilioReq, err := http.NewRequest("POST", url, strings.NewReader(data.Encode())) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Generate nonce + b := make([]byte, 16) + _, err = rand.Read(b) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } - twilioReq.SetBasicAuth(twilioSID, twilioToken) - twilioReq.Header.Add("Content-Type", "application/x-www-form-urlencoded") + } + bytes := hex.EncodeToString(b) - // Twilio uses self-signed certs - transport := &http.Transport { - TLSClientConfig: &tls.Config{ InsecureSkipVerify: true }, - } - client := &http.Client{ Transport: transport } - resp, err := client.Do(twilioReq) + // Set code-nonce pair in redis first + redisClient.Set(code+"nonce", bytes, ttl) + // Set code-phone_number pair + redisClient.Set(code+"phone", phone, ttl) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Send SMS via Twilio + data := url.Values{} + data.Set("MessagingServiceSid", messagingSID) + data.Set("To", phone) + data.Set("Body", fmt.Sprintf("Your OTP for Beep is %s", code)) + + url := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", twilioSID) + twilioReq, err := http.NewRequest("POST", url, strings.NewReader(data.Encode())) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + } + twilioReq.SetBasicAuth(twilioSID, twilioToken) + twilioReq.Header.Add("Content-Type", "application/x-www-form-urlencoded") - // Return nonce - w.Write([]byte(bytes)) + client := &http.Client{} + resp, err := client.Do(twilioReq) + + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + // Return nonce + w.Write([]byte(bytes)) +} + +func InitRequestBypass(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + // Get request body + req := InitRequestBody{} + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&req) + if err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + // Make sure phone number is legitimate + phone, err := ParsePhone(req.PhoneNumber) + if err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + + // Generate nonce + b := make([]byte, 16) + _, err = rand.Read(b) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + bytes := hex.EncodeToString(b) + + code := "000000" + // Set code-nonce pair in redis first + redisClient.Set(code+"nonce", bytes, ttl) + // Set code-phone_number pair + redisClient.Set(code+"phone", phone, ttl) + + // Return nonce + w.Write([]byte(bytes)) } type VerifyRequestBody struct { - Code string `json:"code"` - Nonce string `json:"nonce"` - ClientId string `json:"clientid"` + Code string `json:"code"` + Nonce string `json:"nonce"` + ClientId string `json:"clientid"` } + func VerifyCode(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - // Get request body - req := VerifyRequestBody{} - decoder := json.NewDecoder(r.Body) + // Get request body + req := VerifyRequestBody{} + decoder := json.NewDecoder(r.Body) err := decoder.Decode(&req) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - // Get nonce - storedNonce, err := redisClient.Get(req.Code + "nonce").Result() - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Get nonce + storedNonce, err := redisClient.Get(req.Code + "nonce").Result() + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } + } - // Delete nonce - _, err = redisClient.Del(req.Code + "nonce").Result() - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Delete nonce + _, err = redisClient.Del(req.Code + "nonce").Result() + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } + } - // Check nonce - if req.Nonce != storedNonce { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + // Check nonce + if req.Nonce != storedNonce { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return - } + } - // Get stored phone number - phoneNumber, err := redisClient.Get(req.Code + "phone").Result() - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Get stored phone number + phoneNumber, err := redisClient.Get(req.Code + "phone").Result() + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } + } - // Delete stored phone number - _, err = redisClient.Del(req.Code + "phone").Result() - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Delete stored phone number + _, err = redisClient.Del(req.Code + "phone").Result() + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } + } - // Generate (potential) User ID - userHex, err := RandomHex() - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Generate (potential) User ID + userHex, err := RandomHex() + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } - userIDPotential := "u-" + userHex + } + userIDPotential := "u-" + userHex - // Check for existing user - var userID string - err = db.QueryRow(` + // Check for existing user + var userID string + err = db.QueryRow(` INSERT INTO "user" (id, first_name, last_name, phone_number) VALUES ($1, '', '', $2) ON CONFLICT(phone_number) DO UPDATE SET phone_number=EXCLUDED.phone_number RETURNING id `, userIDPotential, phoneNumber).Scan(&userID) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - log.Println(err) - return - } - - // Generate JWT - token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims { - "userid": userID, - "clientid": req.ClientId, - }) - - tokenString, err := token.SignedString(privateKey) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - - w.Write([]byte(tokenString)) -} - -type LoginData struct { - ID string `json:"userid"` - Client string `json:"clientid"` -} - -func Login(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - login := LoginData {} - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&login) if err != nil { - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + log.Println(err) return } - token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims { - "userid": login.ID, - "clientid": login.Client, - }) + // Generate JWT + token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ + "userid": userID, + "clientid": req.ClientId, + }) - tokenString, err := token.SignedString(privateKey) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + tokenString, err := token.SignedString(privateKey) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } - w.Write([]byte(tokenString)) + w.Write([]byte(tokenString)) } func CreateUser(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - code := p.ByName("code") - nonce := p.ByName("nonce") + code := p.ByName("code") + nonce := p.ByName("nonce") - // Get nonce - storedNonce, err := redisClient.Get(code + "nonce").Result() - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Get nonce + storedNonce, err := redisClient.Get(code + "nonce").Result() + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } + } - // Delete nonce - _, err = redisClient.Del(code + "nonce").Result() - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Delete nonce + _, err = redisClient.Del(code + "nonce").Result() + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } + } - // Check nonce - if nonce != storedNonce { - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + // Check nonce + if nonce != storedNonce { + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return - } + } - // Delete phone number - _, err = redisClient.Del(code + "phone").Result() - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + // Delete phone number + _, err = redisClient.Del(code + "phone").Result() + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return - } + } - proxyReq, err := http.NewRequest(r.Method, coreURL, r.Body) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + proxyReq, err := http.NewRequest(r.Method, coreURL, r.Body) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } - proxyReq.Header.Set("X-User-Claim", dummyToken) - for header, values := range r.Header { - for _, value := range values { - proxyReq.Header.Add(header, value) - } - } + proxyReq.Header.Set("X-User-Claim", dummyToken) + for header, values := range r.Header { + for _, value := range values { + proxyReq.Header.Add(header, value) + } + } - client := &http.Client{} - proxyRes, err := client.Do(proxyReq) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + client := &http.Client{} + proxyRes, err := client.Do(proxyReq) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } - for header, values := range proxyRes.Header { - for _, value := range values { - w.Header().Add(header, value) - } - } - io.Copy(w, proxyRes.Body) - proxyRes.Body.Close() + for header, values := range proxyRes.Header { + for _, value := range values { + w.Header().Add(header, value) + } + } + io.Copy(w, proxyRes.Body) + proxyRes.Body.Close() }