|
| 1 | +using System; |
| 2 | +using System.Text; |
| 3 | +using System.Text.Json; |
| 4 | +using System.Text.Json.Nodes; |
| 5 | +using System.Collections.Generic; |
| 6 | + |
| 7 | +class Program |
| 8 | +{ |
| 9 | + static int Main(string[] args) |
| 10 | + { |
| 11 | + if (args.Length == 0 || Has(args, "-h", "--help")) |
| 12 | + { |
| 13 | + Help(); |
| 14 | + return 0; |
| 15 | + } |
| 16 | + |
| 17 | + string mode = GetArg(args, "--mode"); |
| 18 | + string token = GetArg(args, "-t", "--token"); |
| 19 | + if (string.IsNullOrWhiteSpace(mode) || string.IsNullOrWhiteSpace(token)) return 1; |
| 20 | + |
| 21 | + var parts = token.Split('.'); |
| 22 | + if (parts.Length < 2) return 1; |
| 23 | + |
| 24 | + var headerJson = Decode(parts[0]); |
| 25 | + var payloadJson = Decode(parts[1]); |
| 26 | + if (headerJson == null || payloadJson == null) return 1; |
| 27 | + |
| 28 | + if (mode == "read") |
| 29 | + { |
| 30 | + Console.WriteLine("Header:"); |
| 31 | + Console.WriteLine(headerJson); |
| 32 | + Console.WriteLine(); |
| 33 | + Console.WriteLine("Payload:"); |
| 34 | + Console.WriteLine(payloadJson); |
| 35 | + return 0; |
| 36 | + } |
| 37 | + |
| 38 | + if (mode == "scan") |
| 39 | + { |
| 40 | + Scan(headerJson, payloadJson, parts.Length == 3, token.Length); |
| 41 | + return 0; |
| 42 | + } |
| 43 | + |
| 44 | + if (mode == "edit") |
| 45 | + { |
| 46 | + var header = JsonNode.Parse(headerJson); |
| 47 | + var payload = JsonNode.Parse(payloadJson); |
| 48 | + |
| 49 | + if (Has(args, "--set")) |
| 50 | + { |
| 51 | + var kv = GetArg(args, "--set").Split('=', 2); |
| 52 | + if (kv.Length == 2) |
| 53 | + { |
| 54 | + if (kv[0] == "alg") header[kv[0]] = kv[1]; |
| 55 | + else payload[kv[0]] = JsonValue.Create(ParseValue(kv[1])); |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + if (Has(args, "--remove")) |
| 60 | + { |
| 61 | + var key = GetArg(args, "--remove"); |
| 62 | + payload.AsObject().Remove(key); |
| 63 | + } |
| 64 | + |
| 65 | + PrintNewToken(header, payload); |
| 66 | + return 0; |
| 67 | + } |
| 68 | + |
| 69 | + return 0; |
| 70 | + } |
| 71 | + |
| 72 | + static void Scan(string headerJson, string payloadJson, bool hasSig, int len) |
| 73 | + { |
| 74 | + using var h = JsonDocument.Parse(headerJson); |
| 75 | + using var p = JsonDocument.Parse(payloadJson); |
| 76 | + long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); |
| 77 | + |
| 78 | + if (h.RootElement.TryGetProperty("alg", out var alg)) |
| 79 | + { |
| 80 | + var a = alg.GetString(); |
| 81 | + if (a == "none") Console.WriteLine("VULNERABLE: alg=none"); |
| 82 | + else Console.WriteLine("alg: " + a); |
| 83 | + } |
| 84 | + else Console.WriteLine("WARNING: alg missing"); |
| 85 | + |
| 86 | + if (!hasSig) Console.WriteLine("WARNING: missing signature"); |
| 87 | + |
| 88 | + if (p.RootElement.TryGetProperty("exp", out var exp)) |
| 89 | + { |
| 90 | + if (now > exp.GetInt64()) Console.WriteLine("EXPIRED"); |
| 91 | + else Console.WriteLine("exp: valid"); |
| 92 | + } |
| 93 | + |
| 94 | + if (p.RootElement.TryGetProperty("nbf", out var nbf)) |
| 95 | + if (now < nbf.GetInt64()) Console.WriteLine("NOT VALID YET"); |
| 96 | + |
| 97 | + if (p.RootElement.TryGetProperty("iat", out var iat)) |
| 98 | + if (iat.GetInt64() > now) Console.WriteLine("WARNING: iat future"); |
| 99 | + |
| 100 | + if (!p.RootElement.TryGetProperty("iss", out _)) Console.WriteLine("INFO: iss missing"); |
| 101 | + if (!p.RootElement.TryGetProperty("aud", out _)) Console.WriteLine("INFO: aud missing"); |
| 102 | + if (len > 4096) Console.WriteLine("WARNING: large token"); |
| 103 | + } |
| 104 | + |
| 105 | + static void PrintNewToken(JsonNode h, JsonNode p) |
| 106 | + { |
| 107 | + string eh = Encode(h.ToJsonString()); |
| 108 | + string ep = Encode(p.ToJsonString()); |
| 109 | + Console.WriteLine("Modified JWT:"); |
| 110 | + Console.WriteLine($"{eh}.{ep}."); |
| 111 | + } |
| 112 | + |
| 113 | + static string Decode(string s) |
| 114 | + { |
| 115 | + try |
| 116 | + { |
| 117 | + s = s.Replace('-', '+').Replace('_', '/'); |
| 118 | + if (s.Length % 4 == 2) s += "=="; |
| 119 | + if (s.Length % 4 == 3) s += "="; |
| 120 | + return Encoding.UTF8.GetString(Convert.FromBase64String(s)); |
| 121 | + } |
| 122 | + catch { return null; } |
| 123 | + } |
| 124 | + |
| 125 | + static string Encode(string j) |
| 126 | + { |
| 127 | + return Convert.ToBase64String(Encoding.UTF8.GetBytes(j)).TrimEnd('=').Replace('+', '-').Replace('/', '_'); |
| 128 | + } |
| 129 | + |
| 130 | + static object ParseValue(string v) |
| 131 | + { |
| 132 | + if (long.TryParse(v, out var l)) return l; |
| 133 | + if (bool.TryParse(v, out var b)) return b; |
| 134 | + return v; |
| 135 | + } |
| 136 | + |
| 137 | + static bool Has(string[] a, params string[] k) |
| 138 | + { |
| 139 | + foreach (var x in a) |
| 140 | + foreach (var y in k) |
| 141 | + if (x == y) return true; |
| 142 | + return false; |
| 143 | + } |
| 144 | + |
| 145 | + static string GetArg(string[] a, params string[] k) |
| 146 | + { |
| 147 | + for (int i = 0; i < a.Length - 1; i++) |
| 148 | + foreach (var y in k) |
| 149 | + if (a[i] == y) return a[i + 1]; |
| 150 | + return null; |
| 151 | + } |
| 152 | + |
| 153 | + static void Help() |
| 154 | + { |
| 155 | + Console.WriteLine("Usage:"); |
| 156 | + Console.WriteLine("--mode read|scan|edit -t <JWT>"); |
| 157 | + Console.WriteLine("--set key=value"); |
| 158 | + Console.WriteLine("--remove key"); |
| 159 | + Console.WriteLine("-h --help"); |
| 160 | + } |
| 161 | +} |
0 commit comments