├── 9_evilarc ├── somefile.txt ├── .DS_Store ├── evil.zip └── main.go ├── .DS_Store ├── 7_ssrf ├── ssrf.jpg └── ssrf.go ├── 2_https ├── 04_pki.png ├── 02_https_enc.png ├── 01_http_vs_https.png ├── 03_alicebob_colors.png ├── http │ └── main.go └── https │ ├── main.go │ ├── localhost.csr │ ├── localhost.crt │ └── localhost.key ├── 10_csrf ├── 01_csrf │ ├── csrf.png │ └── csrf.go └── 02_csrf_token │ ├── README.md │ ├── token_hash.go │ ├── token_jwt.go │ ├── token_crypt.go │ └── csrf.go ├── 1_passwords ├── 01_pass │ ├── hashing.png │ ├── pass_bench_test.go │ └── pass.go ├── 03_salt_storing │ ├── hashing_salt.png │ └── salt.go └── 02_leaked_db │ └── db.md ├── .gitignore ├── 4_secrets └── main.go ├── 2_xss_sanitize ├── 02_sanitized │ └── xss_clean.go └── 01_xss │ └── xss.go ├── go.mod ├── 5_pprof └── main.go ├── 6_rce_deps └── main.go ├── 8_gzip └── main.go ├── 3_logs └── main.go ├── 0_sql_inject └── main.go └── go.sum /9_evilarc/somefile.txt: -------------------------------------------------------------------------------- 1 | 123123123 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/.DS_Store -------------------------------------------------------------------------------- /7_ssrf/ssrf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/7_ssrf/ssrf.jpg -------------------------------------------------------------------------------- /2_https/04_pki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/2_https/04_pki.png -------------------------------------------------------------------------------- /9_evilarc/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/9_evilarc/.DS_Store -------------------------------------------------------------------------------- /9_evilarc/evil.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/9_evilarc/evil.zip -------------------------------------------------------------------------------- /10_csrf/01_csrf/csrf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/10_csrf/01_csrf/csrf.png -------------------------------------------------------------------------------- /2_https/02_https_enc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/2_https/02_https_enc.png -------------------------------------------------------------------------------- /2_https/01_http_vs_https.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/2_https/01_http_vs_https.png -------------------------------------------------------------------------------- /1_passwords/01_pass/hashing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/1_passwords/01_pass/hashing.png -------------------------------------------------------------------------------- /2_https/03_alicebob_colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/2_https/03_alicebob_colors.png -------------------------------------------------------------------------------- /1_passwords/03_salt_storing/hashing_salt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skinass/go-sec/HEAD/1_passwords/03_salt_storing/hashing_salt.png -------------------------------------------------------------------------------- /10_csrf/02_csrf_token/README.md: -------------------------------------------------------------------------------- 1 | csrf атаки работают тллько на get и simple post запросы 2 | наличие prefly запросов для всех сложных запросов с недефолтными content type/http verb/headers 3 | CORS 4 | same site cookie 5 | -------------------------------------------------------------------------------- /1_passwords/02_leaked_db/db.md: -------------------------------------------------------------------------------- 1 | # DATABASE 2 | 3 | email - pass: 4 | k.kitsuragi@mail.ru - e10adc3949ba59abbe56e057f20f883e 5 | h.dubois@mail.ru - d8578edf8458ce06fbc5bb76a58c5ca4 6 | ... 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /2_https/http/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | http.HandleFunc("/whats_time", func(w http.ResponseWriter, r *http.Request) { 10 | w.Write([]byte(time.Now().String())) 11 | }) 12 | http.ListenAndServe(":80", nil) 13 | } 14 | 15 | // sudo tcpdump -A -i lo0 'port 80' 16 | // curl http://localhost:80/whats_time 17 | -------------------------------------------------------------------------------- /4_secrets/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | var authTOken = flag.String("auth_token", "", "token to check Auth-Token header") 10 | 11 | func main() { 12 | http.HandleFunc("/whats_time", func(w http.ResponseWriter, r *http.Request) { 13 | if r.Header.Get("Auth-Token") == *authTOken { 14 | w.Write([]byte(time.Now().String())) 15 | } 16 | w.Write([]byte("304")) 17 | }) 18 | 19 | http.ListenAndServe(":80", nil) 20 | } 21 | -------------------------------------------------------------------------------- /9_evilarc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/zip" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | zr, _ := zip.OpenReader("evil.zip") 12 | defer zr.Close() 13 | 14 | fmt.Printf("%+#v", zr.File[0]) 15 | for _, f := range zr.File { 16 | r, _ := f.Open() 17 | data, _ := ioutil.ReadAll(r) 18 | os.WriteFile(f.Name, data, 0644) 19 | } 20 | } 21 | 22 | // github.com/patrickhener/go-evilarc 23 | // go-evilarc -out evil.gz -depth 1 -platform unix somefile.txt 24 | -------------------------------------------------------------------------------- /2_xss_sanitize/02_sanitized/xss_clean.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/microcosm-cc/bluemonday" 7 | ) 8 | 9 | // frontend sanitizer https://github.com/cure53/DOMPurify 10 | 11 | func main() { 12 | sanitizer := bluemonday.UGCPolicy() 13 | 14 | comment := `Mail.ru` 15 | fmt.Printf("comment before sanitizing: %s\n", comment) 16 | 17 | comment = sanitizer.Sanitize(comment) 18 | fmt.Printf("comment after sanitizing: %s\n", comment) 19 | } 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/skinass/go-sec 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/a-h/hsts v0.0.0-20170713145656-509101faf0de 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/labstack/echo/v4 v4.7.2 9 | github.com/labstack/gommon v0.3.1 10 | github.com/microcosm-cc/bluemonday v1.0.19 11 | github.com/proullon/ramsql v0.0.0-20220702164129-ff9cf4501dd2 12 | github.com/skinass/go-sum v0.0.0-20220716224551-0911cc392ee5 13 | github.com/stretchr/testify v1.8.0 // indirect 14 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d 15 | ) 16 | -------------------------------------------------------------------------------- /5_pprof/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "net/http" 6 | _ "net/http/pprof" 7 | "time" 8 | ) 9 | 10 | var authTOken = flag.String("auth_token", "", "token to check Auth-Token header") 11 | 12 | func main() { 13 | http.HandleFunc("/whats_time", func(w http.ResponseWriter, r *http.Request) { 14 | if r.Header.Get("Auth-Token") == *authTOken { 15 | w.Write([]byte(time.Now().String())) 16 | } 17 | w.Write([]byte("304")) 18 | }) 19 | 20 | http.ListenAndServe(":80", nil) 21 | } 22 | 23 | // curl --output - localhost:80/debug/pprof/cmdline 24 | 25 | // use vault 26 | -------------------------------------------------------------------------------- /2_https/https/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/a-h/hsts" 8 | ) 9 | 10 | func main() { 11 | http.Handle("/whats_time", hsts.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | w.Write([]byte(time.Now().String())) 13 | }))) 14 | http.ListenAndServeTLS(":80", "localhost.crt", "localhost.key", nil) 15 | } 16 | 17 | // openssl req -new -subj "/C=RU/ST=Msk/CN=localhost" -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr 18 | // openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt 19 | // 20 | // sudo tcpdump -A -i lo0 'port 80' 21 | // curl -k https://localhost:80/whats_time 22 | -------------------------------------------------------------------------------- /6_rce_deps/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/skinass/go-sum" 9 | ) 10 | 11 | func main() { 12 | counter := sum.NewAdder() 13 | 14 | http.HandleFunc("/whats_time", func(w http.ResponseWriter, r *http.Request) { 15 | counter.Add(1) 16 | w.Write([]byte(time.Now().String())) 17 | }) 18 | 19 | go func() { 20 | for _ = range time.NewTicker(time.Second).C { 21 | fmt.Printf("wow! there is already %d hits!!!\n", counter.Sum()) 22 | } 23 | }() 24 | 25 | http.ListenAndServe(":80", nil) 26 | } 27 | 28 | // nc -l localhost 8081 29 | 30 | // https://github.com/aquasecurity/chain-bench/blob/main/docs/CIS-Software-Supply-Chain-Security-Guide-v1.0.pdf 31 | -------------------------------------------------------------------------------- /1_passwords/01_pass/pass_bench_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkMD5(b *testing.B) { 8 | for i := 0; i < b.N; i++ { 9 | PasswordMD5(plainPassword) 10 | } 11 | } 12 | 13 | func BenchmarkBcrypt(b *testing.B) { 14 | for i := 0; i < b.N; i++ { 15 | PasswordBcrypt(plainPassword) 16 | } 17 | } 18 | 19 | func BenchmarkPBKDF2(b *testing.B) { 20 | for i := 0; i < b.N; i++ { 21 | PasswordPBKDF2(plainPassword) 22 | } 23 | } 24 | 25 | func BenchmarkScrypt(b *testing.B) { 26 | for i := 0; i < b.N; i++ { 27 | PasswordScrypt(plainPassword) 28 | } 29 | } 30 | 31 | func BenchmarkArgon2(b *testing.B) { 32 | for i := 0; i < b.N; i++ { 33 | PasswordArgon2(plainPassword) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /8_gzip/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "fmt" 7 | "io/ioutil" 8 | ) 9 | 10 | func compress(data []byte) ([]byte, error) { 11 | var b bytes.Buffer 12 | w := gzip.NewWriter(&b) 13 | w.Write(data) 14 | w.Close() 15 | return b.Bytes(), nil 16 | } 17 | 18 | func decompress(data []byte) ([]byte, error) { 19 | var b *bytes.Buffer = bytes.NewBuffer(data) 20 | r, err := gzip.NewReader(b) 21 | if err != nil { 22 | return nil, err 23 | } 24 | res, err := ioutil.ReadAll(r) 25 | if err != nil { 26 | return nil, err 27 | } 28 | r.Close() 29 | return res, nil 30 | } 31 | 32 | func main() { 33 | data := bytes.Repeat([]byte("0"), 1024*1024) 34 | fmt.Printf("data len = %d bytes\n", len(data)) 35 | 36 | c, _ := compress(data) 37 | fmt.Printf("compressed data len = %d bytes\n", len(c)) 38 | fmt.Printf("compressed data = %+v\n", c) 39 | } 40 | -------------------------------------------------------------------------------- /2_https/https/localhost.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICdDCCAVwCAQAwLzELMAkGA1UEBhMCUlUxDDAKBgNVBAgMA01zazESMBAGA1UE 3 | AwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3KCx 4 | 2f65c2FGnMsL3tGidZjUz+DLS+lXs/rNcBhLjudT6z/JRoaymc9xT/4J9YZStsYK 5 | Ptpp/kGoxsLbNAI0pWVdQ0R0FNTn/AONC4ho67etA2Z571eVNt5gGXGKFnsa5NpL 6 | l48IWseF50I3XgoA4OlBLB1+8VpqQgeD0M5J9Zov2yo0pB1SP1rTjaZUG1Bf/orJ 7 | srCrO3hXq4tuUdvD9kYVjhmhZFICjnfJpMZ19z2I9oe4mscQsUc+dXxlp9SuP+Ii 8 | OiimtQyvH3Egq8fcfCfcu+pBdkuA3KSV7jUX4UkpdQtUzcDhntq4U2bJDMFiekJ9 9 | wBjaMNMtL6DCXi64qQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAMMLv/0afoYd 10 | NGqQPvwW1CnyjE2fHg7jsED3fdGLfZNtLnO6ahRi03KrSG58+bzusBNkCVKmDoxA 11 | TRRIUM1M2iSiBNgQgMkruiZBJeNd5CCgUStbN8cWRU5X6gqiRvw5/Z1ODFhfnYJp 12 | O9vghRMIUfNvf7fHg/3osz5kREaj8DaXt1pbyeJxEn5cXyrALy+2FD3ZTPS9toMm 13 | B2JARe7ziJ+pP5YyeWw7rdYVf1drnJIj7lYnrg7Rgy+SByRlCUYWR/Vgb1yquDwM 14 | J81ZSbSibGinOF7SdjzcKfHWQdr1C0GtLPFd62JQwoFhbpYLfERXHoAJVYHF/v+v 15 | 1sZrwnfQ89U= 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /1_passwords/03_salt_storing/salt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | // "math/rand" 6 | "crypto/rand" 7 | 8 | "fmt" 9 | 10 | "golang.org/x/crypto/argon2" 11 | ) 12 | 13 | func hashPass(salt []byte, plainPassword string) []byte { 14 | hashedPass := argon2.IDKey([]byte(plainPassword), []byte(salt), 1, 64*1024, 4, 32) 15 | return append(salt, hashedPass...) 16 | } 17 | 18 | func checkPass(passHash []byte, plainPassword string) bool { 19 | salt := passHash[0:8] 20 | userPassHash := hashPass(salt, plainPassword) 21 | return bytes.Equal(userPassHash, passHash) 22 | } 23 | 24 | func passExample() { 25 | pass := "love" 26 | 27 | salt := make([]byte, 8) 28 | rand.Read(salt) 29 | fmt.Printf("salt: %x\n", salt) 30 | 31 | hashedPass := hashPass(salt, pass) 32 | fmt.Printf("hashedPass: %x\n", hashedPass) 33 | 34 | passValid := checkPass(hashedPass, pass) 35 | fmt.Printf("passValid: %v\n", passValid) 36 | } 37 | 38 | func main() { 39 | for i := 0; i < 3; i++ { 40 | fmt.Println("\titeration", i) 41 | passExample() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /2_https/https/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC2jCCAcICCQCb59EsRUZ48zANBgkqhkiG9w0BAQUFADAvMQswCQYDVQQGEwJS 3 | VTEMMAoGA1UECAwDTXNrMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjIwNzEwMjAw 4 | NzAyWhcNMjMwNzEwMjAwNzAyWjAvMQswCQYDVQQGEwJSVTEMMAoGA1UECAwDTXNr 5 | MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 6 | AoIBAQDcoLHZ/rlzYUacywve0aJ1mNTP4MtL6Vez+s1wGEuO51PrP8lGhrKZz3FP 7 | /gn1hlK2xgo+2mn+QajGwts0AjSlZV1DRHQU1Of8A40LiGjrt60DZnnvV5U23mAZ 8 | cYoWexrk2kuXjwhax4XnQjdeCgDg6UEsHX7xWmpCB4PQzkn1mi/bKjSkHVI/WtON 9 | plQbUF/+ismysKs7eFeri25R28P2RhWOGaFkUgKOd8mkxnX3PYj2h7iaxxCxRz51 10 | fGWn1K4/4iI6KKa1DK8fcSCrx9x8J9y76kF2S4DcpJXuNRfhSSl1C1TNwOGe2rhT 11 | ZskMwWJ6Qn3AGNow0y0voMJeLripAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBADtP 12 | W67xd0OoQPshwRsCsLey1tEmjyFUe+pU2pBqoc7BiVavdxOtd0hJ4fvmRFLAViLI 13 | yQD/6xcspEs9VbjtEepuXHEkKmZEuvQtH6Q9xPqVNzuyHQ8/Ad7PU3CkyGSZwuCK 14 | DoTPc5E/CVLm43YpSzm+c4i3ovx/OJLEm5unfSBOPepc/lorHbaQi7vUrbiTtKEg 15 | /e+Q9TScSjBi/Ufmp8sVWNI/aYKaeuI1dGooNIedFxxfp0FMzWWgzxc5j5LpL9q6 16 | FEPlCuI+iVZFHxa6028D9QuJ4cc7ssa/uhgOgqB/Aj59ic5IwPBhMeDA2fvszQD2 17 | CdvzF8pwcUKbLAxJOFo= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /3_logs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | echo "github.com/labstack/echo/v4" 7 | "github.com/labstack/echo/v4/middleware" 8 | "github.com/labstack/gommon/log" 9 | ) 10 | 11 | func main() { 12 | e := echo.New() 13 | 14 | e.POST("/login", func(c echo.Context) error { 15 | c.String(500, "db error") 16 | return nil 17 | }) 18 | 19 | e.Use( 20 | middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ 21 | LogError: true, 22 | LogStatus: true, 23 | LogRequestID: true, 24 | LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { 25 | if v.Status < 400 { 26 | return nil 27 | } 28 | 29 | in, _ := c.FormParams() 30 | out := v.FormValues 31 | 32 | c.Logger().Printj(log.JSON{ 33 | "s": v.Status, 34 | "err": v.Error, 35 | "id": v.RequestID, 36 | "in": in, 37 | "out": out, 38 | }) 39 | return nil 40 | }, 41 | }), 42 | ) 43 | 44 | err := e.Start(":80") 45 | if err != nil { 46 | fmt.Printf("err server %v", err) 47 | } 48 | } 49 | 50 | // curl -X POST 'http://localhost/login' -d "login=a.sulaev&password=123" 51 | -------------------------------------------------------------------------------- /0_sql_inject/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | 10 | _ "github.com/proullon/ramsql/driver" 11 | ) 12 | 13 | func initDB() *sql.DB { 14 | db, err := sql.Open("ramsql", "TestLoadUserAddresses") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | db.Exec("CREATE TABLE user (id BIGSERIAL PRIMARY KEY, username TEXT, password TEXT);") 19 | db.Exec("INSERT INTO user (username, password) VALUES ('admin', 'root');") 20 | db.Exec("INSERT INTO user (username, password) VALUES ('sulaev', '123123');") 21 | db.Exec("INSERT INTO user (username, password) VALUES ('k.kitsuragi', 'revachol');") 22 | 23 | return db 24 | } 25 | 26 | func main() { 27 | db := initDB() 28 | 29 | http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { 30 | r.ParseForm() 31 | username := r.FormValue("username") 32 | password := r.FormValue("password") 33 | 34 | row := db.QueryRow("SELECT id FROM user WHERE username=? AND password=?", username, password) 35 | var id int 36 | if err := row.Scan(&id); err != nil { 37 | fmt.Println(err) 38 | w.Write([]byte("wrong credentials")) 39 | return 40 | } 41 | 42 | w.Write([]byte("ok. your id is " + strconv.Itoa(id))) 43 | }) 44 | 45 | http.ListenAndServe(":80", nil) 46 | } 47 | 48 | // http://localhost/login?username=sulaev&password=123123 49 | // http://localhost/login?username=admin&password="OR"1"="1 50 | -------------------------------------------------------------------------------- /1_passwords/01_pass/pass.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha1" 6 | "fmt" 7 | 8 | "golang.org/x/crypto/argon2" 9 | "golang.org/x/crypto/bcrypt" 10 | "golang.org/x/crypto/pbkdf2" 11 | "golang.org/x/crypto/scrypt" 12 | ) 13 | 14 | var ( 15 | plainPassword = []byte("qwerty") 16 | salt = []byte{0xd7, 0xc2, 0xf2, 0x51, 0xaa, 0x6a, 0x4e, 0x7b} 17 | ) 18 | 19 | func PasswordMD5(plainPassword []byte) []byte { 20 | tmp := md5.Sum(plainPassword) 21 | return tmp[:] 22 | } 23 | 24 | func PasswordBcrypt(plainPassword []byte) []byte { 25 | passBcrypt, _ := bcrypt.GenerateFromPassword(plainPassword, 10) 26 | return passBcrypt 27 | } 28 | 29 | func PasswordPBKDF2(plainPassword []byte) []byte { 30 | return pbkdf2.Key(plainPassword, salt, 4096, 32, sha1.New) 31 | } 32 | 33 | func PasswordScrypt(plainPassword []byte) []byte { 34 | passScrypt, _ := scrypt.Key(plainPassword, salt, 1<<15, 8, 1, 32) 35 | return passScrypt 36 | } 37 | 38 | func PasswordArgon2(plainPassword []byte) []byte { 39 | return argon2.IDKey(plainPassword, salt, 1, 64*1024, 4, 32) 40 | } 41 | 42 | func main() { 43 | fmt.Printf("PasswordMD5: %x\n", PasswordMD5(plainPassword)) 44 | fmt.Printf("PasswordBcrypt: %x\n", PasswordBcrypt(plainPassword)) 45 | fmt.Printf("PasswordPBKDF2: %x\n", PasswordPBKDF2(plainPassword)) 46 | fmt.Printf("PasswordScrypt: %x\n", PasswordScrypt(plainPassword)) 47 | fmt.Printf("PasswordArgon2: %x\n", PasswordArgon2(plainPassword)) 48 | } 49 | -------------------------------------------------------------------------------- /7_ssrf/ssrf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | serverSideServer := &http.Server{Addr: ":8081"} 12 | serverSideServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | fmt.Fprintf(w, "SECRET_CREDENTIALS") 14 | }) 15 | go serverSideServer.ListenAndServe() 16 | 17 | clientSideServer := &http.Server{Addr: ":8080"} 18 | clientSideServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 19 | urls, ok := r.URL.Query()["url"] 20 | if !ok || len(urls) != 1 { 21 | http.Error(w, "bad url param", 500) 22 | return 23 | } 24 | 25 | resp, err := http.Get(urls[0]) 26 | if err != nil { 27 | http.Error(w, err.Error(), 500) 28 | return 29 | } 30 | defer resp.Body.Close() 31 | 32 | bytes, err := ioutil.ReadAll(resp.Body) 33 | if err != nil { 34 | http.Error(w, err.Error(), 500) 35 | return 36 | } 37 | 38 | // Write out the hexdump of the bytes as plaintext. 39 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") 40 | fmt.Fprint(w, hex.Dump(bytes)) 41 | }) 42 | clientSideServer.ListenAndServe() 43 | } 44 | 45 | // http://localhost:8080?url=http://www.google.com 46 | // http://localhost:8080?url=http://localhost:8081 47 | 48 | // https://assets.ctfassets.net/ut4a3ciohj8i/S1QrG5aYDkUjpceDq4Xx7/5ad53a6a2139f21780e38eb6fe7b9448/Denis_Rybin_Zapros_ne_tuda.pdf (slide 41+) 49 | // best solution - one way proxy 50 | -------------------------------------------------------------------------------- /2_https/https/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcoLHZ/rlzYUac 3 | ywve0aJ1mNTP4MtL6Vez+s1wGEuO51PrP8lGhrKZz3FP/gn1hlK2xgo+2mn+QajG 4 | wts0AjSlZV1DRHQU1Of8A40LiGjrt60DZnnvV5U23mAZcYoWexrk2kuXjwhax4Xn 5 | QjdeCgDg6UEsHX7xWmpCB4PQzkn1mi/bKjSkHVI/WtONplQbUF/+ismysKs7eFer 6 | i25R28P2RhWOGaFkUgKOd8mkxnX3PYj2h7iaxxCxRz51fGWn1K4/4iI6KKa1DK8f 7 | cSCrx9x8J9y76kF2S4DcpJXuNRfhSSl1C1TNwOGe2rhTZskMwWJ6Qn3AGNow0y0v 8 | oMJeLripAgMBAAECggEAGlX4ROxaGMLA2hCGvzdI7D6tEI9l1Q7qkCpdAYd05X8J 9 | jmhTz15ezWtsvD63054QG48yQ+RwiJAv5GH4JpIo1KFv64qtTJtJgdSurnS/CXwo 10 | Y+KEpaRPxHfxAriB4kMZp73DDjldpvBwNHW8BDaveil9xRVURN0lF/Krsm4dd/qR 11 | 9ULw1IE2SHCIxiHGCB1qHWUWVerL/kBOv6eeMMkiBK/lr6MVonVj6BhqaMU0XoJW 12 | X+A+kbW+WrUW2ASogsZ0hgdmoixmUisqpuEvVusYg8NhPhS/Jzpr50ZrEcpHyuJQ 13 | miQeSbIFdtDWS0eUd+nKEbl1HwYNEZ3PHRsC4JoAfQKBgQD49S1ZXmuCbm1BZWpe 14 | mgZg6uYKBoxzzMeDKOtSAfiejccnWmVGgpVV3q8T0qbzThvwj6xRU59CqK1Tqc+X 15 | RDiAZ+hzEKeX2X+yRFcvnzMiyu9XDqtx1hnXnjmLX1zR0T81JZI2sJcnFFNSQ6cn 16 | lsNN8DIU7i+jSIG8Da2kECJyYwKBgQDi3l3INlh7bqnsY2Gednm7tEd2G9L/mof1 17 | M+mEAFMZ9e6KNm2YxyGugNjkYYhJw1HuuIQUyD3Tc3afZV0B+w7VkjXPLh38exjb 18 | JgtwDDTtkIAOg3UG7h+w+gOwAPmI8fZFc3qLH6wxsdISQGFtLzwXwx4X2FG7Ypye 19 | KeXEfXgQgwKBgQC7eK0gmihtPMiLMVprSKM7AkgeDpZfcTpgO0URKSjWBL+Ok9Sl 20 | xxm7YwgF7mAH4a7KdPZ2+3h3UtvKn5oSN64wi46Qb180yvUNAceBtelk7tpSfVp/ 21 | S5GEO8Lrf/E+ksp9NACprEr2g6jGzuklt7L0ksaz2jUHSUdgnBBA9mFqLwKBgCB6 22 | QOa2kPgqoXzLxV/QCiFK76gC17PrxWbe5aEWAs+Gm32ByTLD3xiCLYqUQ9R7d7E7 23 | DP2EpQUuVF8BPc1YyYjdg4KDUZuha6+DxMNnm/QeaZaqTwiM8lB9miWKHi7ypJTQ 24 | sUxVXmtGz5H5UXk1995W+mnjd9/hgBZMXS5B3/xNAoGAar1aVokaWKRK5OKhxzbQ 25 | l3BuGhJpUYoZFaPX7SxpkgeJjpP7AdX+YUeCxnFbTgAds6+PMfTFEGvV6LzDZr/X 26 | I7XEVZiKb8vwnc5XBq8jL2R+6OgFf81gpSFsFJLZjg5gbvYscmeCj/XQWFf4GtRT 27 | R3MP3rvQvFBshZIBN2y3AxI= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /2_xss_sanitize/01_xss/xss.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | // "html/template" 6 | "math/rand" 7 | "net/http" 8 | "text/template" 9 | ) 10 | 11 | var messages = []string{"Hello World"} 12 | 13 | var messagesTmpl = ` 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 | 22 |
23 | 24 | {{range .Messages}} 25 |
26 | 27 | 28 | 29 | {{.}} 30 |
31 | {{end}} 32 | ` 33 | 34 | func main() { 35 | tmpl := template.New("main") 36 | tmpl, _ = tmpl.Parse(messagesTmpl) 37 | 38 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 39 | tmpl.Execute(w, struct { 40 | Messages []string 41 | }{ 42 | Messages: messages, 43 | }) 44 | }) 45 | 46 | http.HandleFunc("/add_comment", func(w http.ResponseWriter, r *http.Request) { 47 | r.ParseForm() 48 | commentText := r.FormValue("comment") 49 | messages = append(messages, commentText) 50 | http.Redirect(w, r, "/", http.StatusFound) 51 | }) 52 | 53 | http.HandleFunc("/clear_comments", func(w http.ResponseWriter, r *http.Request) { 54 | messages = []string{} 55 | http.Redirect(w, r, "/", http.StatusFound) 56 | }) 57 | 58 | fmt.Println("starting server at :8080") 59 | http.ListenAndServe(":8080", nil) 60 | } 61 | 62 | func PanicOnErr(err error) { 63 | if err != nil { 64 | panic(err) 65 | } 66 | } 67 | 68 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 69 | 70 | func RandStringRunes(n int) string { 71 | b := make([]rune, n) 72 | for i := range b { 73 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 74 | } 75 | return string(b) 76 | } 77 | 78 | //

This is a heading

79 | -------------------------------------------------------------------------------- /10_csrf/02_csrf_token/token_hash.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "fmt" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // fbc1fd86ab53d52c3ffeb6529aea9676e14bc52b792414c32f5612b4eb2c9745:1567618546 14 | // JSv5M7FZ5iPHnHiLXR1QbhnMcdoY/wvEae4a76KrGBxeHruFb1S90d4GkwsoQQU4R1zqEdSa0KMGflriF2dHj5XWm4Zp6OBxLp6BJFUhqpQxEBEr5yl4sxEHadgssvVWfWtDKe0bENU= 15 | // JSv5M7FZ5iPHnHiLXR1QbhnMcDAU/wvEae4a76KrGBxeHruFb1S90d4GkwsoQQU4R1zqEdSa0KMGflriF2dHj5XWm4Zp6OBxLp6BJFUhqpQxEBEr5yl4sxEHadgssvVWfWtDKe0bENU= 16 | // JSv5M7FZ5iPHnHiLXR1QbhnMcNEF/wvEae4a76KrGBxeHruFb1S90d4GkwsoQQU4R1zqEdSa0KMGflriF2dHj5XWm4Zp6OBxLp6BJFUhqpQxEBEr5yl4sxEHadgssvVWfWtDKe0bENU= 17 | 18 | type HashToken struct { 19 | Secret []byte 20 | } 21 | 22 | func NewHMACHashToken(secret string) (*HashToken, error) { 23 | return &HashToken{Secret: []byte(secret)}, nil 24 | } 25 | 26 | // hash(s.ID+s.UserID+exp_time)+exp_time 27 | func (tk *HashToken) Create(s *Session, tokenExpTime int64) (string, error) { 28 | h := hmac.New(sha256.New, []byte(tk.Secret)) 29 | data := fmt.Sprintf("%s:%d:%d", s.ID, s.UserID, tokenExpTime) 30 | h.Write([]byte(data)) 31 | token := hex.EncodeToString(h.Sum(nil)) + ":" + strconv.FormatInt(tokenExpTime, 10) 32 | return token, nil 33 | } 34 | 35 | func (tk *HashToken) Check(s *Session, inputToken string) (bool, error) { 36 | tokenData := strings.Split(inputToken, ":") 37 | if len(tokenData) != 2 { 38 | return false, fmt.Errorf("bad token data") 39 | } 40 | 41 | tokenExp, err := strconv.ParseInt(tokenData[1], 10, 64) 42 | if err != nil { 43 | return false, fmt.Errorf("bad token time") 44 | } 45 | 46 | if tokenExp < time.Now().Unix() { 47 | return false, fmt.Errorf("token expired") 48 | } 49 | 50 | h := hmac.New(sha256.New, []byte(tk.Secret)) 51 | data := fmt.Sprintf("%s:%d:%d", s.ID, s.UserID, tokenExp) 52 | h.Write([]byte(data)) 53 | expectedMAC := h.Sum(nil) 54 | messageMAC, err := hex.DecodeString(tokenData[0]) 55 | if err != nil { 56 | return false, fmt.Errorf("cand hex decode token") 57 | } 58 | 59 | return hmac.Equal(messageMAC, expectedMAC), nil 60 | } 61 | -------------------------------------------------------------------------------- /10_csrf/02_csrf_token/token_jwt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | jwt "github.com/dgrijalva/jwt-go" 8 | ) 9 | 10 | type JwtToken struct { 11 | Secret []byte 12 | } 13 | 14 | func NewJwtToken(secret string) (*JwtToken, error) { 15 | return &JwtToken{Secret: []byte(secret)}, nil 16 | } 17 | 18 | type JwtCsrfClaims struct { 19 | SessionID string `json:"sid"` 20 | UserID uint32 `json:"uid"` 21 | jwt.StandardClaims 22 | } 23 | 24 | func (tk *JwtToken) Create(s *Session, tokenExpTime int64) (string, error) { 25 | data := JwtCsrfClaims{ 26 | SessionID: s.ID, 27 | UserID: s.UserID, 28 | StandardClaims: jwt.StandardClaims{ 29 | ExpiresAt: tokenExpTime, 30 | IssuedAt: time.Now().Unix(), 31 | }, 32 | } 33 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, data) 34 | return token.SignedString(tk.Secret) 35 | } 36 | 37 | func (tk *JwtToken) parseSecretGetter(token *jwt.Token) (interface{}, error) { 38 | method, ok := token.Method.(*jwt.SigningMethodHMAC) 39 | if !ok || method.Alg() != "HS256" { 40 | return nil, fmt.Errorf("bad sign method") 41 | } 42 | return tk.Secret, nil 43 | } 44 | 45 | func (tk *JwtToken) Check(s *Session, inputToken string) (bool, error) { 46 | payload := &JwtCsrfClaims{} 47 | _, err := jwt.ParseWithClaims(inputToken, payload, tk.parseSecretGetter) 48 | if err != nil { 49 | return false, fmt.Errorf("cant parse jwt token: %v", err) 50 | } 51 | if payload.Valid() != nil { 52 | return false, fmt.Errorf("invalid jwt token: %v", err) 53 | } 54 | return payload.SessionID == s.ID && payload.UserID == s.UserID, nil 55 | } 56 | 57 | /* 58 | func (tk *JwtToken) parseSecretGetterMultiKeys(token *jwt.Token) (interface{}, error) { 59 | method, ok := token.Method.(*jwt.SigningMethodHMAC) 60 | if !ok || method.Alg() != "HS256" { 61 | return nil, fmt.Errorf("bad sign method") 62 | } 63 | 64 | keys := []*Key{ 65 | &Key{Exp: 10, Secret: 1}, 66 | &Key{Exp: 20, Secret: 2}, 67 | &Key{Exp: 30, Secret: 3}, 68 | } 69 | 70 | payload, ok := token.Claims.(*JwtCsrfClaims) 71 | if !ok { 72 | return nil, err 73 | } 74 | secret := "" 75 | for _, key := range keys { 76 | if Key.Exp > payload.Exp { 77 | secret = key.Secret 78 | break 79 | } 80 | } 81 | if secret == "" { 82 | return nil, fmt.Errrof("no secret found") 83 | } 84 | 85 | return secret, nil 86 | } 87 | */ 88 | -------------------------------------------------------------------------------- /10_csrf/02_csrf_token/token_crypt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "encoding/base64" 8 | "encoding/json" 9 | "fmt" 10 | "io" 11 | // "strings" 12 | "time" 13 | ) 14 | 15 | type CryptToken struct { 16 | Secret []byte 17 | } 18 | 19 | type TokenData struct { 20 | SessionID string 21 | UserID uint32 22 | Exp int64 23 | } 24 | 25 | func NewAesCryptHashToken(secret string) (*CryptToken, error) { 26 | key := []byte(secret) 27 | _, err := aes.NewCipher(key) 28 | if err != nil { 29 | return nil, fmt.Errorf("cypher problem %v", err) 30 | } 31 | return &CryptToken{Secret: key}, nil 32 | } 33 | 34 | func (tk *CryptToken) Create(s *Session, tokenExpTime int64) (string, error) { 35 | block, err := aes.NewCipher(tk.Secret) 36 | if err != nil { 37 | return "", err 38 | } 39 | 40 | aesgcm, err := cipher.NewGCM(block) 41 | if err != nil { 42 | return "", err 43 | } 44 | 45 | nonce := make([]byte, aesgcm.NonceSize()) 46 | if _, err := io.ReadFull(rand.Reader, nonce); err != nil { 47 | return "", err 48 | } 49 | 50 | td := &TokenData{SessionID: s.ID, UserID: s.UserID, Exp: tokenExpTime} 51 | data, _ := json.Marshal(td) 52 | ciphertext := aesgcm.Seal(nil, nonce, data, nil) 53 | 54 | res := append([]byte(nil), nonce...) 55 | res = append(res, ciphertext...) 56 | 57 | token := base64.StdEncoding.EncodeToString(res) 58 | return token, nil 59 | } 60 | 61 | func (tk *CryptToken) Check(s *Session, inputToken string) (bool, error) { 62 | block, err := aes.NewCipher(tk.Secret) 63 | if err != nil { 64 | return false, err 65 | } 66 | aesgcm, err := cipher.NewGCM(block) 67 | if err != nil { 68 | return false, err 69 | } 70 | ciphertext, err := base64.StdEncoding.DecodeString(inputToken) 71 | if err != nil { 72 | return false, err 73 | } 74 | nonceSize := aesgcm.NonceSize() 75 | if len(ciphertext) < nonceSize { 76 | return false, fmt.Errorf("ciphertext too short") 77 | } 78 | 79 | nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] 80 | plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil) 81 | if err != nil { 82 | return false, fmt.Errorf("decrypt fail: %v", err) 83 | } 84 | 85 | td := TokenData{} 86 | err = json.Unmarshal(plaintext, &td) 87 | if err != nil { 88 | return false, fmt.Errorf("bad json: %v", err) 89 | } 90 | 91 | if td.Exp < time.Now().Unix() { 92 | return false, fmt.Errorf("token expired") 93 | } 94 | 95 | expected := TokenData{SessionID: s.ID, UserID: s.UserID} 96 | td.Exp = 0 97 | return td == expected, nil 98 | } 99 | -------------------------------------------------------------------------------- /10_csrf/01_csrf/csrf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | // "html/template" 6 | "fmt" 7 | "strconv" 8 | "text/template" // надо заменить text/template на html/template чтобы по-умоллчанию было правильное экранирование 9 | ) 10 | 11 | var cnt = 1 12 | 13 | type Msg struct { 14 | ID int 15 | Message string 16 | Rating int 17 | } 18 | 19 | var messages = map[int]*Msg{} 20 | 21 | var messagesTmpl = ` 22 | 23 | 38 | 39 | 40 |
41 |
42 |
43 | 44 |
45 |
46 | 47 | {{range $idx, $var := .Messages}} 48 |
49 | 50 | {{$var.Rating}} 51 | 52 |   53 | {{$var.Message}} 54 |
55 | {{end}} 56 | ` 57 | 58 | func main() { 59 | 60 | tmpl := template.New("main") 61 | tmpl, _ = tmpl.Parse(messagesTmpl) 62 | 63 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 64 | tmpl.Execute(w, struct { 65 | Messages map[int]*Msg 66 | }{ 67 | Messages: messages, 68 | }) 69 | }) 70 | 71 | http.HandleFunc("/comment", func(w http.ResponseWriter, r *http.Request) { 72 | r.ParseForm() 73 | commentText := r.FormValue("comment") 74 | id := cnt 75 | messages[id] = &Msg{ 76 | ID: id, 77 | Message: commentText, 78 | Rating: 0, 79 | } 80 | cnt++ 81 | http.Redirect(w, r, "/", http.StatusFound) 82 | }) 83 | 84 | http.HandleFunc("/rate", func(w http.ResponseWriter, r *http.Request) { 85 | w.Header().Set("Content-Type", "application/json") 86 | 87 | id, _ := strconv.Atoi(r.URL.Query().Get("id")) 88 | vote := r.URL.Query().Get("vote") 89 | 90 | if msg, ok := messages[id]; ok { 91 | if vote == "up" { 92 | msg.Rating++ 93 | } else if vote == "down" { 94 | msg.Rating-- 95 | } 96 | w.Write([]byte(fmt.Sprintf(`{"id":%d, "rating":%d}`, msg.ID, msg.Rating))) 97 | } else { 98 | w.Write([]byte(`{"id":0, "rating":0}`)) 99 | } 100 | }) 101 | 102 | http.HandleFunc("/clear_comments", func(w http.ResponseWriter, r *http.Request) { 103 | messages = map[int]*Msg{} 104 | http.Redirect(w, r, "/", http.StatusFound) 105 | }) 106 | 107 | fmt.Println("starting server at :8080") 108 | http.ListenAndServe(":8080", nil) 109 | } 110 | 111 | // 112 | -------------------------------------------------------------------------------- /10_csrf/02_csrf_token/csrf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | // "html/template" 6 | "fmt" 7 | "log" 8 | "math/rand" 9 | "strconv" 10 | "text/template" 11 | "time" 12 | ) 13 | 14 | var sessions = map[string]*Session{} 15 | var cnt = 1 16 | 17 | type Msg struct { 18 | ID int 19 | Message string 20 | Rating int 21 | } 22 | 23 | type Session struct { 24 | UserID uint32 25 | ID string 26 | } 27 | 28 | var messages = map[int]*Msg{} 29 | 30 | var loginFormTmplRaw = ` 31 |
32 | Login: 33 | Password: 34 | 35 |
36 | ` 37 | 38 | var messagesTmpl = ` 39 | 40 | 55 | 56 | 57 | <img src="/rate?id=1&vote=up"> 58 |
59 |
60 | 61 |
62 | 63 |
64 | 65 |
66 |
67 | 68 | {{range $idx, $var := .Messages}} 69 |
70 | 71 | {{$var.Rating}} 72 | 73 |   74 | 75 | 76 | 77 | {{$var.Message}} 78 |
79 | {{end}} 80 | ` 81 | 82 | func main() { 83 | 84 | tmpl := template.New("main") 85 | tmpl, _ = tmpl.Parse(messagesTmpl) 86 | 87 | tokens, _ := NewHMACHashToken("golang") 88 | // tokens, _ := NewAesCryptHashToken("qsRY2e4hcM5T7X984E9WQ5uZ8Nty7fxB") 89 | // tokens, _ := NewJwtToken("qsRY2e4hcM5T7X984E9WQ5uZ8Nty7fxB") 90 | 91 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 92 | sess, err := checkSession(r) 93 | if err != nil { 94 | w.Write([]byte(loginFormTmplRaw)) 95 | return 96 | } 97 | 98 | token, err := tokens.Create(sess, time.Now().Add(24*time.Hour).Unix()) 99 | if err != nil { 100 | log.Println("csrf token creation error:", err) 101 | http.Error(w, "internal error", http.StatusInternalServerError) 102 | return 103 | } 104 | 105 | tmpl.Execute(w, struct { 106 | Messages map[int]*Msg 107 | CSRFToken string 108 | }{ 109 | Messages: messages, 110 | CSRFToken: token, 111 | }) 112 | }) 113 | 114 | http.HandleFunc("/comment", func(w http.ResponseWriter, r *http.Request) { 115 | r.ParseForm() 116 | 117 | sess, err := checkSession(r) 118 | if err != nil || r.Method == http.MethodGet { 119 | w.Write([]byte(loginFormTmplRaw)) 120 | return 121 | } 122 | 123 | CSRFToken := r.FormValue("csrf-token") 124 | _, err = tokens.Check(sess, CSRFToken) 125 | if err != nil { 126 | w.Write([]byte("{}")) 127 | return 128 | } 129 | 130 | commentText := r.FormValue("comment") 131 | id := cnt 132 | messages[id] = &Msg{ 133 | ID: id, 134 | Message: commentText, 135 | Rating: 0, 136 | } 137 | cnt++ 138 | http.Redirect(w, r, "/", http.StatusFound) 139 | }) 140 | 141 | http.HandleFunc("/rate", func(w http.ResponseWriter, r *http.Request) { 142 | w.Header().Set("Content-Type", "application/json") 143 | 144 | emptyResponse := []byte(`{"id":0, "rating":0}`) 145 | sess, err := checkSession(r) 146 | if err != nil || r.Method == http.MethodGet { 147 | w.Write([]byte(emptyResponse)) 148 | return 149 | } 150 | 151 | CSRFToken := r.Header.Get("csrf-token") 152 | _, err = tokens.Check(sess, CSRFToken) 153 | if err != nil { 154 | w.Write([]byte(emptyResponse)) 155 | return 156 | } 157 | 158 | id, _ := strconv.Atoi(r.URL.Query().Get("id")) 159 | vote := r.URL.Query().Get("vote") 160 | 161 | if msg, ok := messages[id]; ok { 162 | if vote == "up" { 163 | msg.Rating++ 164 | } else if vote == "down" { 165 | msg.Rating-- 166 | } 167 | w.Write([]byte(fmt.Sprintf(`{"id":%d, "rating":%d}`, msg.ID, msg.Rating))) 168 | } else { 169 | w.Write([]byte(emptyResponse)) 170 | } 171 | }) 172 | 173 | http.HandleFunc("/clear_comments", func(w http.ResponseWriter, r *http.Request) { 174 | _, err := checkSession(r) 175 | if err != nil { 176 | w.Write([]byte(loginFormTmplRaw)) 177 | return 178 | } 179 | messages = map[int]*Msg{} 180 | http.Redirect(w, r, "/", http.StatusFound) 181 | }) 182 | 183 | http.HandleFunc("/login", loginHandler) 184 | 185 | fmt.Println("starting server at :8080") 186 | http.ListenAndServe(":8080", nil) 187 | } 188 | 189 | func loginHandler(w http.ResponseWriter, r *http.Request) { 190 | r.ParseForm() 191 | expiration := time.Now().Add(365 * 24 * time.Hour) 192 | 193 | sessionID := RandStringRunes(32) 194 | sessions[sessionID] = &Session{ID: sessionID, UserID: 123} 195 | 196 | cookie := http.Cookie{Name: "session_id", Value: sessionID, Expires: expiration} 197 | http.SetCookie(w, &cookie) 198 | http.Redirect(w, r, "/", http.StatusFound) 199 | } 200 | 201 | func checkSession(r *http.Request) (*Session, error) { 202 | sessionID, err := r.Cookie("session_id") 203 | if err == http.ErrNoCookie { 204 | return nil, fmt.Errorf("no cookie") 205 | } else if err != nil { 206 | return nil, err 207 | } 208 | sess, ok := sessions[sessionID.Value] 209 | if !ok { 210 | return nil, fmt.Errorf("no session") 211 | } 212 | return sess, nil 213 | } 214 | 215 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 216 | 217 | func RandStringRunes(n int) string { 218 | b := make([]rune, n) 219 | for i := range b { 220 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 221 | } 222 | return string(b) 223 | } 224 | 225 | // 226 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/a-h/hsts v0.0.0-20170713145656-509101faf0de h1:EA7uAk3mk8uND/JcEnxcHdgE921n3er8ZGYbsyVA498= 2 | github.com/a-h/hsts v0.0.0-20170713145656-509101faf0de/go.mod h1:N65QR4h5nMsbtGbRX/prBVT33qf0E5qXeVdhLZI/lIA= 3 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 4 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 9 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 10 | github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= 11 | github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= 12 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 13 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 14 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 15 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 16 | github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI= 17 | github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= 18 | github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= 19 | github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 20 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 21 | github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= 22 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 23 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 24 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 25 | github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c= 26 | github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE= 27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 29 | github.com/proullon/ramsql v0.0.0-20220702164129-ff9cf4501dd2 h1:ETbFZcLGfL78AfV57l0FHWA3egZSIP0B4jxWgrwE+i0= 30 | github.com/proullon/ramsql v0.0.0-20220702164129-ff9cf4501dd2/go.mod h1:jG8oAQG0ZPHPyxg5QlMERS31airDC+ZuqiAe8DUvFVo= 31 | github.com/skinass/go-sum v0.0.0-20220711074512-1ec2dddcb9eb h1:3qO/4wPxSnc+90H33cHTkuyv0cyorlzyTwEjKt8Q2Vw= 32 | github.com/skinass/go-sum v0.0.0-20220711074512-1ec2dddcb9eb/go.mod h1:FXYAyrDuB+Qyj6kexk1gKQsZZXc5qj+Ql2YDeRm4vMc= 33 | github.com/skinass/go-sum v0.0.0-20220716224551-0911cc392ee5 h1:KYGSpJZuBva7AuVgcEECeM9uIAuB2NpikAs31X5LLyQ= 34 | github.com/skinass/go-sum v0.0.0-20220716224551-0911cc392ee5/go.mod h1:FXYAyrDuB+Qyj6kexk1gKQsZZXc5qj+Ql2YDeRm4vMc= 35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 36 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 37 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 38 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 39 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 40 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 41 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 42 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 43 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 44 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 45 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 46 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 47 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 48 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 49 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 50 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 51 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 52 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 53 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 55 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= 59 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 60 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 61 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 62 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 63 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 64 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 65 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= 66 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 67 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 68 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 69 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 70 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 71 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 72 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 73 | --------------------------------------------------------------------------------